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