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