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