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