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 replaceRepositories )
584    {
585        if ( modelResolver == null )
586        {
587            return;
588        }
589
590        problems.setSource( model );
591
592        List<Repository> repositories = model.getRepositories();
593
594        for ( Repository repository : repositories )
595        {
596            try
597            {
598                modelResolver.addRepository( repository, replaceRepositories );
599            }
600            catch ( InvalidRepositoryException e )
601            {
602                problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE )
603                        .setMessage( "Invalid repository " + repository.getId() + ": " + e.getMessage() )
604                        .setLocation( repository.getLocation( "" ) )
605                        .setException( e ) );
606            }
607        }
608    }
609
610    private void checkPluginVersions( List<ModelData> lineage, ModelBuildingRequest request,
611                                      ModelProblemCollector problems )
612    {
613        if ( request.getValidationLevel() < ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0 )
614        {
615            return;
616        }
617
618        Map<String, Plugin> plugins = new HashMap<String, Plugin>();
619        Map<String, String> versions = new HashMap<String, String>();
620        Map<String, String> managedVersions = new HashMap<String, String>();
621
622        for ( int i = lineage.size() - 1; i >= 0; i-- )
623        {
624            Model model = lineage.get( i ).getModel();
625            Build build = model.getBuild();
626            if ( build != null )
627            {
628                for ( Plugin plugin : build.getPlugins() )
629                {
630                    String key = plugin.getKey();
631                    if ( versions.get( key ) == null )
632                    {
633                        versions.put( key, plugin.getVersion() );
634                        plugins.put( key, plugin );
635                    }
636                }
637                PluginManagement mngt = build.getPluginManagement();
638                if ( mngt != null )
639                {
640                    for ( Plugin plugin : mngt.getPlugins() )
641                    {
642                        String key = plugin.getKey();
643                        if ( managedVersions.get( key ) == null )
644                        {
645                            managedVersions.put( key, plugin.getVersion() );
646                        }
647                    }
648                }
649            }
650        }
651
652        for ( String key : versions.keySet() )
653        {
654            if ( versions.get( key ) == null && managedVersions.get( key ) == null )
655            {
656                InputLocation location = plugins.get( key ).getLocation( "" );
657                problems.add( new ModelProblemCollectorRequest( Severity.WARNING, Version.V20 )
658                        .setMessage( "'build.plugins.plugin.version' for " + key + " is missing." )
659                        .setLocation( location ) );
660            }
661        }
662    }
663
664    private void assembleInheritance( List<ModelData> lineage, ModelBuildingRequest request,
665                                      ModelProblemCollector problems )
666    {
667        for ( int i = lineage.size() - 2; i >= 0; i-- )
668        {
669            Model parent = lineage.get( i + 1 ).getModel();
670            Model child = lineage.get( i ).getModel();
671            inheritanceAssembler.assembleModelInheritance( child, parent, request, problems );
672        }
673    }
674
675    private Map<String, Activation> getProfileActivations( Model model, boolean clone )
676    {
677        Map<String, Activation> activations = new HashMap<String, Activation>();
678        for ( Profile profile : model.getProfiles() )
679        {
680            Activation activation = profile.getActivation();
681
682            if ( activation == null )
683            {
684                continue;
685            }
686
687            if ( clone )
688            {
689                activation = activation.clone();
690            }
691
692            activations.put( profile.getId(), activation );
693        }
694
695        return activations;
696    }
697
698    private void injectProfileActivations( Model model, Map<String, Activation> activations )
699    {
700        for ( Profile profile : model.getProfiles() )
701        {
702            Activation activation = profile.getActivation();
703
704            if ( activation == null )
705            {
706                continue;
707            }
708
709            // restore activation
710            profile.setActivation( activations.get( profile.getId() ) );
711        }
712    }
713
714    private Model interpolateModel( Model model, ModelBuildingRequest request, ModelProblemCollector problems )
715    {
716        // save profile activations before interpolation, since they are evaluated with limited scope
717        Map<String, Activation> originalActivations = getProfileActivations( model, true );
718
719        Model result = modelInterpolator.interpolateModel( model, model.getProjectDirectory(), request, problems );
720        result.setPomFile( model.getPomFile() );
721
722        // restore profiles with file activation to their value before full interpolation
723        injectProfileActivations( model, originalActivations );
724
725        return result;
726    }
727
728    private ModelData readParent( Model childModel, ModelSource childSource, ModelBuildingRequest request,
729                                  DefaultModelProblemCollector problems )
730        throws ModelBuildingException
731    {
732        ModelData parentData;
733
734        Parent parent = childModel.getParent();
735
736        if ( parent != null )
737        {
738            String groupId = parent.getGroupId();
739            String artifactId = parent.getArtifactId();
740            String version = parent.getVersion();
741
742            parentData = getCache( request.getModelCache(), groupId, artifactId, version, ModelCacheTag.RAW );
743
744            if ( parentData == null )
745            {
746                parentData = readParentLocally( childModel, childSource, request, problems );
747
748                if ( parentData == null )
749                {
750                    parentData = readParentExternally( childModel, request, problems );
751                }
752
753                putCache( request.getModelCache(), groupId, artifactId, version, ModelCacheTag.RAW, parentData );
754            }
755            else
756            {
757                /*
758                 * NOTE: This is a sanity check of the cache hit. If the cached parent POM was locally resolved, the
759                 * child's <relativePath> should point at that parent, too. If it doesn't, we ignore the cache and
760                 * resolve externally, to mimic the behavior if the cache didn't exist in the first place. Otherwise,
761                 * the cache would obscure a bad POM.
762                 */
763
764                File pomFile = parentData.getModel().getPomFile();
765                if ( pomFile != null )
766                {
767                    ModelSource expectedParentSource = getParentPomFile( childModel, childSource );
768
769                    if ( expectedParentSource instanceof ModelSource2
770                        && !pomFile.toURI().equals( ( (ModelSource2) expectedParentSource ).getLocationURI() ) )
771                    {
772                        parentData = readParentExternally( childModel, request, problems );
773                    }
774                }
775            }
776
777            Model parentModel = parentData.getModel();
778
779            if ( !"pom".equals( parentModel.getPackaging() ) )
780            {
781                problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE )
782                        .setMessage( "Invalid packaging for parent POM " + ModelProblemUtils.toSourceHint( parentModel )
783                                     + ", must be \"pom\" but is \"" + parentModel.getPackaging() + "\"" )
784                        .setLocation( parentModel.getLocation( "packaging" ) ) );
785            }
786        }
787        else
788        {
789            parentData = null;
790        }
791
792        return parentData;
793    }
794
795    private ModelData readParentLocally( Model childModel, ModelSource childSource, ModelBuildingRequest request,
796                                         DefaultModelProblemCollector problems )
797        throws ModelBuildingException
798    {
799        ModelSource candidateSource = getParentPomFile( childModel, childSource );
800
801        if ( candidateSource == null )
802        {
803            return null;
804        }
805
806        File pomFile = null;
807        if ( candidateSource instanceof FileModelSource )
808        {
809            pomFile = ( (FileModelSource) candidateSource ).getPomFile();
810        }
811
812        Model candidateModel = readModel( candidateSource, pomFile, request, problems );
813
814        String groupId = candidateModel.getGroupId();
815        if ( groupId == null && candidateModel.getParent() != null )
816        {
817            groupId = candidateModel.getParent().getGroupId();
818        }
819        String artifactId = candidateModel.getArtifactId();
820        String version = candidateModel.getVersion();
821        if ( version == null && candidateModel.getParent() != null )
822        {
823            version = candidateModel.getParent().getVersion();
824        }
825
826        Parent parent = childModel.getParent();
827
828        if ( groupId == null || !groupId.equals( parent.getGroupId() ) || artifactId == null
829            || !artifactId.equals( parent.getArtifactId() ) )
830        {
831            StringBuilder buffer = new StringBuilder( 256 );
832            buffer.append( "'parent.relativePath'" );
833            if ( childModel != problems.getRootModel() )
834            {
835                buffer.append( " of POM " ).append( ModelProblemUtils.toSourceHint( childModel ) );
836            }
837            buffer.append( " points at " ).append( groupId ).append( ":" ).append( artifactId );
838            buffer.append( " instead of " ).append( parent.getGroupId() ).append( ":" ).append( parent.getArtifactId() );
839            buffer.append( ", please verify your project structure" );
840
841            problems.setSource( childModel );
842            problems.add( new ModelProblemCollectorRequest( Severity.WARNING, Version.BASE )
843                    .setMessage( buffer.toString() )
844                    .setLocation( parent.getLocation( "" ) ) );
845            return null;
846        }
847        if ( version == null || !version.equals( parent.getVersion() ) )
848        {
849            return null;
850        }
851
852        ModelData parentData = new ModelData( candidateSource, candidateModel, groupId, artifactId, version );
853
854        return parentData;
855    }
856
857    private ModelSource getParentPomFile( Model childModel, ModelSource source )
858    {
859        if ( !( source instanceof ModelSource2 ) )
860        {
861            return null;
862        }
863
864        String parentPath = childModel.getParent().getRelativePath();
865
866        if ( parentPath == null || parentPath.length() <= 0 )
867        {
868            return null;
869        }
870
871        return ( (ModelSource2) source ).getRelatedSource( parentPath );
872    }
873
874    private ModelData readParentExternally( Model childModel, ModelBuildingRequest request,
875                                            DefaultModelProblemCollector problems )
876        throws ModelBuildingException
877    {
878        problems.setSource( childModel );
879
880        Parent parent = childModel.getParent().clone();
881
882        String groupId = parent.getGroupId();
883        String artifactId = parent.getArtifactId();
884        String version = parent.getVersion();
885
886        ModelResolver modelResolver = request.getModelResolver();
887
888        if ( modelResolver == null )
889        {
890            throw new IllegalArgumentException( "no model resolver provided, cannot resolve parent POM "
891                + ModelProblemUtils.toId( groupId, artifactId, version ) + " for POM "
892                + ModelProblemUtils.toSourceHint( childModel ) );
893        }
894
895        ModelSource modelSource;
896        try
897        {
898            modelSource = modelResolver.resolveModel( parent );
899        }
900        catch ( UnresolvableModelException e )
901        {
902            StringBuilder buffer = new StringBuilder( 256 );
903            buffer.append( "Non-resolvable parent POM" );
904            if ( !containsCoordinates( e.getMessage(), groupId, artifactId, version ) )
905            {
906                buffer.append( " " ).append( ModelProblemUtils.toId( groupId, artifactId, version ) );
907            }
908            if ( childModel != problems.getRootModel() )
909            {
910                buffer.append( " for " ).append( ModelProblemUtils.toId( childModel ) );
911            }
912            buffer.append( ": " ).append( e.getMessage() );
913            if ( childModel.getProjectDirectory() != null )
914            {
915                if ( parent.getRelativePath() == null || parent.getRelativePath().length() <= 0 )
916                {
917                    buffer.append( " and 'parent.relativePath' points at no local POM" );
918                }
919                else
920                {
921                    buffer.append( " and 'parent.relativePath' points at wrong local POM" );
922                }
923            }
924
925            problems.add( new ModelProblemCollectorRequest( Severity.FATAL, Version.BASE )
926                    .setMessage( buffer.toString() )
927                    .setLocation( parent.getLocation( "" ) )
928                    .setException( e ) );
929            throw problems.newModelBuildingException();
930        }
931
932        ModelBuildingRequest lenientRequest = request;
933        if ( request.getValidationLevel() > ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0 )
934        {
935            lenientRequest = new FilterModelBuildingRequest( request )
936            {
937                @Override
938                public int getValidationLevel()
939                {
940                    return ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0;
941                }
942            };
943        }
944
945        Model parentModel = readModel( modelSource, null, lenientRequest, problems );
946
947        if ( !parent.getVersion().equals( version ) )
948        {
949            if ( childModel.getVersion() == null )
950            {
951                problems.add( new ModelProblemCollectorRequest( Severity.FATAL, Version.V31 ).
952                    setMessage( "Version must be a constant" ).
953                    setLocation( childModel.getLocation( "" ) ) );
954
955            }
956            else
957            {
958                if ( childModel.getVersion().indexOf( "${" ) > -1 )
959                {
960                    problems.add( new ModelProblemCollectorRequest( Severity.FATAL, Version.V31 ).
961                        setMessage( "Version must be a constant" ).
962                        setLocation( childModel.getLocation( "version" ) ) );
963
964                }
965            }
966
967            // MNG-2199: What else to check here ?
968        }
969
970        ModelData parentData = new ModelData( modelSource, parentModel, parent.getGroupId(), parent.getArtifactId(),
971                                              parent.getVersion() );
972
973        return parentData;
974    }
975
976    private Model getSuperModel()
977    {
978        return superPomProvider.getSuperModel( "4.0.0" ).clone();
979    }
980
981    private void importDependencyManagement( Model model, ModelBuildingRequest request,
982                                             DefaultModelProblemCollector problems, Collection<String> importIds )
983    {
984        DependencyManagement depMngt = model.getDependencyManagement();
985
986        if ( depMngt == null )
987        {
988            return;
989        }
990
991        String importing = model.getGroupId() + ':' + model.getArtifactId() + ':' + model.getVersion();
992
993        importIds.add( importing );
994
995        ModelResolver modelResolver = request.getModelResolver();
996
997        ModelBuildingRequest importRequest = null;
998
999        List<DependencyManagement> importMngts = null;
1000
1001        for ( Iterator<Dependency> it = depMngt.getDependencies().iterator(); it.hasNext(); )
1002        {
1003            Dependency dependency = it.next();
1004
1005            if ( !"pom".equals( dependency.getType() ) || !"import".equals( dependency.getScope() ) )
1006            {
1007                continue;
1008            }
1009
1010            it.remove();
1011
1012            String groupId = dependency.getGroupId();
1013            String artifactId = dependency.getArtifactId();
1014            String version = dependency.getVersion();
1015
1016            if ( groupId == null || groupId.length() <= 0 )
1017            {
1018                problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE )
1019                        .setMessage( "'dependencyManagement.dependencies.dependency.groupId' for "
1020                                        + dependency.getManagementKey() + " is missing." )
1021                        .setLocation( dependency.getLocation( "" ) ) );
1022                continue;
1023            }
1024            if ( artifactId == null || artifactId.length() <= 0 )
1025            {
1026                problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE )
1027                        .setMessage( "'dependencyManagement.dependencies.dependency.artifactId' for "
1028                                        + dependency.getManagementKey() + " is missing." )
1029                        .setLocation( dependency.getLocation( "" ) ) );
1030                continue;
1031            }
1032            if ( version == null || version.length() <= 0 )
1033            {
1034                problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE )
1035                        .setMessage( "'dependencyManagement.dependencies.dependency.version' for "
1036                                        + dependency.getManagementKey() + " is missing." )
1037                        .setLocation( dependency.getLocation( "" ) ) );
1038                continue;
1039            }
1040
1041            String imported = groupId + ':' + artifactId + ':' + version;
1042
1043            if ( importIds.contains( imported ) )
1044            {
1045                String message = "The dependencies of type=pom and with scope=import form a cycle: ";
1046                for ( String modelId : importIds )
1047                {
1048                    message += modelId + " -> ";
1049                }
1050                message += imported;
1051                problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE ).setMessage( message ) );
1052
1053                continue;
1054            }
1055
1056            DependencyManagement importMngt =
1057                getCache( request.getModelCache(), groupId, artifactId, version, ModelCacheTag.IMPORT );
1058
1059            if ( importMngt == null )
1060            {
1061                if ( modelResolver == null )
1062                {
1063                    throw new IllegalArgumentException( "no model resolver provided, cannot resolve import POM "
1064                        + ModelProblemUtils.toId( groupId, artifactId, version ) + " for POM "
1065                        + ModelProblemUtils.toSourceHint( model ) );
1066                }
1067
1068                ModelSource importSource;
1069                try
1070                {
1071                    importSource = modelResolver.resolveModel( groupId, artifactId, version );
1072                }
1073                catch ( UnresolvableModelException e )
1074                {
1075                    StringBuilder buffer = new StringBuilder( 256 );
1076                    buffer.append( "Non-resolvable import POM" );
1077                    if ( !containsCoordinates( e.getMessage(), groupId, artifactId, version ) )
1078                    {
1079                        buffer.append( " " ).append( ModelProblemUtils.toId( groupId, artifactId, version ) );
1080                    }
1081                    buffer.append( ": " ).append( e.getMessage() );
1082
1083                    problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE )
1084                            .setMessage( buffer.toString() )
1085                            .setLocation( dependency.getLocation( "" ) )
1086                            .setException( e ) );
1087                    continue;
1088                }
1089
1090                if ( importRequest == null )
1091                {
1092                    importRequest = new DefaultModelBuildingRequest();
1093                    importRequest.setValidationLevel( ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL );
1094                    importRequest.setModelCache( request.getModelCache() );
1095                    importRequest.setSystemProperties( request.getSystemProperties() );
1096                    importRequest.setUserProperties( request.getUserProperties() );
1097                    importRequest.setLocationTracking( request.isLocationTracking() );
1098                }
1099
1100                importRequest.setModelSource( importSource );
1101                importRequest.setModelResolver( modelResolver.newCopy() );
1102
1103                ModelBuildingResult importResult;
1104                try
1105                {
1106                    importResult = build( importRequest );
1107                }
1108                catch ( ModelBuildingException e )
1109                {
1110                    problems.addAll( e.getProblems() );
1111                    continue;
1112                }
1113
1114                problems.addAll( importResult.getProblems() );
1115
1116                Model importModel = importResult.getEffectiveModel();
1117
1118                importMngt = importModel.getDependencyManagement();
1119
1120                if ( importMngt == null )
1121                {
1122                    importMngt = new DependencyManagement();
1123                }
1124
1125                putCache( request.getModelCache(), groupId, artifactId, version, ModelCacheTag.IMPORT, importMngt );
1126            }
1127
1128            if ( importMngts == null )
1129            {
1130                importMngts = new ArrayList<DependencyManagement>();
1131            }
1132
1133            importMngts.add( importMngt );
1134        }
1135
1136        importIds.remove( importing );
1137
1138        dependencyManagementImporter.importManagement( model, importMngts, request, problems );
1139    }
1140
1141    private <T> void putCache( ModelCache modelCache, String groupId, String artifactId, String version,
1142                               ModelCacheTag<T> tag, T data )
1143    {
1144        if ( modelCache != null )
1145        {
1146            modelCache.put( groupId, artifactId, version, tag.getName(), tag.intoCache( data ) );
1147        }
1148    }
1149
1150    private <T> T getCache( ModelCache modelCache, String groupId, String artifactId, String version,
1151                            ModelCacheTag<T> tag )
1152    {
1153        if ( modelCache != null )
1154        {
1155            Object data = modelCache.get( groupId, artifactId, version, tag.getName() );
1156            if ( data != null )
1157            {
1158                return tag.fromCache( tag.getType().cast( data ) );
1159            }
1160        }
1161        return null;
1162    }
1163
1164    private void fireEvent( Model model, ModelBuildingRequest request, ModelProblemCollector problems,
1165                            ModelBuildingEventCatapult catapult )
1166        throws ModelBuildingException
1167    {
1168        ModelBuildingListener listener = request.getModelBuildingListener();
1169
1170        if ( listener != null )
1171        {
1172            ModelBuildingEvent event = new DefaultModelBuildingEvent( model, request, problems );
1173
1174            catapult.fire( listener, event );
1175        }
1176    }
1177
1178    private boolean containsCoordinates( String message, String groupId, String artifactId, String version )
1179    {
1180        return message != null && ( groupId == null || message.contains( groupId ) )
1181            && ( artifactId == null || message.contains( artifactId ) )
1182            && ( version == null || message.contains( version ) );
1183    }
1184
1185    protected boolean hasModelErrors( ModelProblemCollectorExt problems )
1186    {
1187        if ( problems instanceof DefaultModelProblemCollector )
1188        {
1189            return ( (DefaultModelProblemCollector) problems ).hasErrors();
1190        }
1191        else
1192        {
1193            // the default execution path only knows the DefaultModelProblemCollector,
1194            // only reason it's not in signature is because it's package private
1195            throw new IllegalStateException();
1196        }
1197    }
1198
1199    protected boolean hasFatalErrors( ModelProblemCollectorExt problems )
1200    {
1201        if ( problems instanceof DefaultModelProblemCollector )
1202        {
1203            return ( (DefaultModelProblemCollector) problems ).hasFatalErrors();
1204        }
1205        else
1206        {
1207            // the default execution path only knows the DefaultModelProblemCollector,
1208            // only reason it's not in signature is because it's package private
1209            throw new IllegalStateException();
1210        }
1211    }
1212
1213}