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.internal.impl.model;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.lang.reflect.Field;
24  import java.nio.file.Files;
25  import java.nio.file.Path;
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.HashMap;
29  import java.util.Iterator;
30  import java.util.LinkedHashSet;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Objects;
34  import java.util.Properties;
35  import java.util.concurrent.Callable;
36  import java.util.concurrent.atomic.AtomicReference;
37  import java.util.function.BiConsumer;
38  import java.util.function.Supplier;
39  import java.util.function.UnaryOperator;
40  import java.util.stream.Collectors;
41  import java.util.stream.Stream;
42  
43  import org.apache.maven.api.Session;
44  import org.apache.maven.api.Type;
45  import org.apache.maven.api.VersionRange;
46  import org.apache.maven.api.annotations.Nullable;
47  import org.apache.maven.api.di.Inject;
48  import org.apache.maven.api.di.Named;
49  import org.apache.maven.api.di.Singleton;
50  import org.apache.maven.api.feature.Features;
51  import org.apache.maven.api.model.Activation;
52  import org.apache.maven.api.model.ActivationFile;
53  import org.apache.maven.api.model.Build;
54  import org.apache.maven.api.model.Dependency;
55  import org.apache.maven.api.model.DependencyManagement;
56  import org.apache.maven.api.model.Exclusion;
57  import org.apache.maven.api.model.InputLocation;
58  import org.apache.maven.api.model.InputLocationTracker;
59  import org.apache.maven.api.model.InputSource;
60  import org.apache.maven.api.model.Model;
61  import org.apache.maven.api.model.Parent;
62  import org.apache.maven.api.model.Plugin;
63  import org.apache.maven.api.model.PluginManagement;
64  import org.apache.maven.api.model.Profile;
65  import org.apache.maven.api.services.BuilderProblem.Severity;
66  import org.apache.maven.api.services.ModelBuilder;
67  import org.apache.maven.api.services.ModelBuilderException;
68  import org.apache.maven.api.services.ModelBuilderRequest;
69  import org.apache.maven.api.services.ModelBuilderResult;
70  import org.apache.maven.api.services.ModelCache;
71  import org.apache.maven.api.services.ModelProblem;
72  import org.apache.maven.api.services.ModelProblemCollector;
73  import org.apache.maven.api.services.ModelResolver;
74  import org.apache.maven.api.services.ModelResolverException;
75  import org.apache.maven.api.services.ModelSource;
76  import org.apache.maven.api.services.ModelTransformer;
77  import org.apache.maven.api.services.ModelTransformerContext;
78  import org.apache.maven.api.services.ModelTransformerContextBuilder;
79  import org.apache.maven.api.services.ModelTransformerException;
80  import org.apache.maven.api.services.Source;
81  import org.apache.maven.api.services.SuperPomProvider;
82  import org.apache.maven.api.services.VersionParserException;
83  import org.apache.maven.api.services.model.DependencyManagementImporter;
84  import org.apache.maven.api.services.model.DependencyManagementInjector;
85  import org.apache.maven.api.services.model.InheritanceAssembler;
86  import org.apache.maven.api.services.model.LifecycleBindingsInjector;
87  import org.apache.maven.api.services.model.ModelBuildingEvent;
88  import org.apache.maven.api.services.model.ModelBuildingListener;
89  import org.apache.maven.api.services.model.ModelInterpolator;
90  import org.apache.maven.api.services.model.ModelNormalizer;
91  import org.apache.maven.api.services.model.ModelPathTranslator;
92  import org.apache.maven.api.services.model.ModelProcessor;
93  import org.apache.maven.api.services.model.ModelUrlNormalizer;
94  import org.apache.maven.api.services.model.ModelValidator;
95  import org.apache.maven.api.services.model.ModelVersionParser;
96  import org.apache.maven.api.services.model.PluginConfigurationExpander;
97  import org.apache.maven.api.services.model.PluginManagementInjector;
98  import org.apache.maven.api.services.model.ProfileActivationContext;
99  import org.apache.maven.api.services.model.ProfileInjector;
100 import org.apache.maven.api.services.model.ProfileSelector;
101 import org.apache.maven.api.services.model.WorkspaceModelResolver;
102 import org.apache.maven.api.services.xml.XmlReaderException;
103 import org.apache.maven.api.services.xml.XmlReaderRequest;
104 import org.apache.maven.internal.impl.resolver.DefaultModelCache;
105 import org.apache.maven.internal.impl.resolver.DefaultModelRepositoryHolder;
106 import org.apache.maven.internal.impl.resolver.DefaultModelResolver;
107 import org.apache.maven.model.v4.MavenTransformer;
108 import org.codehaus.plexus.interpolation.InterpolationException;
109 import org.codehaus.plexus.interpolation.Interpolator;
110 import org.codehaus.plexus.interpolation.MapBasedValueSource;
111 import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
112 import org.codehaus.plexus.interpolation.StringSearchInterpolator;
113 import org.slf4j.Logger;
114 import org.slf4j.LoggerFactory;
115 
116 /**
117  */
118 @Named
119 @Singleton
120 public class DefaultModelBuilder implements ModelBuilder {
121 
122     public static final String NAMESPACE_PREFIX = "http://maven.apache.org/POM/";
123     private static final String RAW = "raw";
124     private static final String FILE = "file";
125     private static final String IMPORT = "import";
126 
127     private final Logger logger = LoggerFactory.getLogger(getClass());
128 
129     private final ModelProcessor modelProcessor;
130     private final ModelValidator modelValidator;
131     private final ModelNormalizer modelNormalizer;
132     private final ModelInterpolator modelInterpolator;
133     private final ModelPathTranslator modelPathTranslator;
134     private final ModelUrlNormalizer modelUrlNormalizer;
135     private final SuperPomProvider superPomProvider;
136     private final InheritanceAssembler inheritanceAssembler;
137     private final ProfileSelector profileSelector;
138     private final ProfileInjector profileInjector;
139     private final PluginManagementInjector pluginManagementInjector;
140     private final DependencyManagementInjector dependencyManagementInjector;
141     private final DependencyManagementImporter dependencyManagementImporter;
142     private final LifecycleBindingsInjector lifecycleBindingsInjector;
143     private final PluginConfigurationExpander pluginConfigurationExpander;
144     private final ProfileActivationFilePathInterpolator profileActivationFilePathInterpolator;
145     private final ModelTransformer transformer;
146     private final ModelVersionParser versionParser;
147 
148     @SuppressWarnings("checkstyle:ParameterNumber")
149     @Inject
150     public DefaultModelBuilder(
151             ModelProcessor modelProcessor,
152             ModelValidator modelValidator,
153             ModelNormalizer modelNormalizer,
154             ModelInterpolator modelInterpolator,
155             ModelPathTranslator modelPathTranslator,
156             ModelUrlNormalizer modelUrlNormalizer,
157             SuperPomProvider superPomProvider,
158             InheritanceAssembler inheritanceAssembler,
159             ProfileSelector profileSelector,
160             ProfileInjector profileInjector,
161             PluginManagementInjector pluginManagementInjector,
162             DependencyManagementInjector dependencyManagementInjector,
163             DependencyManagementImporter dependencyManagementImporter,
164             @Nullable LifecycleBindingsInjector lifecycleBindingsInjector,
165             PluginConfigurationExpander pluginConfigurationExpander,
166             ProfileActivationFilePathInterpolator profileActivationFilePathInterpolator,
167             ModelTransformer transformer,
168             ModelVersionParser versionParser) {
169         this.modelProcessor = modelProcessor;
170         this.modelValidator = modelValidator;
171         this.modelNormalizer = modelNormalizer;
172         this.modelInterpolator = modelInterpolator;
173         this.modelPathTranslator = modelPathTranslator;
174         this.modelUrlNormalizer = modelUrlNormalizer;
175         this.superPomProvider = superPomProvider;
176         this.inheritanceAssembler = inheritanceAssembler;
177         this.profileSelector = profileSelector;
178         this.profileInjector = profileInjector;
179         this.pluginManagementInjector = pluginManagementInjector;
180         this.dependencyManagementInjector = dependencyManagementInjector;
181         this.dependencyManagementImporter = dependencyManagementImporter;
182         this.lifecycleBindingsInjector = lifecycleBindingsInjector;
183         this.pluginConfigurationExpander = pluginConfigurationExpander;
184         this.profileActivationFilePathInterpolator = profileActivationFilePathInterpolator;
185         this.transformer = transformer;
186         this.versionParser = versionParser;
187     }
188 
189     @Override
190     public ModelTransformerContextBuilder newTransformerContextBuilder() {
191         return new DefaultModelTransformerContextBuilder(this);
192     }
193 
194     @Override
195     public ModelBuilderResult build(ModelBuilderRequest request) throws ModelBuilderException {
196         request = fillRequestDefaults(request);
197         if (request.getInterimResult() != null) {
198             return build(request, request.getInterimResult(), new LinkedHashSet<>());
199         } else {
200             return build(request, new LinkedHashSet<>());
201         }
202     }
203 
204     private static ModelBuilderRequest fillRequestDefaults(ModelBuilderRequest request) {
205         ModelBuilderRequest.ModelBuilderRequestBuilder builder = ModelBuilderRequest.builder(request);
206         if (request.getModelCache() == null) {
207             builder.modelCache(new DefaultModelCache());
208         }
209         if (request.getModelRepositoryHolder() == null) {
210             builder.modelRepositoryHolder(new DefaultModelRepositoryHolder(
211                     request.getSession(),
212                     DefaultModelRepositoryHolder.RepositoryMerging.POM_DOMINANT,
213                     request.getSession().getRemoteRepositories()));
214         }
215         if (request.getModelResolver() == null) {
216             builder.modelResolver(new DefaultModelResolver());
217         }
218         return builder.build();
219     }
220 
221     protected ModelBuilderResult build(ModelBuilderRequest request, Collection<String> importIds)
222             throws ModelBuilderException {
223         // phase 1
224         DefaultModelBuilderResult result = new DefaultModelBuilderResult();
225 
226         DefaultModelProblemCollector problems = new DefaultModelProblemCollector(result);
227 
228         // read and validate raw model
229         Model fileModel = readFileModel(request, problems);
230         result.setFileModel(fileModel);
231 
232         Model activatedFileModel = activateFileModel(fileModel, request, result, problems);
233         result.setActivatedFileModel(activatedFileModel);
234 
235         if (!request.isTwoPhaseBuilding()) {
236             return build(request, result, importIds);
237         } else if (hasModelErrors(problems)) {
238             throw problems.newModelBuilderException();
239         }
240 
241         return result;
242     }
243 
244     private Model activateFileModel(
245             Model inputModel,
246             ModelBuilderRequest request,
247             DefaultModelBuilderResult result,
248             DefaultModelProblemCollector problems)
249             throws ModelBuilderException {
250         problems.setRootModel(inputModel);
251 
252         // profile activation
253         DefaultProfileActivationContext profileActivationContext = getProfileActivationContext(request, inputModel);
254 
255         problems.setSource("(external profiles)");
256         List<Profile> activeExternalProfiles =
257                 profileSelector.getActiveProfiles(request.getProfiles(), profileActivationContext, problems);
258 
259         result.setActiveExternalProfiles(activeExternalProfiles);
260 
261         if (!activeExternalProfiles.isEmpty()) {
262             Properties profileProps = new Properties();
263             for (Profile profile : activeExternalProfiles) {
264                 profileProps.putAll(profile.getProperties());
265             }
266             profileProps.putAll(profileActivationContext.getUserProperties());
267             profileActivationContext.setUserProperties(profileProps);
268         }
269 
270         profileActivationContext.setProjectProperties(inputModel.getProperties());
271         problems.setSource(inputModel);
272         List<Profile> activePomProfiles =
273                 profileSelector.getActiveProfiles(inputModel.getProfiles(), profileActivationContext, problems);
274 
275         // model normalization
276         problems.setSource(inputModel);
277         inputModel = modelNormalizer.mergeDuplicates(inputModel, request, problems);
278 
279         Map<String, Activation> interpolatedActivations = getProfileActivations(inputModel);
280         inputModel = injectProfileActivations(inputModel, interpolatedActivations);
281 
282         // profile injection
283         inputModel = profileInjector.injectProfiles(inputModel, activePomProfiles, request, problems);
284         inputModel = profileInjector.injectProfiles(inputModel, activeExternalProfiles, request, problems);
285 
286         return inputModel;
287     }
288 
289     @SuppressWarnings("checkstyle:methodlength")
290     private Model readEffectiveModel(
291             final ModelBuilderRequest request,
292             final DefaultModelBuilderResult result,
293             DefaultModelProblemCollector problems)
294             throws ModelBuilderException {
295         Model inputModel = readRawModel(request, problems);
296         if (problems.hasFatalErrors()) {
297             throw problems.newModelBuilderException();
298         }
299 
300         inputModel = activateFileModel(inputModel, request, result, problems);
301 
302         problems.setRootModel(inputModel);
303 
304         ModelData resultData = new ModelData(request.getSource(), inputModel);
305         String superModelVersion = inputModel.getModelVersion() != null ? inputModel.getModelVersion() : "4.0.0";
306         if (!VALID_MODEL_VERSIONS.contains(superModelVersion)) {
307             // Maven 3.x is always using 4.0.0 version to load the supermodel, so
308             // do the same when loading a dependency.  The model validator will also
309             // check that field later.
310             superModelVersion = MODEL_VERSION_4_0_0;
311         }
312         ModelData superData = new ModelData(null, getSuperModel(superModelVersion));
313 
314         // profile activation
315         DefaultProfileActivationContext profileActivationContext = getProfileActivationContext(request, inputModel);
316 
317         List<Profile> activeExternalProfiles = result.getActiveExternalProfiles();
318 
319         if (!activeExternalProfiles.isEmpty()) {
320             Properties profileProps = new Properties();
321             for (Profile profile : activeExternalProfiles) {
322                 profileProps.putAll(profile.getProperties());
323             }
324             profileProps.putAll(profileActivationContext.getUserProperties());
325             profileActivationContext.setUserProperties(profileProps);
326         }
327 
328         Collection<String> parentIds = new LinkedHashSet<>();
329 
330         List<Model> lineage = new ArrayList<>();
331 
332         for (ModelData currentData = resultData; ; ) {
333             String modelId = currentData.id();
334             result.addModelId(modelId);
335 
336             Model model = currentData.model();
337             result.setRawModel(modelId, model);
338             problems.setSource(model);
339 
340             // model normalization
341             model = modelNormalizer.mergeDuplicates(model, request, problems);
342 
343             // profile activation
344             profileActivationContext.setProjectProperties(model.getProperties());
345 
346             List<Profile> interpolatedProfiles =
347                     interpolateActivations(model.getProfiles(), profileActivationContext, problems);
348 
349             // profile injection
350             List<Profile> activePomProfiles =
351                     profileSelector.getActiveProfiles(interpolatedProfiles, profileActivationContext, problems);
352             result.setActivePomProfiles(modelId, activePomProfiles);
353             model = profileInjector.injectProfiles(model, activePomProfiles, request, problems);
354             if (currentData == resultData) {
355                 model = profileInjector.injectProfiles(model, activeExternalProfiles, request, problems);
356             }
357 
358             lineage.add(model);
359 
360             if (currentData == superData) {
361                 break;
362             }
363 
364             // add repositories specified by the current model so that we can resolve the parent
365             if (!model.getRepositories().isEmpty()) {
366                 List<String> oldRepos = request.getModelRepositoryHolder().getRepositories().stream()
367                         .map(Object::toString)
368                         .toList();
369                 request.getModelRepositoryHolder().merge(model.getRepositories(), false);
370                 List<String> newRepos = request.getModelRepositoryHolder().getRepositories().stream()
371                         .map(Object::toString)
372                         .toList();
373                 if (!Objects.equals(oldRepos, newRepos)) {
374                     logger.debug("Merging repositories from " + model.getId() + "\n"
375                             + newRepos.stream().map(s -> "    " + s).collect(Collectors.joining("\n")));
376                 }
377             }
378 
379             // we pass a cloned model, so that resolving the parent version does not affect the returned model
380             ModelData parentData = readParent(model, currentData.source(), request, problems);
381 
382             if (parentData == null) {
383                 currentData = superData;
384             } else if (!parentIds.add(parentData.id())) {
385                 StringBuilder message = new StringBuilder("The parents form a cycle: ");
386                 for (String parentId : parentIds) {
387                     message.append(parentId).append(" -> ");
388                 }
389                 message.append(parentData.id());
390 
391                 problems.add(Severity.FATAL, ModelProblem.Version.BASE, message.toString());
392 
393                 throw problems.newModelBuilderException();
394             } else {
395                 currentData = parentData;
396             }
397         }
398 
399         Model tmpModel = lineage.get(0);
400 
401         // inject interpolated activations
402         List<Profile> interpolated = interpolateActivations(tmpModel.getProfiles(), profileActivationContext, problems);
403         if (interpolated != tmpModel.getProfiles()) {
404             tmpModel = tmpModel.withProfiles(interpolated);
405         }
406 
407         // inject external profile into current model
408         tmpModel = profileInjector.injectProfiles(tmpModel, activeExternalProfiles, request, problems);
409 
410         lineage.set(0, tmpModel);
411 
412         checkPluginVersions(lineage, request, problems);
413 
414         // inheritance assembly
415         Model resultModel = assembleInheritance(lineage, request, problems);
416 
417         // consider caching inherited model
418 
419         problems.setSource(resultModel);
420         problems.setRootModel(resultModel);
421 
422         // model interpolation
423         resultModel = interpolateModel(resultModel, request, problems);
424 
425         // url normalization
426         resultModel = modelUrlNormalizer.normalize(resultModel, request);
427 
428         result.setEffectiveModel(resultModel);
429 
430         // Now the fully interpolated model is available: reconfigure the resolver
431         if (!resultModel.getRepositories().isEmpty()) {
432             List<String> oldRepos = request.getModelRepositoryHolder().getRepositories().stream()
433                     .map(Object::toString)
434                     .toList();
435             request.getModelRepositoryHolder().merge(resultModel.getRepositories(), true);
436             List<String> newRepos = request.getModelRepositoryHolder().getRepositories().stream()
437                     .map(Object::toString)
438                     .toList();
439             if (!Objects.equals(oldRepos, newRepos)) {
440                 logger.debug("Replacing repositories from " + resultModel.getId() + "\n"
441                         + newRepos.stream().map(s -> "    " + s).collect(Collectors.joining("\n")));
442             }
443         }
444 
445         return resultModel;
446     }
447 
448     private List<Profile> interpolateActivations(
449             List<Profile> profiles, DefaultProfileActivationContext context, DefaultModelProblemCollector problems) {
450         if (profiles.stream()
451                 .map(org.apache.maven.api.model.Profile::getActivation)
452                 .noneMatch(Objects::nonNull)) {
453             return profiles;
454         }
455         final Interpolator xform = new RegexBasedInterpolator();
456         xform.setCacheAnswers(true);
457         Stream.of(context.getUserProperties(), context.getSystemProperties())
458                 .map(MapBasedValueSource::new)
459                 .forEach(xform::addValueSource);
460 
461         class ProfileInterpolator extends MavenTransformer implements UnaryOperator<Profile> {
462             ProfileInterpolator() {
463                 super(s -> {
464                     if (isNotEmpty(s)) {
465                         try {
466                             return xform.interpolate(s);
467                         } catch (InterpolationException e) {
468                             problems.add(Severity.ERROR, ModelProblem.Version.BASE, e.getMessage(), e);
469                         }
470                     }
471                     return s;
472                 });
473             }
474 
475             @Override
476             public Profile apply(Profile p) {
477                 return Profile.newBuilder(p)
478                         .activation(transformActivation(p.getActivation()))
479                         .build();
480             }
481 
482             @Override
483             protected ActivationFile.Builder transformActivationFile_Missing(
484                     Supplier<? extends ActivationFile.Builder> creator,
485                     ActivationFile.Builder builder,
486                     ActivationFile target) {
487                 String path = target.getMissing();
488                 String xformed = transformPath(path, target, "missing");
489                 return xformed != path ? (builder != null ? builder : creator.get()).missing(xformed) : builder;
490             }
491 
492             @Override
493             protected ActivationFile.Builder transformActivationFile_Exists(
494                     Supplier<? extends ActivationFile.Builder> creator,
495                     ActivationFile.Builder builder,
496                     ActivationFile target) {
497                 final String path = target.getExists();
498                 final String xformed = transformPath(path, target, "exists");
499                 return xformed != path ? (builder != null ? builder : creator.get()).exists(xformed) : builder;
500             }
501 
502             private String transformPath(String path, ActivationFile target, String locationKey) {
503                 if (isNotEmpty(path)) {
504                     try {
505                         return profileActivationFilePathInterpolator.interpolate(path, context);
506                     } catch (InterpolationException e) {
507                         addInterpolationProblem(problems, target, path, e, locationKey);
508                     }
509                 }
510                 return path;
511             }
512         }
513         return profiles.stream().map(new ProfileInterpolator()).toList();
514     }
515 
516     private static void addInterpolationProblem(
517             DefaultModelProblemCollector problems,
518             InputLocationTracker target,
519             String path,
520             InterpolationException e,
521             String locationKey) {
522         problems.add(
523                 Severity.ERROR,
524                 ModelProblem.Version.BASE,
525                 "Failed to interpolate file location " + path + ": " + e.getMessage(),
526                 target.getLocation(locationKey),
527                 e);
528     }
529 
530     private static boolean isNotEmpty(String string) {
531         return string != null && !string.isEmpty();
532     }
533 
534     public ModelBuilderResult build(final ModelBuilderRequest request, final ModelBuilderResult result)
535             throws ModelBuilderException {
536         return build(request, result, new LinkedHashSet<>());
537     }
538 
539     public Model buildRawModel(ModelBuilderRequest request) throws ModelBuilderException {
540         request = fillRequestDefaults(request);
541         DefaultModelProblemCollector problems = new DefaultModelProblemCollector(new DefaultModelBuilderResult());
542         Model model = readRawModel(request, problems);
543         if (hasModelErrors(problems)) {
544             throw problems.newModelBuilderException();
545         }
546         return model;
547     }
548 
549     private ModelBuilderResult build(
550             ModelBuilderRequest request, final ModelBuilderResult phaseOneResult, Collection<String> importIds)
551             throws ModelBuilderException {
552         DefaultModelBuilderResult result = asDefaultModelBuilderResult(phaseOneResult);
553 
554         DefaultModelProblemCollector problems = new DefaultModelProblemCollector(result);
555 
556         // phase 2
557         Model resultModel = readEffectiveModel(request, result, problems);
558         problems.setSource(resultModel);
559         problems.setRootModel(resultModel);
560 
561         // model path translation
562         resultModel = modelPathTranslator.alignToBaseDirectory(resultModel, resultModel.getProjectDirectory(), request);
563 
564         // plugin management injection
565         resultModel = pluginManagementInjector.injectManagement(resultModel, request, problems);
566 
567         resultModel = fireEvent(resultModel, request, problems, ModelBuildingListener::buildExtensionsAssembled);
568 
569         if (request.isProcessPlugins()) {
570             if (lifecycleBindingsInjector == null) {
571                 throw new IllegalStateException("lifecycle bindings injector is missing");
572             }
573 
574             // lifecycle bindings injection
575             resultModel = lifecycleBindingsInjector.injectLifecycleBindings(resultModel, request, problems);
576         }
577 
578         // dependency management import
579         resultModel = importDependencyManagement(resultModel, request, problems, importIds);
580 
581         // dependency management injection
582         resultModel = dependencyManagementInjector.injectManagement(resultModel, request, problems);
583 
584         resultModel = modelNormalizer.injectDefaultValues(resultModel, request, problems);
585 
586         if (request.isProcessPlugins()) {
587             // plugins configuration
588             resultModel = pluginConfigurationExpander.expandPluginConfiguration(resultModel, request, problems);
589         }
590 
591         result.setEffectiveModel(resultModel);
592 
593         // effective model validation
594         modelValidator.validateEffectiveModel(resultModel, request, problems);
595 
596         if (hasModelErrors(problems)) {
597             throw problems.newModelBuilderException();
598         }
599 
600         return result;
601     }
602 
603     private DefaultModelBuilderResult asDefaultModelBuilderResult(ModelBuilderResult phaseOneResult) {
604         if (phaseOneResult instanceof DefaultModelBuilderResult) {
605             return (DefaultModelBuilderResult) phaseOneResult;
606         } else {
607             return new DefaultModelBuilderResult(phaseOneResult);
608         }
609     }
610 
611     public Result<? extends Model> buildRawModel(Path pomFile, int validationLevel, boolean locationTracking) {
612         return buildRawModel(pomFile, validationLevel, locationTracking, null);
613     }
614 
615     public Result<? extends Model> buildRawModel(
616             Path pomFile, int validationLevel, boolean locationTracking, ModelTransformerContext context) {
617         final ModelBuilderRequest request = ModelBuilderRequest.builder()
618                 .validationLevel(validationLevel)
619                 .locationTracking(locationTracking)
620                 .source(ModelSource.fromPath(pomFile))
621                 .build();
622         DefaultModelProblemCollector problems = new DefaultModelProblemCollector(new DefaultModelBuilderResult());
623         try {
624             Model model = readFileModel(request, problems);
625 
626             try {
627                 if (transformer != null && context != null) {
628                     transformer.transform(context, model, pomFile);
629                 }
630             } catch (ModelBuilderException e) {
631                 problems.add(Severity.FATAL, ModelProblem.Version.V40, null, e);
632             }
633 
634             return Result.newResult(model, problems.getProblems());
635         } catch (ModelBuilderException e) {
636             return Result.error(problems.getProblems());
637         }
638     }
639 
640     Model readFileModel(ModelBuilderRequest request, DefaultModelProblemCollector problems)
641             throws ModelBuilderException {
642         ModelSource modelSource = request.getSource();
643         Model model =
644                 cache(getModelCache(request), modelSource, FILE, () -> doReadFileModel(modelSource, request, problems));
645 
646         if (modelSource.getPath() != null) {
647             if (getTransformerContextBuilder(request) instanceof DefaultModelTransformerContextBuilder contextBuilder) {
648                 contextBuilder.putSource(getGroupId(model), model.getArtifactId(), modelSource);
649             }
650         }
651 
652         return model;
653     }
654 
655     @SuppressWarnings("checkstyle:methodlength")
656     private Model doReadFileModel(
657             ModelSource modelSource, ModelBuilderRequest request, DefaultModelProblemCollector problems)
658             throws ModelBuilderException {
659         Model model;
660         problems.setSource(modelSource.getLocation());
661         try {
662             boolean strict = request.getValidationLevel() >= ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_2_0;
663 
664             Path rootDirectory;
665             try {
666                 rootDirectory = request.getSession().getRootDirectory();
667             } catch (IllegalStateException ignore) {
668                 rootDirectory = modelSource.getPath();
669             }
670             try (InputStream is = modelSource.openStream()) {
671                 model = modelProcessor.read(XmlReaderRequest.builder()
672                         .strict(strict)
673                         .location(modelSource.getLocation())
674                         .path(modelSource.getPath())
675                         .rootDirectory(rootDirectory)
676                         .inputStream(is)
677                         .build());
678             } catch (XmlReaderException e) {
679                 if (!strict) {
680                     throw e;
681                 }
682                 try (InputStream is = modelSource.openStream()) {
683                     model = modelProcessor.read(XmlReaderRequest.builder()
684                             .strict(false)
685                             .location(modelSource.getLocation())
686                             .path(modelSource.getPath())
687                             .rootDirectory(rootDirectory)
688                             .inputStream(is)
689                             .build());
690                 } catch (XmlReaderException ne) {
691                     // still unreadable even in non-strict mode, rethrow original error
692                     throw e;
693                 }
694 
695                 Severity severity = request.isProjectBuild() ? Severity.ERROR : Severity.WARNING;
696                 problems.add(
697                         severity,
698                         ModelProblem.Version.V20,
699                         "Malformed POM " + modelSource.getLocation() + ": " + e.getMessage(),
700                         e);
701             }
702 
703             InputLocation loc = model.getLocation("");
704             InputSource v4src = loc != null ? loc.getSource() : null;
705             if (v4src != null) {
706                 try {
707                     Field field = InputSource.class.getDeclaredField("modelId");
708                     field.setAccessible(true);
709                     field.set(v4src, ModelProblemUtils.toId(model));
710                 } catch (Throwable t) {
711                     // TODO: use a lazy source ?
712                     throw new IllegalStateException("Unable to set modelId on InputSource", t);
713                 }
714             }
715         } catch (XmlReaderException e) {
716             problems.add(
717                     Severity.FATAL,
718                     ModelProblem.Version.BASE,
719                     "Non-parseable POM " + modelSource.getLocation() + ": " + e.getMessage(),
720                     e);
721             throw problems.newModelBuilderException();
722         } catch (IOException e) {
723             String msg = e.getMessage();
724             if (msg == null || msg.isEmpty()) {
725                 // NOTE: There's java.nio.charset.MalformedInputException and sun.io.MalformedInputException
726                 if (e.getClass().getName().endsWith("MalformedInputException")) {
727                     msg = "Some input bytes do not match the file encoding.";
728                 } else {
729                     msg = e.getClass().getSimpleName();
730                 }
731             }
732             problems.add(
733                     Severity.FATAL,
734                     ModelProblem.Version.BASE,
735                     "Non-readable POM " + modelSource.getLocation() + ": " + msg,
736                     e);
737             throw problems.newModelBuilderException();
738         }
739 
740         if (modelSource.getPath() != null) {
741             model = model.withPomFile(modelSource.getPath());
742 
743             // subprojects discovery
744             if (model.getSubprojects().isEmpty()
745                     && model.getModules().isEmpty()
746                     // only discover subprojects if POM > 4.0.0
747                     && !MODEL_VERSION_4_0_0.equals(model.getModelVersion())
748                     // and if packaging is POM (we check type, but the session is not yet available,
749                     // we would require the project realm if we want to support extensions
750                     && Type.POM.equals(model.getPackaging())) {
751                 List<String> subprojects = new ArrayList<>();
752                 try (Stream<Path> files = Files.list(model.getProjectDirectory())) {
753                     for (Path f : files.toList()) {
754                         if (Files.isDirectory(f)) {
755                             Path subproject = modelProcessor.locateExistingPom(f);
756                             if (subproject != null) {
757                                 subprojects.add(f.getFileName().toString());
758                             }
759                         }
760                     }
761                     if (!subprojects.isEmpty()) {
762                         model = model.withSubprojects(subprojects);
763                     }
764                 } catch (IOException e) {
765                     problems.add(Severity.FATAL, ModelProblem.Version.V41, "Error discovering subprojects", e);
766                 }
767             }
768         }
769 
770         problems.setSource(model);
771         modelValidator.validateFileModel(model, request, problems);
772         if (hasFatalErrors(problems)) {
773             throw problems.newModelBuilderException();
774         }
775 
776         return model;
777     }
778 
779     Model readRawModel(ModelBuilderRequest request, DefaultModelProblemCollector problems)
780             throws ModelBuilderException {
781         ModelSource modelSource = request.getSource();
782 
783         ModelData modelData =
784                 cache(getModelCache(request), modelSource, RAW, () -> doReadRawModel(modelSource, request, problems));
785 
786         return modelData.model();
787     }
788 
789     private ModelData doReadRawModel(
790             ModelSource modelSource, ModelBuilderRequest request, DefaultModelProblemCollector problems)
791             throws ModelBuilderException {
792         Model rawModel = readFileModel(request, problems);
793         if (Features.buildConsumer(request.getUserProperties()) && modelSource.getPath() != null) {
794             Path pomFile = modelSource.getPath();
795 
796             try {
797                 ModelTransformerContextBuilder transformerContextBuilder = getTransformerContextBuilder(request);
798                 if (transformerContextBuilder != null) {
799                     ModelTransformerContext context = transformerContextBuilder.initialize(request, problems);
800                     rawModel = this.transformer.transform(context, rawModel, pomFile);
801                 }
802             } catch (ModelTransformerException e) {
803                 problems.add(Severity.FATAL, ModelProblem.Version.V40, null, e);
804             }
805         }
806 
807         String namespace = rawModel.getNamespaceUri();
808         if (rawModel.getModelVersion() == null && namespace != null && namespace.startsWith(NAMESPACE_PREFIX)) {
809             rawModel = rawModel.withModelVersion(namespace.substring(NAMESPACE_PREFIX.length()));
810         }
811 
812         modelValidator.validateRawModel(rawModel, request, problems);
813 
814         if (hasFatalErrors(problems)) {
815             throw problems.newModelBuilderException();
816         }
817 
818         return new ModelData(modelSource, rawModel);
819     }
820 
821     static String getGroupId(Model model) {
822         String groupId = model.getGroupId();
823         if (groupId == null && model.getParent() != null) {
824             groupId = model.getParent().getGroupId();
825         }
826         return groupId;
827     }
828 
829     private String getVersion(Model model) {
830         String version = model.getVersion();
831         if (version == null && model.getParent() != null) {
832             version = model.getParent().getVersion();
833         }
834         return version;
835     }
836 
837     private DefaultProfileActivationContext getProfileActivationContext(ModelBuilderRequest request, Model model) {
838         DefaultProfileActivationContext context = new DefaultProfileActivationContext();
839 
840         context.setActiveProfileIds(request.getActiveProfileIds());
841         context.setInactiveProfileIds(request.getInactiveProfileIds());
842         context.setSystemProperties(request.getSystemProperties());
843         // enrich user properties with project packaging
844         Map<String, String> userProperties = new HashMap<>(request.getUserProperties());
845         if (!userProperties.containsKey(ProfileActivationContext.PROPERTY_NAME_PACKAGING)) {
846             userProperties.put(ProfileActivationContext.PROPERTY_NAME_PACKAGING, model.getPackaging());
847         }
848         context.setUserProperties(userProperties);
849         context.setProjectDirectory(model.getProjectDirectory());
850 
851         return context;
852     }
853 
854     private void checkPluginVersions(List<Model> lineage, ModelBuilderRequest request, ModelProblemCollector problems) {
855         if (request.getValidationLevel() < ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_2_0) {
856             return;
857         }
858 
859         Map<String, Plugin> plugins = new HashMap<>();
860         Map<String, String> versions = new HashMap<>();
861         Map<String, String> managedVersions = new HashMap<>();
862 
863         for (int i = lineage.size() - 1; i >= 0; i--) {
864             Model model = lineage.get(i);
865             Build build = model.getBuild();
866             if (build != null) {
867                 for (Plugin plugin : build.getPlugins()) {
868                     String key = plugin.getKey();
869                     if (versions.get(key) == null) {
870                         versions.put(key, plugin.getVersion());
871                         plugins.put(key, plugin);
872                     }
873                 }
874                 PluginManagement mgmt = build.getPluginManagement();
875                 if (mgmt != null) {
876                     for (Plugin plugin : mgmt.getPlugins()) {
877                         String key = plugin.getKey();
878                         managedVersions.computeIfAbsent(key, k -> plugin.getVersion());
879                     }
880                 }
881             }
882         }
883 
884         for (String key : versions.keySet()) {
885             if (versions.get(key) == null && managedVersions.get(key) == null) {
886                 InputLocation location = plugins.get(key).getLocation("");
887                 problems.add(
888                         Severity.WARNING,
889                         ModelProblem.Version.V20,
890                         "'build.plugins.plugin.version' for " + key + " is missing.",
891                         location);
892             }
893         }
894     }
895 
896     private Model assembleInheritance(
897             List<Model> lineage, ModelBuilderRequest request, ModelProblemCollector problems) {
898         Model parent = lineage.get(lineage.size() - 1);
899         for (int i = lineage.size() - 2; i >= 0; i--) {
900             Model child = lineage.get(i);
901             parent = inheritanceAssembler.assembleModelInheritance(child, parent, request, problems);
902         }
903         return parent;
904     }
905 
906     private Map<String, Activation> getProfileActivations(Model model) {
907         return model.getProfiles().stream()
908                 .filter(p -> p.getActivation() != null)
909                 .collect(Collectors.toMap(Profile::getId, Profile::getActivation));
910     }
911 
912     private Model injectProfileActivations(Model model, Map<String, Activation> activations) {
913         List<Profile> profiles = new ArrayList<>();
914         boolean modified = false;
915         for (Profile profile : model.getProfiles()) {
916             Activation activation = profile.getActivation();
917             if (activation != null) {
918                 // restore activation
919                 profile = profile.withActivation(activations.get(profile.getId()));
920                 modified = true;
921             }
922             profiles.add(profile);
923         }
924         return modified ? model.withProfiles(profiles) : model;
925     }
926 
927     private Model interpolateModel(Model model, ModelBuilderRequest request, ModelProblemCollector problems) {
928         Model interpolatedModel =
929                 modelInterpolator.interpolateModel(model, model.getProjectDirectory(), request, problems);
930         if (interpolatedModel.getParent() != null) {
931             StringSearchInterpolator ssi = new StringSearchInterpolator();
932             ssi.addValueSource(new MapBasedValueSource(request.getSession().getUserProperties()));
933             ssi.addValueSource(new MapBasedValueSource(model.getProperties()));
934             ssi.addValueSource(new MapBasedValueSource(request.getSession().getSystemProperties()));
935             try {
936                 String interpolated =
937                         ssi.interpolate(interpolatedModel.getParent().getVersion());
938                 interpolatedModel = interpolatedModel.withParent(
939                         interpolatedModel.getParent().withVersion(interpolated));
940             } catch (Exception e) {
941                 problems.add(
942                         Severity.ERROR,
943                         ModelProblem.Version.BASE,
944                         "Failed to interpolate field: "
945                                 + interpolatedModel.getParent().getVersion()
946                                 + " on class: ",
947                         e);
948             }
949         }
950         interpolatedModel = interpolatedModel.withPomFile(model.getPomFile());
951         return interpolatedModel;
952     }
953 
954     private ModelData readParent(
955             Model childModel,
956             ModelSource childSource,
957             ModelBuilderRequest request,
958             DefaultModelProblemCollector problems)
959             throws ModelBuilderException {
960         ModelData parentData = null;
961 
962         Parent parent = childModel.getParent();
963         if (parent != null) {
964             parentData = readParentLocally(childModel, childSource, request, problems);
965             if (parentData == null) {
966                 parentData = readParentExternally(childModel, request, problems);
967             }
968 
969             Model parentModel = parentData.model();
970             if (!"pom".equals(parentModel.getPackaging())) {
971                 problems.add(
972                         Severity.ERROR,
973                         ModelProblem.Version.BASE,
974                         "Invalid packaging for parent POM " + ModelProblemUtils.toSourceHint(parentModel)
975                                 + ", must be \"pom\" but is \"" + parentModel.getPackaging() + "\"",
976                         parentModel.getLocation("packaging"));
977             }
978         }
979 
980         return parentData;
981     }
982 
983     private ModelData readParentLocally(
984             Model childModel,
985             ModelSource childSource,
986             ModelBuilderRequest request,
987             DefaultModelProblemCollector problems)
988             throws ModelBuilderException {
989         final Parent parent = childModel.getParent();
990         final ModelSource candidateSource;
991         final Model candidateModel;
992         final WorkspaceModelResolver resolver = getWorkspaceModelResolver(request);
993         if (resolver == null) {
994             candidateSource = getParentPomFile(childModel, childSource);
995 
996             if (candidateSource == null) {
997                 return null;
998             }
999 
1000             ModelBuilderRequest candidateBuildRequest = ModelBuilderRequest.build(request, candidateSource);
1001 
1002             candidateModel = readRawModel(candidateBuildRequest, problems);
1003         } else {
1004             try {
1005                 candidateModel =
1006                         resolver.resolveRawModel(parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
1007             } catch (ModelBuilderException e) {
1008                 problems.add(Severity.FATAL, ModelProblem.Version.BASE, e.getMessage(), parent.getLocation(""), e);
1009                 throw problems.newModelBuilderException();
1010             }
1011             if (candidateModel == null) {
1012                 return null;
1013             }
1014             candidateSource = ModelSource.fromPath(candidateModel.getPomFile());
1015         }
1016 
1017         //
1018         // TODO jvz Why isn't all this checking the job of the duty of the workspace resolver, we know that we
1019         // have a model that is suitable, yet more checks are done here and the one for the version is problematic
1020         // before because with parents as ranges it will never work in this scenario.
1021         //
1022 
1023         String groupId = getGroupId(candidateModel);
1024         String artifactId = candidateModel.getArtifactId();
1025 
1026         if (groupId == null
1027                 || !groupId.equals(parent.getGroupId())
1028                 || artifactId == null
1029                 || !artifactId.equals(parent.getArtifactId())) {
1030             StringBuilder buffer = new StringBuilder(256);
1031             buffer.append("'parent.relativePath'");
1032             if (childModel != problems.getRootModel()) {
1033                 buffer.append(" of POM ").append(ModelProblemUtils.toSourceHint(childModel));
1034             }
1035             buffer.append(" points at ").append(groupId).append(':').append(artifactId);
1036             buffer.append(" instead of ").append(parent.getGroupId()).append(':');
1037             buffer.append(parent.getArtifactId()).append(", please verify your project structure");
1038 
1039             problems.setSource(childModel);
1040             problems.add(Severity.WARNING, ModelProblem.Version.BASE, buffer.toString(), parent.getLocation(""));
1041             return null;
1042         }
1043 
1044         String version = getVersion(candidateModel);
1045         if (version != null && parent.getVersion() != null && !version.equals(parent.getVersion())) {
1046             try {
1047                 VersionRange parentRange = versionParser.parseVersionRange(parent.getVersion());
1048                 if (!parentRange.contains(versionParser.parseVersion(version))) {
1049                     // version skew drop back to resolution from the repository
1050                     return null;
1051                 }
1052 
1053                 // Validate versions aren't inherited when using parent ranges the same way as when read externally.
1054                 String rawChildModelVersion = childModel.getVersion();
1055 
1056                 if (rawChildModelVersion == null) {
1057                     // Message below is checked for in the MNG-2199 core IT.
1058                     problems.add(
1059                             Severity.FATAL,
1060                             ModelProblem.Version.V31,
1061                             "Version must be a constant",
1062                             childModel.getLocation(""));
1063 
1064                 } else {
1065                     if (rawChildVersionReferencesParent(rawChildModelVersion)) {
1066                         // Message below is checked for in the MNG-2199 core IT.
1067                         problems.add(
1068                                 Severity.FATAL,
1069                                 ModelProblem.Version.V31,
1070                                 "Version must be a constant",
1071                                 childModel.getLocation("version"));
1072                     }
1073                 }
1074 
1075                 // MNG-2199: What else to check here ?
1076             } catch (VersionParserException e) {
1077                 // invalid version range, so drop back to resolution from the repository
1078                 return null;
1079             }
1080         }
1081 
1082         //
1083         // Here we just need to know that a version is fine to use but this validation we can do in our workspace
1084         // resolver.
1085         //
1086 
1087         /*
1088          * if ( version == null || !version.equals( parent.getVersion() ) ) { return null; }
1089          */
1090 
1091         return new ModelData(candidateSource, candidateModel);
1092     }
1093 
1094     private boolean rawChildVersionReferencesParent(String rawChildModelVersion) {
1095         return rawChildModelVersion.equals("${pom.version}")
1096                 || rawChildModelVersion.equals("${project.version}")
1097                 || rawChildModelVersion.equals("${pom.parent.version}")
1098                 || rawChildModelVersion.equals("${project.parent.version}");
1099     }
1100 
1101     private ModelSource getParentPomFile(Model childModel, ModelSource source) {
1102         String parentPath = childModel.getParent().getRelativePath();
1103         if (parentPath == null || parentPath.isEmpty()) {
1104             return null;
1105         } else {
1106             return source.resolve(modelProcessor::locateExistingPom, parentPath);
1107         }
1108     }
1109 
1110     private ModelData readParentExternally(
1111             Model childModel, ModelBuilderRequest request, DefaultModelProblemCollector problems)
1112             throws ModelBuilderException {
1113         problems.setSource(childModel);
1114 
1115         Parent parent = childModel.getParent();
1116 
1117         String groupId = parent.getGroupId();
1118         String artifactId = parent.getArtifactId();
1119         String version = parent.getVersion();
1120 
1121         ModelResolver modelResolver = getModelResolver(request);
1122         Objects.requireNonNull(
1123                 modelResolver,
1124                 String.format(
1125                         "request.modelResolver cannot be null (parent POM %s and POM %s)",
1126                         ModelProblemUtils.toId(groupId, artifactId, version),
1127                         ModelProblemUtils.toSourceHint(childModel)));
1128 
1129         ModelSource modelSource;
1130         try {
1131             AtomicReference<Parent> modified = new AtomicReference<>();
1132             Session session = request.getSession()
1133                     .withRemoteRepositories(request.getModelRepositoryHolder().getRepositories());
1134             modelSource = modelResolver.resolveModel(session, parent, modified);
1135             if (modified.get() != null) {
1136                 parent = modified.get();
1137             }
1138         } catch (ModelResolverException e) {
1139             // Message below is checked for in the MNG-2199 core IT.
1140             StringBuilder buffer = new StringBuilder(256);
1141             buffer.append("Non-resolvable parent POM");
1142             if (!containsCoordinates(e.getMessage(), groupId, artifactId, version)) {
1143                 buffer.append(' ').append(ModelProblemUtils.toId(groupId, artifactId, version));
1144             }
1145             if (childModel != problems.getRootModel()) {
1146                 buffer.append(" for ").append(ModelProblemUtils.toId(childModel));
1147             }
1148             buffer.append(": ").append(e.getMessage());
1149             if (childModel.getProjectDirectory() != null) {
1150                 if (parent.getRelativePath() == null || parent.getRelativePath().isEmpty()) {
1151                     buffer.append(" and 'parent.relativePath' points at no local POM");
1152                 } else {
1153                     buffer.append(" and 'parent.relativePath' points at wrong local POM");
1154                 }
1155             }
1156 
1157             problems.add(Severity.FATAL, ModelProblem.Version.BASE, buffer.toString(), parent.getLocation(""), e);
1158             throw problems.newModelBuilderException();
1159         }
1160 
1161         int validationLevel = Math.min(request.getValidationLevel(), ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_2_0);
1162         ModelBuilderRequest lenientRequest = ModelBuilderRequest.builder(request)
1163                 .validationLevel(validationLevel)
1164                 .projectBuild(false)
1165                 .source(modelSource)
1166                 .build();
1167 
1168         Model parentModel = readRawModel(lenientRequest, problems);
1169 
1170         if (!parent.getVersion().equals(version)) {
1171             String rawChildModelVersion = childModel.getVersion();
1172 
1173             if (rawChildModelVersion == null) {
1174                 // Message below is checked for in the MNG-2199 core IT.
1175                 problems.add(
1176                         Severity.FATAL,
1177                         ModelProblem.Version.V31,
1178                         "Version must be a constant",
1179                         childModel.getLocation(""));
1180 
1181             } else {
1182                 if (rawChildVersionReferencesParent(rawChildModelVersion)) {
1183                     // Message below is checked for in the MNG-2199 core IT.
1184                     problems.add(
1185                             Severity.FATAL,
1186                             ModelProblem.Version.V31,
1187                             "Version must be a constant",
1188                             childModel.getLocation("version"));
1189                 }
1190             }
1191 
1192             // MNG-2199: What else to check here ?
1193         }
1194 
1195         return new ModelData(modelSource, parentModel);
1196     }
1197 
1198     private Model getSuperModel(String modelVersion) {
1199         return superPomProvider.getSuperPom(modelVersion);
1200     }
1201 
1202     private Model importDependencyManagement(
1203             Model model,
1204             ModelBuilderRequest request,
1205             DefaultModelProblemCollector problems,
1206             Collection<String> importIds) {
1207         DependencyManagement depMgmt = model.getDependencyManagement();
1208 
1209         if (depMgmt == null) {
1210             return model;
1211         }
1212 
1213         String importing = model.getGroupId() + ':' + model.getArtifactId() + ':' + model.getVersion();
1214 
1215         importIds.add(importing);
1216 
1217         List<DependencyManagement> importMgmts = null;
1218 
1219         List<Dependency> deps = new ArrayList<>(depMgmt.getDependencies());
1220         for (Iterator<Dependency> it = deps.iterator(); it.hasNext(); ) {
1221             Dependency dependency = it.next();
1222 
1223             if (!("pom".equals(dependency.getType()) && "import".equals(dependency.getScope()))
1224                     || "bom".equals(dependency.getType())) {
1225                 continue;
1226             }
1227 
1228             it.remove();
1229 
1230             DependencyManagement importMgmt = loadDependencyManagement(model, request, problems, dependency, importIds);
1231 
1232             if (importMgmt != null) {
1233                 if (importMgmts == null) {
1234                     importMgmts = new ArrayList<>();
1235                 }
1236 
1237                 importMgmts.add(importMgmt);
1238             }
1239         }
1240 
1241         importIds.remove(importing);
1242 
1243         model = model.withDependencyManagement(model.getDependencyManagement().withDependencies(deps));
1244 
1245         return dependencyManagementImporter.importManagement(model, importMgmts, request, problems);
1246     }
1247 
1248     private DependencyManagement loadDependencyManagement(
1249             Model model,
1250             ModelBuilderRequest request,
1251             DefaultModelProblemCollector problems,
1252             Dependency dependency,
1253             Collection<String> importIds) {
1254         String groupId = dependency.getGroupId();
1255         String artifactId = dependency.getArtifactId();
1256         String version = dependency.getVersion();
1257 
1258         if (groupId == null || groupId.isEmpty()) {
1259             problems.add(
1260                     Severity.ERROR,
1261                     ModelProblem.Version.BASE,
1262                     "'dependencyManagement.dependencies.dependency.groupId' for " + dependency.getManagementKey()
1263                             + " is missing.",
1264                     dependency.getLocation(""));
1265             return null;
1266         }
1267         if (artifactId == null || artifactId.isEmpty()) {
1268             problems.add(
1269                     Severity.ERROR,
1270                     ModelProblem.Version.BASE,
1271                     "'dependencyManagement.dependencies.dependency.artifactId' for " + dependency.getManagementKey()
1272                             + " is missing.",
1273                     dependency.getLocation(""));
1274             return null;
1275         }
1276         if (version == null || version.isEmpty()) {
1277             problems.add(
1278                     Severity.ERROR,
1279                     ModelProblem.Version.BASE,
1280                     "'dependencyManagement.dependencies.dependency.version' for " + dependency.getManagementKey()
1281                             + " is missing.",
1282                     dependency.getLocation(""));
1283             return null;
1284         }
1285 
1286         String imported = groupId + ':' + artifactId + ':' + version;
1287 
1288         if (importIds.contains(imported)) {
1289             StringBuilder message =
1290                     new StringBuilder("The dependencies of type=pom and with scope=import form a cycle: ");
1291             for (String modelId : importIds) {
1292                 message.append(modelId).append(" -> ");
1293             }
1294             message.append(imported);
1295             problems.add(Severity.ERROR, ModelProblem.Version.BASE, message.toString());
1296 
1297             return null;
1298         }
1299 
1300         Model importModel = cache(
1301                 getModelCache(request),
1302                 groupId,
1303                 artifactId,
1304                 version,
1305                 IMPORT,
1306                 () -> doLoadDependencyManagement(
1307                         model, request, problems, dependency, groupId, artifactId, version, importIds));
1308         DependencyManagement importMgmt = importModel != null ? importModel.getDependencyManagement() : null;
1309         if (importMgmt == null) {
1310             importMgmt = DependencyManagement.newInstance();
1311         }
1312 
1313         // [MNG-5600] Dependency management import should support exclusions.
1314         List<Exclusion> exclusions = dependency.getExclusions();
1315         if (importMgmt != null && !exclusions.isEmpty()) {
1316             // Dependency excluded from import.
1317             List<Dependency> dependencies = importMgmt.getDependencies().stream()
1318                     .filter(candidate -> exclusions.stream().noneMatch(exclusion -> match(exclusion, candidate)))
1319                     .map(candidate -> addExclusions(candidate, exclusions))
1320                     .collect(Collectors.toList());
1321             importMgmt = importMgmt.withDependencies(dependencies);
1322         }
1323 
1324         return importMgmt;
1325     }
1326 
1327     private static org.apache.maven.api.model.Dependency addExclusions(
1328             org.apache.maven.api.model.Dependency candidate, List<Exclusion> exclusions) {
1329         return candidate.withExclusions(Stream.concat(candidate.getExclusions().stream(), exclusions.stream())
1330                 .toList());
1331     }
1332 
1333     private boolean match(Exclusion exclusion, Dependency candidate) {
1334         return match(exclusion.getGroupId(), candidate.getGroupId())
1335                 && match(exclusion.getArtifactId(), candidate.getArtifactId());
1336     }
1337 
1338     private boolean match(String match, String text) {
1339         return match.equals("*") || match.equals(text);
1340     }
1341 
1342     @SuppressWarnings("checkstyle:parameternumber")
1343     private Model doLoadDependencyManagement(
1344             Model model,
1345             ModelBuilderRequest request,
1346             DefaultModelProblemCollector problems,
1347             Dependency dependency,
1348             String groupId,
1349             String artifactId,
1350             String version,
1351             Collection<String> importIds) {
1352         final WorkspaceModelResolver workspaceResolver = getWorkspaceModelResolver(request);
1353         final ModelResolver modelResolver = getModelResolver(request);
1354         if (workspaceResolver == null && modelResolver == null) {
1355             throw new NullPointerException(String.format(
1356                     "request.workspaceModelResolver and request.modelResolver cannot be null (parent POM %s and POM %s)",
1357                     ModelProblemUtils.toId(groupId, artifactId, version), ModelProblemUtils.toSourceHint(model)));
1358         }
1359 
1360         Model importModel = null;
1361         if (workspaceResolver != null) {
1362             try {
1363                 importModel = workspaceResolver.resolveEffectiveModel(groupId, artifactId, version);
1364             } catch (ModelBuilderException e) {
1365                 problems.add(Severity.FATAL, ModelProblem.Version.BASE, null, e);
1366                 return null;
1367             }
1368         }
1369 
1370         // no workspace resolver or workspace resolver returned null (i.e. model not in workspace)
1371         if (importModel == null) {
1372             final ModelSource importSource;
1373             try {
1374                 Session session = request.getSession()
1375                         .withRemoteRepositories(
1376                                 request.getModelRepositoryHolder().getRepositories());
1377                 importSource = modelResolver.resolveModel(session, dependency, new AtomicReference<>());
1378             } catch (ModelBuilderException e) {
1379                 StringBuilder buffer = new StringBuilder(256);
1380                 buffer.append("Non-resolvable import POM");
1381                 if (!containsCoordinates(e.getMessage(), groupId, artifactId, version)) {
1382                     buffer.append(' ').append(ModelProblemUtils.toId(groupId, artifactId, version));
1383                 }
1384                 buffer.append(": ").append(e.getMessage());
1385 
1386                 problems.add(
1387                         Severity.ERROR, ModelProblem.Version.BASE, buffer.toString(), dependency.getLocation(""), e);
1388                 return null;
1389             }
1390 
1391             Path rootDirectory;
1392             try {
1393                 rootDirectory = request.getSession().getRootDirectory();
1394             } catch (IllegalStateException e) {
1395                 rootDirectory = null;
1396             }
1397             if (importSource.getPath() != null && rootDirectory != null) {
1398                 Path sourcePath = importSource.getPath();
1399                 if (sourcePath.startsWith(rootDirectory)) {
1400                     problems.add(
1401                             Severity.WARNING,
1402                             ModelProblem.Version.BASE,
1403                             "BOM imports from within reactor should be avoided",
1404                             dependency.getLocation(""));
1405                 }
1406             }
1407 
1408             final ModelBuilderResult importResult;
1409             try {
1410                 ModelBuilderRequest importRequest = ModelBuilderRequest.builder()
1411                         .session(request.getSession()
1412                                 .withRemoteRepositories(
1413                                         request.getModelRepositoryHolder().getRepositories()))
1414                         .validationLevel(ModelBuilderRequest.VALIDATION_LEVEL_MINIMAL)
1415                         .systemProperties(request.getSystemProperties())
1416                         .userProperties(request.getUserProperties())
1417                         .source(importSource)
1418                         .modelResolver(modelResolver)
1419                         .modelCache(request.getModelCache())
1420                         .modelRepositoryHolder(
1421                                 request.getModelRepositoryHolder().copy())
1422                         .twoPhaseBuilding(false)
1423                         .build();
1424                 importResult = build(importRequest, importIds);
1425             } catch (ModelBuilderException e) {
1426                 e.getResult().getProblems().forEach(problems::add);
1427                 return null;
1428             }
1429 
1430             importResult.getProblems().forEach(problems::add);
1431 
1432             importModel = importResult.getEffectiveModel();
1433         }
1434 
1435         return importModel;
1436     }
1437 
1438     private static <T> T cache(
1439             ModelCache cache, String groupId, String artifactId, String version, String tag, Callable<T> supplier) {
1440         Supplier<T> s = asSupplier(supplier);
1441         if (cache == null) {
1442             return s.get();
1443         } else {
1444             return cache.computeIfAbsent(groupId, artifactId, version, tag, s);
1445         }
1446     }
1447 
1448     private static <T> T cache(ModelCache cache, Source source, String tag, Callable<T> supplier) {
1449         Supplier<T> s = asSupplier(supplier);
1450         if (cache == null) {
1451             return s.get();
1452         } else {
1453             return cache.computeIfAbsent(source, tag, s);
1454         }
1455     }
1456 
1457     private static <T> Supplier<T> asSupplier(Callable<T> supplier) {
1458         return () -> {
1459             try {
1460                 return supplier.call();
1461             } catch (Exception e) {
1462                 uncheckedThrow(e);
1463                 return null;
1464             }
1465         };
1466     }
1467 
1468     static <T extends Throwable> void uncheckedThrow(Throwable t) throws T {
1469         throw (T) t; // rely on vacuous cast
1470     }
1471 
1472     private Model fireEvent(
1473             Model model,
1474             ModelBuilderRequest request,
1475             ModelProblemCollector problems,
1476             BiConsumer<ModelBuildingListener, ModelBuildingEvent> catapult) {
1477         ModelBuildingListener listener = getModelBuildingListener(request);
1478 
1479         if (listener != null) {
1480             AtomicReference<Model> m = new AtomicReference<>(model);
1481 
1482             ModelBuildingEvent event = new DefaultModelBuildingEvent(model, m::set, request, problems);
1483 
1484             catapult.accept(listener, event);
1485 
1486             return m.get();
1487         }
1488 
1489         return model;
1490     }
1491 
1492     private boolean containsCoordinates(String message, String groupId, String artifactId, String version) {
1493         return message != null
1494                 && (groupId == null || message.contains(groupId))
1495                 && (artifactId == null || message.contains(artifactId))
1496                 && (version == null || message.contains(version));
1497     }
1498 
1499     protected boolean hasModelErrors(ModelProblemCollector problems) {
1500         return problems.hasErrors();
1501     }
1502 
1503     protected boolean hasFatalErrors(ModelProblemCollector problems) {
1504         return problems.hasFatalErrors();
1505     }
1506 
1507     ModelProcessor getModelProcessor() {
1508         return modelProcessor;
1509     }
1510 
1511     private static ModelCache getModelCache(ModelBuilderRequest request) {
1512         return request.getModelCache();
1513     }
1514 
1515     private static ModelBuildingListener getModelBuildingListener(ModelBuilderRequest request) {
1516         return (ModelBuildingListener) request.getListener();
1517     }
1518 
1519     private static WorkspaceModelResolver getWorkspaceModelResolver(ModelBuilderRequest request) {
1520         return null; // request.getWorkspaceModelResolver();
1521     }
1522 
1523     private static ModelResolver getModelResolver(ModelBuilderRequest request) {
1524         return request.getModelResolver();
1525     }
1526 
1527     private static ModelTransformerContextBuilder getTransformerContextBuilder(ModelBuilderRequest request) {
1528         return request.getTransformerContextBuilder();
1529     }
1530 }