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