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