001    package org.apache.maven.model.building;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *   http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import java.io.File;
023    import java.io.IOException;
024    import java.util.ArrayList;
025    import java.util.Collection;
026    import java.util.Iterator;
027    import java.util.LinkedHashSet;
028    import java.util.List;
029    import java.util.Map;
030    import java.util.HashMap;
031    import java.util.Properties;
032    
033    import org.apache.maven.model.Build;
034    import org.apache.maven.model.Dependency;
035    import org.apache.maven.model.DependencyManagement;
036    import org.apache.maven.model.InputLocation;
037    import org.apache.maven.model.InputSource;
038    import org.apache.maven.model.Model;
039    import org.apache.maven.model.Parent;
040    import org.apache.maven.model.Plugin;
041    import org.apache.maven.model.PluginManagement;
042    import org.apache.maven.model.Profile;
043    import org.apache.maven.model.Repository;
044    import org.apache.maven.model.building.ModelProblem.Severity;
045    import org.apache.maven.model.building.ModelProblem.Version;
046    import org.apache.maven.model.composition.DependencyManagementImporter;
047    import org.apache.maven.model.inheritance.InheritanceAssembler;
048    import org.apache.maven.model.interpolation.ModelInterpolator;
049    import org.apache.maven.model.io.ModelParseException;
050    import org.apache.maven.model.management.DependencyManagementInjector;
051    import org.apache.maven.model.management.PluginManagementInjector;
052    import org.apache.maven.model.normalization.ModelNormalizer;
053    import org.apache.maven.model.path.ModelPathTranslator;
054    import org.apache.maven.model.path.ModelUrlNormalizer;
055    import org.apache.maven.model.plugin.LifecycleBindingsInjector;
056    import org.apache.maven.model.plugin.PluginConfigurationExpander;
057    import org.apache.maven.model.plugin.ReportConfigurationExpander;
058    import org.apache.maven.model.plugin.ReportingConverter;
059    import org.apache.maven.model.profile.DefaultProfileActivationContext;
060    import org.apache.maven.model.profile.ProfileInjector;
061    import org.apache.maven.model.profile.ProfileSelector;
062    import org.apache.maven.model.resolution.InvalidRepositoryException;
063    import org.apache.maven.model.resolution.ModelResolver;
064    import org.apache.maven.model.resolution.UnresolvableModelException;
065    import org.apache.maven.model.superpom.SuperPomProvider;
066    import org.apache.maven.model.validation.ModelValidator;
067    import org.codehaus.plexus.component.annotations.Component;
068    import org.codehaus.plexus.component.annotations.Requirement;
069    
070    /**
071     * @author Benjamin Bentmann
072     */
073    @Component( role = ModelBuilder.class )
074    public class DefaultModelBuilder
075        implements ModelBuilder
076    {
077        @Requirement
078        private ModelProcessor modelProcessor;
079    
080        @Requirement
081        private ModelValidator modelValidator;
082    
083        @Requirement
084        private ModelNormalizer modelNormalizer;
085    
086        @Requirement
087        private ModelInterpolator modelInterpolator;
088    
089        @Requirement
090        private ModelPathTranslator modelPathTranslator;
091    
092        @Requirement
093        private ModelUrlNormalizer modelUrlNormalizer;
094    
095        @Requirement
096        private SuperPomProvider superPomProvider;
097    
098        @Requirement
099        private InheritanceAssembler inheritanceAssembler;
100    
101        @Requirement
102        private ProfileSelector profileSelector;
103    
104        @Requirement
105        private ProfileInjector profileInjector;
106    
107        @Requirement
108        private PluginManagementInjector pluginManagementInjector;
109    
110        @Requirement
111        private DependencyManagementInjector dependencyManagementInjector;
112    
113        @Requirement
114        private DependencyManagementImporter dependencyManagementImporter;
115    
116        @Requirement( optional = true )
117        private LifecycleBindingsInjector lifecycleBindingsInjector;
118    
119        @Requirement
120        private PluginConfigurationExpander pluginConfigurationExpander;
121    
122        @Requirement
123        private ReportConfigurationExpander reportConfigurationExpander;
124    
125        @Requirement
126        private ReportingConverter reportingConverter;
127    
128        public DefaultModelBuilder setModelProcessor( ModelProcessor modelProcessor )
129        {
130            this.modelProcessor = modelProcessor;
131            return this;
132        }
133    
134        public DefaultModelBuilder setModelValidator( ModelValidator modelValidator )
135        {
136            this.modelValidator = modelValidator;
137            return this;
138        }
139    
140        public DefaultModelBuilder setModelNormalizer( ModelNormalizer modelNormalizer )
141        {
142            this.modelNormalizer = modelNormalizer;
143            return this;
144        }
145    
146        public DefaultModelBuilder setModelInterpolator( ModelInterpolator modelInterpolator )
147        {
148            this.modelInterpolator = modelInterpolator;
149            return this;
150        }
151    
152        public DefaultModelBuilder setModelPathTranslator( ModelPathTranslator modelPathTranslator )
153        {
154            this.modelPathTranslator = modelPathTranslator;
155            return this;
156        }
157    
158        public DefaultModelBuilder setModelUrlNormalizer( ModelUrlNormalizer modelUrlNormalizer )
159        {
160            this.modelUrlNormalizer = modelUrlNormalizer;
161            return this;
162        }
163    
164        public DefaultModelBuilder setSuperPomProvider( SuperPomProvider superPomProvider )
165        {
166            this.superPomProvider = superPomProvider;
167            return this;
168        }
169    
170        public DefaultModelBuilder setProfileSelector( ProfileSelector profileSelector )
171        {
172            this.profileSelector = profileSelector;
173            return this;
174        }
175    
176        public DefaultModelBuilder setProfileInjector( ProfileInjector profileInjector )
177        {
178            this.profileInjector = profileInjector;
179            return this;
180        }
181    
182        public DefaultModelBuilder setInheritanceAssembler( InheritanceAssembler inheritanceAssembler )
183        {
184            this.inheritanceAssembler = inheritanceAssembler;
185            return this;
186        }
187    
188        public DefaultModelBuilder setDependencyManagementImporter( DependencyManagementImporter depMngmntImporter )
189        {
190            this.dependencyManagementImporter = depMngmntImporter;
191            return this;
192        }
193    
194        public DefaultModelBuilder setDependencyManagementInjector( DependencyManagementInjector depMngmntInjector )
195        {
196            this.dependencyManagementInjector = depMngmntInjector;
197            return this;
198        }
199    
200        public DefaultModelBuilder setLifecycleBindingsInjector( LifecycleBindingsInjector lifecycleBindingsInjector )
201        {
202            this.lifecycleBindingsInjector = lifecycleBindingsInjector;
203            return this;
204        }
205    
206        public DefaultModelBuilder setPluginConfigurationExpander( PluginConfigurationExpander pluginConfigurationExpander )
207        {
208            this.pluginConfigurationExpander = pluginConfigurationExpander;
209            return this;
210        }
211    
212        public DefaultModelBuilder setPluginManagementInjector( PluginManagementInjector pluginManagementInjector )
213        {
214            this.pluginManagementInjector = pluginManagementInjector;
215            return this;
216        }
217    
218        public DefaultModelBuilder setReportConfigurationExpander( ReportConfigurationExpander reportConfigurationExpander )
219        {
220            this.reportConfigurationExpander = reportConfigurationExpander;
221            return this;
222        }
223    
224        public DefaultModelBuilder setReportingConverter( ReportingConverter reportingConverter )
225        {
226            this.reportingConverter = reportingConverter;
227            return this;
228        }
229    
230        public ModelBuildingResult build( ModelBuildingRequest request )
231            throws ModelBuildingException
232        {
233            DefaultModelBuildingResult result = new DefaultModelBuildingResult();
234    
235            DefaultModelProblemCollector problems = new DefaultModelProblemCollector( result );
236    
237            DefaultProfileActivationContext profileActivationContext = getProfileActivationContext( request );
238    
239            problems.setSource( "(external profiles)" );
240            List<Profile> activeExternalProfiles =
241                profileSelector.getActiveProfiles( request.getProfiles(), profileActivationContext, problems );
242    
243            result.setActiveExternalProfiles( activeExternalProfiles );
244    
245            if ( !activeExternalProfiles.isEmpty() )
246            {
247                Properties profileProps = new Properties();
248                for ( Profile profile : activeExternalProfiles )
249                {
250                    profileProps.putAll( profile.getProperties() );
251                }
252                profileProps.putAll( profileActivationContext.getUserProperties() );
253                profileActivationContext.setUserProperties( profileProps );
254            }
255    
256            Model inputModel = readModel( request.getModelSource(), request.getPomFile(), request, problems );
257    
258            problems.setRootModel( inputModel );
259    
260            ModelData resultData = new ModelData( request.getModelSource(), inputModel );
261            ModelData superData = new ModelData( null, getSuperModel() );
262    
263            Collection<String> parentIds = new LinkedHashSet<String>();
264            parentIds.add( ModelProblemUtils.toId( inputModel ) );
265    
266            List<ModelData> lineage = new ArrayList<ModelData>();
267    
268            for ( ModelData currentData = resultData; currentData != null; )
269            {
270                lineage.add( currentData );
271    
272                Model tmpModel = currentData.getModel();
273    
274                Model rawModel = tmpModel.clone();
275                currentData.setRawModel( rawModel );
276    
277                problems.setSource( tmpModel );
278    
279                modelNormalizer.mergeDuplicates( tmpModel, request, problems );
280    
281                profileActivationContext.setProjectProperties( tmpModel.getProperties() );
282    
283                List<Profile> activePomProfiles =
284                    profileSelector.getActiveProfiles( rawModel.getProfiles(), profileActivationContext, problems );
285                currentData.setActiveProfiles( activePomProfiles );
286    
287                for ( Profile activeProfile : activePomProfiles )
288                {
289                    profileInjector.injectProfile( tmpModel, activeProfile, request, problems );
290                }
291    
292                if ( currentData == resultData )
293                {
294                    for ( Profile activeProfile : activeExternalProfiles )
295                    {
296                        profileInjector.injectProfile( tmpModel, activeProfile, request, problems );
297                    }
298                }
299    
300                if ( currentData == superData )
301                {
302                    break;
303                }
304    
305                configureResolver( request.getModelResolver(), tmpModel, problems );
306    
307                currentData = readParent( tmpModel, currentData.getSource(), request, problems );
308    
309                if ( currentData == null )
310                {
311                    currentData = superData;
312                }
313                else if ( !parentIds.add( currentData.getId() ) )
314                {
315                    String message = "The parents form a cycle: ";
316                    for ( String modelId : parentIds )
317                    {
318                        message += modelId + " -> ";
319                    }
320                    message += currentData.getId();
321    
322                    problems.add( new ModelProblemCollectorRequest( ModelProblem.Severity.FATAL, ModelProblem.Version.BASE ).setMessage( message ) );
323                    throw problems.newModelBuildingException();
324                }
325            }
326    
327            problems.setSource( inputModel );
328            checkPluginVersions( lineage, request, problems );
329    
330            assembleInheritance( lineage, request, problems );
331    
332            Model resultModel = resultData.getModel();
333    
334            problems.setSource( resultModel );
335            problems.setRootModel( resultModel );
336    
337            resultModel = interpolateModel( resultModel, request, problems );
338            resultData.setModel( resultModel );
339    
340            modelUrlNormalizer.normalize( resultModel, request );
341    
342            resultData.setGroupId( resultModel.getGroupId() );
343            resultData.setArtifactId( resultModel.getArtifactId() );
344            resultData.setVersion( resultModel.getVersion() );
345    
346            result.setEffectiveModel( resultModel );
347    
348            for ( ModelData currentData : lineage )
349            {
350                String modelId = ( currentData != superData ) ? currentData.getId() : "";
351    
352                result.addModelId( modelId );
353                result.setActivePomProfiles( modelId, currentData.getActiveProfiles() );
354                result.setRawModel( modelId, currentData.getRawModel() );
355            }
356    
357            if ( !request.isTwoPhaseBuilding() )
358            {
359                build( request, result );
360            }
361    
362            return result;
363        }
364    
365        public ModelBuildingResult build( ModelBuildingRequest request, ModelBuildingResult result )
366            throws ModelBuildingException
367        {
368            return build( request, result, new LinkedHashSet<String>() );
369        }
370    
371        private ModelBuildingResult build( ModelBuildingRequest request, ModelBuildingResult result,
372                                           Collection<String> imports )
373            throws ModelBuildingException
374        {
375            Model resultModel = result.getEffectiveModel();
376    
377            DefaultModelProblemCollector problems = new DefaultModelProblemCollector( result );
378            problems.setSource( resultModel );
379            problems.setRootModel( resultModel );
380    
381            modelPathTranslator.alignToBaseDirectory( resultModel, resultModel.getProjectDirectory(), request );
382    
383            pluginManagementInjector.injectManagement( resultModel, request, problems );
384    
385            fireEvent( resultModel, request, problems, ModelBuildingEventCatapult.BUILD_EXTENSIONS_ASSEMBLED );
386    
387            if ( request.isProcessPlugins() )
388            {
389                if ( lifecycleBindingsInjector == null )
390                {
391                    throw new IllegalStateException( "lifecycle bindings injector is missing" );
392                }
393    
394                lifecycleBindingsInjector.injectLifecycleBindings( resultModel, request, problems );
395            }
396    
397            importDependencyManagement( resultModel, request, problems, imports );
398    
399            dependencyManagementInjector.injectManagement( resultModel, request, problems );
400    
401            modelNormalizer.injectDefaultValues( resultModel, request, problems );
402    
403            if ( request.isProcessPlugins() )
404            {
405                reportConfigurationExpander.expandPluginConfiguration( resultModel, request, problems );
406    
407                reportingConverter.convertReporting( resultModel, request, problems );
408    
409                pluginConfigurationExpander.expandPluginConfiguration( resultModel, request, problems );
410            }
411    
412            modelValidator.validateEffectiveModel( resultModel, request, problems );
413    
414            if ( hasModelErrors( problems ) )
415            {
416                throw problems.newModelBuildingException();
417            }
418    
419            return result;
420        }
421    
422        private Model readModel( ModelSource modelSource, File pomFile, ModelBuildingRequest request,
423                                 DefaultModelProblemCollector problems )
424            throws ModelBuildingException
425        {
426            Model model;
427    
428            if ( modelSource == null )
429            {
430                if ( pomFile != null )
431                {
432                    modelSource = new FileModelSource( pomFile );
433                }
434                else
435                {
436                    throw new IllegalArgumentException( "neither model source nor input file are specified" );
437                }
438            }
439    
440            problems.setSource( modelSource.getLocation() );
441            try
442            {
443                boolean strict = request.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0;
444                InputSource source = request.isLocationTracking() ? new InputSource() : null;
445    
446                Map<String, Object> options = new HashMap<String, Object>();
447                options.put( ModelProcessor.IS_STRICT, strict );
448                options.put( ModelProcessor.INPUT_SOURCE, source );
449                options.put( ModelProcessor.SOURCE, modelSource );
450    
451                try
452                {
453                    model = modelProcessor.read( modelSource.getInputStream(), options );
454                }
455                catch ( ModelParseException e )
456                {
457                    if ( !strict )
458                    {
459                        throw e;
460                    }
461    
462                    options.put( ModelProcessor.IS_STRICT, Boolean.FALSE );
463    
464                    try
465                    {
466                        model = modelProcessor.read( modelSource.getInputStream(), options );
467                    }
468                    catch ( ModelParseException ne )
469                    {
470                        // still unreadable even in non-strict mode, rethrow original error
471                        throw e;
472                    }
473    
474                    if ( pomFile != null )
475                    {
476                        problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.V20 )
477                                .setMessage( "Malformed POM " + modelSource.getLocation() + ": " + e.getMessage() )
478                                .setException( e ) );
479                    }
480                    else
481                    {
482                        problems.add( new ModelProblemCollectorRequest( Severity.WARNING, Version.V20 )
483                                .setMessage( "Malformed POM " + modelSource.getLocation() + ": " + e.getMessage() )
484                                .setException( e ) );
485                    }
486                }
487    
488                if ( source != null )
489                {
490                    source.setModelId( ModelProblemUtils.toId( model ) );
491                    source.setLocation( modelSource.getLocation() );
492                }
493            }
494            catch ( ModelParseException e )
495            {
496                problems.add( new ModelProblemCollectorRequest( Severity.FATAL, Version.BASE )
497                        .setMessage( "Non-parseable POM " + modelSource.getLocation() + ": " + e.getMessage() )
498                        .setException( e ) );
499                throw problems.newModelBuildingException();
500            }
501            catch ( IOException e )
502            {
503                String msg = e.getMessage();
504                if ( msg == null || msg.length() <= 0 )
505                {
506                    // NOTE: There's java.nio.charset.MalformedInputException and sun.io.MalformedInputException
507                    if ( e.getClass().getName().endsWith( "MalformedInputException" ) )
508                    {
509                        msg = "Some input bytes do not match the file encoding.";
510                    }
511                    else
512                    {
513                        msg = e.getClass().getSimpleName();
514                    }
515                }
516                problems.add( new ModelProblemCollectorRequest( Severity.FATAL, Version.BASE )
517                        .setMessage( "Non-readable POM " + modelSource.getLocation() + ": " + msg )
518                        .setException( e ) );
519                throw problems.newModelBuildingException();
520            }
521    
522            model.setPomFile( pomFile );
523    
524            problems.setSource( model );
525            modelValidator.validateRawModel( model, request, problems );
526    
527            if ( hasFatalErrors( problems ) )
528            {
529                throw problems.newModelBuildingException();
530            }
531    
532            return model;
533        }
534    
535        private DefaultProfileActivationContext getProfileActivationContext( ModelBuildingRequest request )
536        {
537            DefaultProfileActivationContext context = new DefaultProfileActivationContext();
538    
539            context.setActiveProfileIds( request.getActiveProfileIds() );
540            context.setInactiveProfileIds( request.getInactiveProfileIds() );
541            context.setSystemProperties( request.getSystemProperties() );
542            context.setUserProperties( request.getUserProperties() );
543            context.setProjectDirectory( ( request.getPomFile() != null ) ? request.getPomFile().getParentFile() : null );
544    
545            return context;
546        }
547    
548        private void configureResolver( ModelResolver modelResolver, Model model, DefaultModelProblemCollector problems )
549        {
550            if ( modelResolver == null )
551            {
552                return;
553            }
554    
555            problems.setSource( model );
556    
557            List<Repository> repositories = model.getRepositories();
558    
559            for ( Repository repository : repositories )
560            {
561                try
562                {
563                    modelResolver.addRepository( repository );
564                }
565                catch ( InvalidRepositoryException e )
566                {
567                    problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE )
568                            .setMessage( "Invalid repository " + repository.getId() + ": " + e.getMessage() )
569                            .setLocation( repository.getLocation( "" ) )
570                            .setException( e ) );
571                }
572            }
573        }
574    
575        private void checkPluginVersions( List<ModelData> lineage, ModelBuildingRequest request,
576                                          ModelProblemCollector problems )
577        {
578            if ( request.getValidationLevel() < ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0 )
579            {
580                return;
581            }
582    
583            Map<String, Plugin> plugins = new HashMap<String, Plugin>();
584            Map<String, String> versions = new HashMap<String, String>();
585            Map<String, String> managedVersions = new HashMap<String, String>();
586    
587            for ( int i = lineage.size() - 1; i >= 0; i-- )
588            {
589                Model model = lineage.get( i ).getModel();
590                Build build = model.getBuild();
591                if ( build != null )
592                {
593                    for ( Plugin plugin : build.getPlugins() )
594                    {
595                        String key = plugin.getKey();
596                        if ( versions.get( key ) == null )
597                        {
598                            versions.put( key, plugin.getVersion() );
599                            plugins.put( key, plugin );
600                        }
601                    }
602                    PluginManagement mngt = build.getPluginManagement();
603                    if ( mngt != null )
604                    {
605                        for ( Plugin plugin : mngt.getPlugins() )
606                        {
607                            String key = plugin.getKey();
608                            if ( managedVersions.get( key ) == null )
609                            {
610                                managedVersions.put( key, plugin.getVersion() );
611                            }
612                        }
613                    }
614                }
615            }
616    
617            for ( String key : versions.keySet() )
618            {
619                if ( versions.get( key ) == null && managedVersions.get( key ) == null )
620                {
621                    InputLocation location = plugins.get( key ).getLocation( "" );
622                    problems.add( new ModelProblemCollectorRequest( Severity.WARNING, Version.V20 )
623                            .setMessage( "'build.plugins.plugin.version' for " + key + " is missing." )
624                            .setLocation( location ) );
625                }
626            }
627        }
628    
629        private void assembleInheritance( List<ModelData> lineage, ModelBuildingRequest request,
630                                          ModelProblemCollector problems )
631        {
632            for ( int i = lineage.size() - 2; i >= 0; i-- )
633            {
634                Model parent = lineage.get( i + 1 ).getModel();
635                Model child = lineage.get( i ).getModel();
636                inheritanceAssembler.assembleModelInheritance( child, parent, request, problems );
637            }
638        }
639    
640        private Model interpolateModel( Model model, ModelBuildingRequest request, ModelProblemCollector problems )
641        {
642            Model result = modelInterpolator.interpolateModel( model, model.getProjectDirectory(), request, problems );
643            result.setPomFile( model.getPomFile() );
644            return result;
645        }
646    
647        private ModelData readParent( Model childModel, ModelSource childSource, ModelBuildingRequest request,
648                                      DefaultModelProblemCollector problems )
649            throws ModelBuildingException
650        {
651            ModelData parentData;
652    
653            Parent parent = childModel.getParent();
654    
655            if ( parent != null )
656            {
657                String groupId = parent.getGroupId();
658                String artifactId = parent.getArtifactId();
659                String version = parent.getVersion();
660    
661                parentData = getCache( request.getModelCache(), groupId, artifactId, version, ModelCacheTag.RAW );
662    
663                if ( parentData == null )
664                {
665                    parentData = readParentLocally( childModel, childSource, request, problems );
666    
667                    if ( parentData == null )
668                    {
669                        parentData = readParentExternally( childModel, request, problems );
670                    }
671    
672                    putCache( request.getModelCache(), groupId, artifactId, version, ModelCacheTag.RAW, parentData );
673                }
674                else
675                {
676                    /*
677                     * NOTE: This is a sanity check of the cache hit. If the cached parent POM was locally resolved, the
678                     * child's <relativePath> should point at that parent, too. If it doesn't, we ignore the cache and
679                     * resolve externally, to mimic the behavior if the cache didn't exist in the first place. Otherwise,
680                     * the cache would obscure a bad POM.
681                     */
682    
683                    File pomFile = parentData.getModel().getPomFile();
684                    if ( pomFile != null )
685                    {
686                        ModelSource expectedParentSource = getParentPomFile( childModel, childSource );
687    
688                        if ( expectedParentSource instanceof ModelSource2
689                            && !pomFile.toURI().equals( ( (ModelSource2) expectedParentSource ).getLocationURI() ) )
690                        {
691                            parentData = readParentExternally( childModel, request, problems );
692                        }
693                    }
694                }
695    
696                Model parentModel = parentData.getModel();
697    
698                if ( !"pom".equals( parentModel.getPackaging() ) )
699                {
700                    problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE )
701                            .setMessage( "Invalid packaging for parent POM " + ModelProblemUtils.toSourceHint( parentModel )
702                                         + ", must be \"pom\" but is \"" + parentModel.getPackaging() + "\"" )
703                            .setLocation( parentModel.getLocation( "packaging" ) ) );
704                }
705            }
706            else
707            {
708                parentData = null;
709            }
710    
711            return parentData;
712        }
713    
714        private ModelData readParentLocally( Model childModel, ModelSource childSource, ModelBuildingRequest request,
715                                             DefaultModelProblemCollector problems )
716            throws ModelBuildingException
717        {
718            ModelSource candidateSource = getParentPomFile( childModel, childSource );
719    
720            if ( candidateSource == null )
721            {
722                return null;
723            }
724    
725            File pomFile = null;
726            if ( candidateSource instanceof FileModelSource )
727            {
728                pomFile = ( (FileModelSource) candidateSource ).getPomFile();
729            }
730    
731            Model candidateModel = readModel( candidateSource, pomFile, request, problems );
732    
733            String groupId = candidateModel.getGroupId();
734            if ( groupId == null && candidateModel.getParent() != null )
735            {
736                groupId = candidateModel.getParent().getGroupId();
737            }
738            String artifactId = candidateModel.getArtifactId();
739            String version = candidateModel.getVersion();
740            if ( version == null && candidateModel.getParent() != null )
741            {
742                version = candidateModel.getParent().getVersion();
743            }
744    
745            Parent parent = childModel.getParent();
746    
747            if ( groupId == null || !groupId.equals( parent.getGroupId() ) || artifactId == null
748                || !artifactId.equals( parent.getArtifactId() ) )
749            {
750                StringBuilder buffer = new StringBuilder( 256 );
751                buffer.append( "'parent.relativePath'" );
752                if ( childModel != problems.getRootModel() )
753                {
754                    buffer.append( " of POM " ).append( ModelProblemUtils.toSourceHint( childModel ) );
755                }
756                buffer.append( " points at " ).append( groupId ).append( ":" ).append( artifactId );
757                buffer.append( " instead of " ).append( parent.getGroupId() ).append( ":" ).append( parent.getArtifactId() );
758                buffer.append( ", please verify your project structure" );
759    
760                problems.setSource( childModel );
761                problems.add( new ModelProblemCollectorRequest( Severity.WARNING, Version.BASE )
762                        .setMessage( buffer.toString() )
763                        .setLocation( parent.getLocation( "" ) ) );
764                return null;
765            }
766            if ( version == null || !version.equals( parent.getVersion() ) )
767            {
768                return null;
769            }
770    
771            ModelData parentData = new ModelData( candidateSource, candidateModel, groupId, artifactId, version );
772    
773            return parentData;
774        }
775    
776        private ModelSource getParentPomFile( Model childModel, ModelSource source )
777        {
778            if ( !( source instanceof ModelSource2 ) )
779            {
780                return null;
781            }
782    
783            String parentPath = childModel.getParent().getRelativePath();
784    
785            if ( parentPath == null || parentPath.length() <= 0 )
786            {
787                return null;
788            }
789    
790            return ( (ModelSource2) source ).getRelatedSource( parentPath );
791        }
792    
793        private ModelData readParentExternally( Model childModel, ModelBuildingRequest request,
794                                                DefaultModelProblemCollector problems )
795            throws ModelBuildingException
796        {
797            problems.setSource( childModel );
798    
799            Parent parent = childModel.getParent();
800    
801            String groupId = parent.getGroupId();
802            String artifactId = parent.getArtifactId();
803            String version = parent.getVersion();
804    
805            ModelResolver modelResolver = request.getModelResolver();
806    
807            if ( modelResolver == null )
808            {
809                throw new IllegalArgumentException( "no model resolver provided, cannot resolve parent POM "
810                    + ModelProblemUtils.toId( groupId, artifactId, version ) + " for POM "
811                    + ModelProblemUtils.toSourceHint( childModel ) );
812            }
813    
814            ModelSource modelSource;
815            try
816            {
817                modelSource = modelResolver.resolveModel( groupId, artifactId, version );
818            }
819            catch ( UnresolvableModelException e )
820            {
821                StringBuilder buffer = new StringBuilder( 256 );
822                buffer.append( "Non-resolvable parent POM" );
823                if ( !containsCoordinates( e.getMessage(), groupId, artifactId, version ) )
824                {
825                    buffer.append( " " ).append( ModelProblemUtils.toId( groupId, artifactId, version ) );
826                }
827                if ( childModel != problems.getRootModel() )
828                {
829                    buffer.append( " for " ).append( ModelProblemUtils.toId( childModel ) );
830                }
831                buffer.append( ": " ).append( e.getMessage() );
832                if ( childModel.getProjectDirectory() != null )
833                {
834                    if ( parent.getRelativePath() == null || parent.getRelativePath().length() <= 0 )
835                    {
836                        buffer.append( " and 'parent.relativePath' points at no local POM" );
837                    }
838                    else
839                    {
840                        buffer.append( " and 'parent.relativePath' points at wrong local POM" );
841                    }
842                }
843    
844                problems.add( new ModelProblemCollectorRequest( Severity.FATAL, Version.BASE )
845                        .setMessage( buffer.toString() )
846                        .setLocation( parent.getLocation( "" ) )
847                        .setException( e ) );
848                throw problems.newModelBuildingException();
849            }
850    
851            ModelBuildingRequest lenientRequest = request;
852            if ( request.getValidationLevel() > ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0 )
853            {
854                lenientRequest = new FilterModelBuildingRequest( request )
855                {
856                    @Override
857                    public int getValidationLevel()
858                    {
859                        return ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0;
860                    }
861                };
862            }
863    
864            Model parentModel = readModel( modelSource, null, lenientRequest, problems );
865    
866            ModelData parentData = new ModelData( modelSource, parentModel, groupId, artifactId, version );
867    
868            return parentData;
869        }
870    
871        private Model getSuperModel()
872        {
873            return superPomProvider.getSuperModel( "4.0.0" ).clone();
874        }
875    
876        private void importDependencyManagement( Model model, ModelBuildingRequest request,
877                                                 DefaultModelProblemCollector problems, Collection<String> importIds )
878        {
879            DependencyManagement depMngt = model.getDependencyManagement();
880    
881            if ( depMngt == null )
882            {
883                return;
884            }
885    
886            String importing = model.getGroupId() + ':' + model.getArtifactId() + ':' + model.getVersion();
887    
888            importIds.add( importing );
889    
890            ModelResolver modelResolver = request.getModelResolver();
891    
892            ModelBuildingRequest importRequest = null;
893    
894            List<DependencyManagement> importMngts = null;
895    
896            for ( Iterator<Dependency> it = depMngt.getDependencies().iterator(); it.hasNext(); )
897            {
898                Dependency dependency = it.next();
899    
900                if ( !"pom".equals( dependency.getType() ) || !"import".equals( dependency.getScope() ) )
901                {
902                    continue;
903                }
904    
905                it.remove();
906    
907                String groupId = dependency.getGroupId();
908                String artifactId = dependency.getArtifactId();
909                String version = dependency.getVersion();
910    
911                if ( groupId == null || groupId.length() <= 0 )
912                {
913                    problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE )
914                            .setMessage( "'dependencyManagement.dependencies.dependency.groupId' for "
915                                            + dependency.getManagementKey() + " is missing." )
916                            .setLocation( dependency.getLocation( "" ) ) );
917                    continue;
918                }
919                if ( artifactId == null || artifactId.length() <= 0 )
920                {
921                    problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE )
922                            .setMessage( "'dependencyManagement.dependencies.dependency.artifactId' for "
923                                            + dependency.getManagementKey() + " is missing." )
924                            .setLocation( dependency.getLocation( "" ) ) );
925                    continue;
926                }
927                if ( version == null || version.length() <= 0 )
928                {
929                    problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE )
930                            .setMessage( "'dependencyManagement.dependencies.dependency.version' for "
931                                            + dependency.getManagementKey() + " is missing." )
932                            .setLocation( dependency.getLocation( "" ) ) );
933                    continue;
934                }
935    
936                String imported = groupId + ':' + artifactId + ':' + version;
937    
938                if ( importIds.contains( imported ) )
939                {
940                    String message = "The dependencies of type=pom and with scope=import form a cycle: ";
941                    for ( String modelId : importIds )
942                    {
943                        message += modelId + " -> ";
944                    }
945                    message += imported;
946                    problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE ).setMessage( message ) );
947    
948                    continue;
949                }
950    
951                DependencyManagement importMngt =
952                    getCache( request.getModelCache(), groupId, artifactId, version, ModelCacheTag.IMPORT );
953    
954                if ( importMngt == null )
955                {
956                    if ( modelResolver == null )
957                    {
958                        throw new IllegalArgumentException( "no model resolver provided, cannot resolve import POM "
959                            + ModelProblemUtils.toId( groupId, artifactId, version ) + " for POM "
960                            + ModelProblemUtils.toSourceHint( model ) );
961                    }
962    
963                    ModelSource importSource;
964                    try
965                    {
966                        importSource = modelResolver.resolveModel( groupId, artifactId, version );
967                    }
968                    catch ( UnresolvableModelException e )
969                    {
970                        StringBuilder buffer = new StringBuilder( 256 );
971                        buffer.append( "Non-resolvable import POM" );
972                        if ( !containsCoordinates( e.getMessage(), groupId, artifactId, version ) )
973                        {
974                            buffer.append( " " ).append( ModelProblemUtils.toId( groupId, artifactId, version ) );
975                        }
976                        buffer.append( ": " ).append( e.getMessage() );
977    
978                        problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE )
979                                .setMessage( buffer.toString() )
980                                .setLocation( dependency.getLocation( "" ) )
981                                .setException( e ) );
982                        continue;
983                    }
984    
985                    if ( importRequest == null )
986                    {
987                        importRequest = new DefaultModelBuildingRequest();
988                        importRequest.setValidationLevel( ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL );
989                        importRequest.setModelCache( request.getModelCache() );
990                        importRequest.setSystemProperties( request.getSystemProperties() );
991                        importRequest.setUserProperties( request.getUserProperties() );
992                        importRequest.setLocationTracking( request.isLocationTracking() );
993                    }
994    
995                    importRequest.setModelSource( importSource );
996                    importRequest.setModelResolver( modelResolver.newCopy() );
997    
998                    ModelBuildingResult importResult;
999                    try
1000                    {
1001                        importResult = build( importRequest );
1002                    }
1003                    catch ( ModelBuildingException e )
1004                    {
1005                        problems.addAll( e.getProblems() );
1006                        continue;
1007                    }
1008    
1009                    problems.addAll( importResult.getProblems() );
1010    
1011                    Model importModel = importResult.getEffectiveModel();
1012    
1013                    importMngt = importModel.getDependencyManagement();
1014    
1015                    if ( importMngt == null )
1016                    {
1017                        importMngt = new DependencyManagement();
1018                    }
1019    
1020                    putCache( request.getModelCache(), groupId, artifactId, version, ModelCacheTag.IMPORT, importMngt );
1021                }
1022    
1023                if ( importMngts == null )
1024                {
1025                    importMngts = new ArrayList<DependencyManagement>();
1026                }
1027    
1028                importMngts.add( importMngt );
1029            }
1030    
1031            importIds.remove( importing );
1032    
1033            dependencyManagementImporter.importManagement( model, importMngts, request, problems );
1034        }
1035    
1036        private <T> void putCache( ModelCache modelCache, String groupId, String artifactId, String version,
1037                                   ModelCacheTag<T> tag, T data )
1038        {
1039            if ( modelCache != null )
1040            {
1041                modelCache.put( groupId, artifactId, version, tag.getName(), tag.intoCache( data ) );
1042            }
1043        }
1044    
1045        private <T> T getCache( ModelCache modelCache, String groupId, String artifactId, String version,
1046                                ModelCacheTag<T> tag )
1047        {
1048            if ( modelCache != null )
1049            {
1050                Object data = modelCache.get( groupId, artifactId, version, tag.getName() );
1051                if ( data != null )
1052                {
1053                    return tag.fromCache( tag.getType().cast( data ) );
1054                }
1055            }
1056            return null;
1057        }
1058    
1059        private void fireEvent( Model model, ModelBuildingRequest request, ModelProblemCollector problems,
1060                                ModelBuildingEventCatapult catapult )
1061            throws ModelBuildingException
1062        {
1063            ModelBuildingListener listener = request.getModelBuildingListener();
1064    
1065            if ( listener != null )
1066            {
1067                ModelBuildingEvent event = new DefaultModelBuildingEvent( model, request, problems );
1068    
1069                catapult.fire( listener, event );
1070            }
1071        }
1072    
1073        private boolean containsCoordinates( String message, String groupId, String artifactId, String version )
1074        {
1075            return message != null && ( groupId == null || message.contains( groupId ) )
1076                && ( artifactId == null || message.contains( artifactId ) )
1077                && ( version == null || message.contains( version ) );
1078        }
1079    
1080        protected boolean hasModelErrors( ModelProblemCollectorExt problems )
1081        {
1082            if ( problems instanceof DefaultModelProblemCollector )
1083            {
1084                return ( (DefaultModelProblemCollector) problems ).hasErrors();
1085            }
1086            else
1087            {
1088                // the default execution path only knows the DefaultModelProblemCollector,
1089                // only reason it's not in signature is because it's package private
1090                throw new IllegalStateException();
1091            }
1092        }
1093    
1094        protected boolean hasFatalErrors( ModelProblemCollectorExt problems )
1095        {
1096            if ( problems instanceof DefaultModelProblemCollector )
1097            {
1098                return ( (DefaultModelProblemCollector) problems ).hasFatalErrors();
1099            }
1100            else
1101            {
1102                // the default execution path only knows the DefaultModelProblemCollector,
1103                // only reason it's not in signature is because it's package private
1104                throw new IllegalStateException();
1105            }
1106        }
1107    
1108    }