001package 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
022import java.io.File;
023import java.io.IOException;
024import java.util.ArrayList;
025import java.util.Collection;
026import java.util.Iterator;
027import java.util.LinkedHashSet;
028import java.util.List;
029import java.util.Map;
030import java.util.HashMap;
031import java.util.Properties;
032
033import org.apache.maven.model.Build;
034import org.apache.maven.model.Dependency;
035import org.apache.maven.model.DependencyManagement;
036import org.apache.maven.model.InputLocation;
037import org.apache.maven.model.InputSource;
038import org.apache.maven.model.Model;
039import org.apache.maven.model.Parent;
040import org.apache.maven.model.Plugin;
041import org.apache.maven.model.PluginManagement;
042import org.apache.maven.model.Profile;
043import org.apache.maven.model.Repository;
044import org.apache.maven.model.building.ModelProblem.Severity;
045import org.apache.maven.model.building.ModelProblem.Version;
046import org.apache.maven.model.composition.DependencyManagementImporter;
047import org.apache.maven.model.inheritance.InheritanceAssembler;
048import org.apache.maven.model.interpolation.ModelInterpolator;
049import org.apache.maven.model.io.ModelParseException;
050import org.apache.maven.model.management.DependencyManagementInjector;
051import org.apache.maven.model.management.PluginManagementInjector;
052import org.apache.maven.model.normalization.ModelNormalizer;
053import org.apache.maven.model.path.ModelPathTranslator;
054import org.apache.maven.model.path.ModelUrlNormalizer;
055import org.apache.maven.model.plugin.LifecycleBindingsInjector;
056import org.apache.maven.model.plugin.PluginConfigurationExpander;
057import org.apache.maven.model.plugin.ReportConfigurationExpander;
058import org.apache.maven.model.plugin.ReportingConverter;
059import org.apache.maven.model.profile.DefaultProfileActivationContext;
060import org.apache.maven.model.profile.ProfileInjector;
061import org.apache.maven.model.profile.ProfileSelector;
062import org.apache.maven.model.resolution.InvalidRepositoryException;
063import org.apache.maven.model.resolution.ModelResolver;
064import org.apache.maven.model.resolution.UnresolvableModelException;
065import org.apache.maven.model.superpom.SuperPomProvider;
066import org.apache.maven.model.validation.ModelValidator;
067import org.codehaus.plexus.component.annotations.Component;
068import org.codehaus.plexus.component.annotations.Requirement;
069
070/**
071 * @author Benjamin Bentmann
072 */
073@Component( role = ModelBuilder.class )
074public 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}