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