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.File;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.lang.reflect.Field;
25  import java.nio.file.Files;
26  import java.nio.file.Path;
27  import java.nio.file.Paths;
28  import java.util.ArrayList;
29  import java.util.Collection;
30  import java.util.HashMap;
31  import java.util.HashSet;
32  import java.util.Iterator;
33  import java.util.LinkedHashSet;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.Objects;
37  import java.util.Set;
38  import java.util.concurrent.ConcurrentHashMap;
39  import java.util.concurrent.CopyOnWriteArrayList;
40  import java.util.concurrent.Executor;
41  import java.util.concurrent.Executors;
42  import java.util.concurrent.atomic.AtomicReference;
43  import java.util.function.Function;
44  import java.util.function.Supplier;
45  import java.util.function.UnaryOperator;
46  import java.util.regex.Matcher;
47  import java.util.regex.Pattern;
48  import java.util.stream.Collectors;
49  import java.util.stream.Stream;
50  
51  import org.apache.maven.api.Constants;
52  import org.apache.maven.api.RemoteRepository;
53  import org.apache.maven.api.Session;
54  import org.apache.maven.api.SessionData;
55  import org.apache.maven.api.Type;
56  import org.apache.maven.api.VersionRange;
57  import org.apache.maven.api.di.Inject;
58  import org.apache.maven.api.di.Named;
59  import org.apache.maven.api.di.Singleton;
60  import org.apache.maven.api.model.Activation;
61  import org.apache.maven.api.model.ActivationFile;
62  import org.apache.maven.api.model.Dependency;
63  import org.apache.maven.api.model.DependencyManagement;
64  import org.apache.maven.api.model.Exclusion;
65  import org.apache.maven.api.model.InputLocation;
66  import org.apache.maven.api.model.InputSource;
67  import org.apache.maven.api.model.Model;
68  import org.apache.maven.api.model.Parent;
69  import org.apache.maven.api.model.Profile;
70  import org.apache.maven.api.model.Repository;
71  import org.apache.maven.api.services.BuilderProblem;
72  import org.apache.maven.api.services.BuilderProblem.Severity;
73  import org.apache.maven.api.services.Interpolator;
74  import org.apache.maven.api.services.InterpolatorException;
75  import org.apache.maven.api.services.MavenException;
76  import org.apache.maven.api.services.ModelBuilder;
77  import org.apache.maven.api.services.ModelBuilderException;
78  import org.apache.maven.api.services.ModelBuilderRequest;
79  import org.apache.maven.api.services.ModelBuilderResult;
80  import org.apache.maven.api.services.ModelProblem;
81  import org.apache.maven.api.services.ModelProblem.Version;
82  import org.apache.maven.api.services.ModelProblemCollector;
83  import org.apache.maven.api.services.ModelSource;
84  import org.apache.maven.api.services.RepositoryFactory;
85  import org.apache.maven.api.services.Source;
86  import org.apache.maven.api.services.SuperPomProvider;
87  import org.apache.maven.api.services.VersionParserException;
88  import org.apache.maven.api.services.model.DependencyManagementImporter;
89  import org.apache.maven.api.services.model.DependencyManagementInjector;
90  import org.apache.maven.api.services.model.InheritanceAssembler;
91  import org.apache.maven.api.services.model.ModelCache;
92  import org.apache.maven.api.services.model.ModelCacheFactory;
93  import org.apache.maven.api.services.model.ModelInterpolator;
94  import org.apache.maven.api.services.model.ModelNormalizer;
95  import org.apache.maven.api.services.model.ModelPathTranslator;
96  import org.apache.maven.api.services.model.ModelProcessor;
97  import org.apache.maven.api.services.model.ModelResolver;
98  import org.apache.maven.api.services.model.ModelResolverException;
99  import org.apache.maven.api.services.model.ModelUrlNormalizer;
100 import org.apache.maven.api.services.model.ModelValidator;
101 import org.apache.maven.api.services.model.ModelVersionParser;
102 import org.apache.maven.api.services.model.PathTranslator;
103 import org.apache.maven.api.services.model.PluginConfigurationExpander;
104 import org.apache.maven.api.services.model.PluginManagementInjector;
105 import org.apache.maven.api.services.model.ProfileInjector;
106 import org.apache.maven.api.services.model.ProfileSelector;
107 import org.apache.maven.api.services.model.RootLocator;
108 import org.apache.maven.api.services.xml.XmlReaderException;
109 import org.apache.maven.api.services.xml.XmlReaderRequest;
110 import org.apache.maven.api.spi.ModelParserException;
111 import org.apache.maven.api.spi.ModelTransformer;
112 import org.apache.maven.internal.impl.util.PhasingExecutor;
113 import org.apache.maven.model.v4.MavenTransformer;
114 import org.slf4j.Logger;
115 import org.slf4j.LoggerFactory;
116 
117 /**
118  * The model builder is responsible for building the {@link Model} from the POM file.
119  * There are two ways to main use cases: the first one is to build the model from a POM file
120  * on the file system in order to actually build the project. The second one is to build the
121  * model for a dependency  or an external parent.
122  */
123 @Named
124 @Singleton
125 public class DefaultModelBuilder implements ModelBuilder {
126 
127     public static final String NAMESPACE_PREFIX = "http://maven.apache.org/POM/";
128     private static final String RAW = "raw";
129     private static final String FILE = "file";
130     private static final String IMPORT = "import";
131     private static final String PARENT = "parent";
132 
133     private final Logger logger = LoggerFactory.getLogger(getClass());
134 
135     private final ModelProcessor modelProcessor;
136     private final ModelValidator modelValidator;
137     private final ModelNormalizer modelNormalizer;
138     private final ModelInterpolator modelInterpolator;
139     private final ModelPathTranslator modelPathTranslator;
140     private final ModelUrlNormalizer modelUrlNormalizer;
141     private final SuperPomProvider superPomProvider;
142     private final InheritanceAssembler inheritanceAssembler;
143     private final ProfileSelector profileSelector;
144     private final ProfileInjector profileInjector;
145     private final PluginManagementInjector pluginManagementInjector;
146     private final DependencyManagementInjector dependencyManagementInjector;
147     private final DependencyManagementImporter dependencyManagementImporter;
148     private final PluginConfigurationExpander pluginConfigurationExpander;
149     private final ModelVersionParser versionParser;
150     private final List<ModelTransformer> transformers;
151     private final ModelCacheFactory modelCacheFactory;
152     private final ModelResolver modelResolver;
153     private final Interpolator interpolator;
154     private final PathTranslator pathTranslator;
155     private final RootLocator rootLocator;
156 
157     @SuppressWarnings("checkstyle:ParameterNumber")
158     @Inject
159     public DefaultModelBuilder(
160             ModelProcessor modelProcessor,
161             ModelValidator modelValidator,
162             ModelNormalizer modelNormalizer,
163             ModelInterpolator modelInterpolator,
164             ModelPathTranslator modelPathTranslator,
165             ModelUrlNormalizer modelUrlNormalizer,
166             SuperPomProvider superPomProvider,
167             InheritanceAssembler inheritanceAssembler,
168             ProfileSelector profileSelector,
169             ProfileInjector profileInjector,
170             PluginManagementInjector pluginManagementInjector,
171             DependencyManagementInjector dependencyManagementInjector,
172             DependencyManagementImporter dependencyManagementImporter,
173             PluginConfigurationExpander pluginConfigurationExpander,
174             ModelVersionParser versionParser,
175             List<ModelTransformer> transformers,
176             ModelCacheFactory modelCacheFactory,
177             ModelResolver modelResolver,
178             Interpolator interpolator,
179             PathTranslator pathTranslator,
180             RootLocator rootLocator) {
181         this.modelProcessor = modelProcessor;
182         this.modelValidator = modelValidator;
183         this.modelNormalizer = modelNormalizer;
184         this.modelInterpolator = modelInterpolator;
185         this.modelPathTranslator = modelPathTranslator;
186         this.modelUrlNormalizer = modelUrlNormalizer;
187         this.superPomProvider = superPomProvider;
188         this.inheritanceAssembler = inheritanceAssembler;
189         this.profileSelector = profileSelector;
190         this.profileInjector = profileInjector;
191         this.pluginManagementInjector = pluginManagementInjector;
192         this.dependencyManagementInjector = dependencyManagementInjector;
193         this.dependencyManagementImporter = dependencyManagementImporter;
194         this.pluginConfigurationExpander = pluginConfigurationExpander;
195         this.versionParser = versionParser;
196         this.transformers = transformers;
197         this.modelCacheFactory = modelCacheFactory;
198         this.modelResolver = modelResolver;
199         this.interpolator = interpolator;
200         this.pathTranslator = pathTranslator;
201         this.rootLocator = rootLocator;
202     }
203 
204     public ModelBuilderSession newSession() {
205         return new ModelBuilderSessionImpl();
206     }
207 
208     protected class ModelBuilderSessionImpl implements ModelBuilderSession {
209         ModelBuilderSessionState mainSession;
210 
211         /**
212          * Builds a model based on the provided ModelBuilderRequest.
213          *
214          * @param request The request containing the parameters for building the model.
215          * @return The result of the model building process.
216          * @throws ModelBuilderException If an error occurs during model building.
217          */
218         @Override
219         public ModelBuilderResult build(ModelBuilderRequest request) throws ModelBuilderException {
220             // Create or derive a session based on the request
221             ModelBuilderSessionState session;
222             if (mainSession == null) {
223                 mainSession = new ModelBuilderSessionState(request);
224                 session = mainSession;
225             } else {
226                 session = mainSession.derive(request, new DefaultModelBuilderResult());
227             }
228             // Build the request
229             if (request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_PROJECT) {
230                 // build the build poms
231                 session.buildBuildPom();
232             } else {
233                 // simply build the effective model
234                 session.buildEffectiveModel(new LinkedHashSet<>());
235             }
236             return session.result;
237         }
238     }
239 
240     protected class ModelBuilderSessionState implements ModelProblemCollector {
241         private static final Pattern REGEX = Pattern.compile("\\$\\{([^}]+)}");
242 
243         final Session session;
244         final ModelBuilderRequest request;
245         final DefaultModelBuilderResult result;
246         final ModelCache cache;
247         final Graph dag;
248         final Map<GAKey, Set<ModelSource>> mappedSources;
249 
250         String source;
251         Model sourceModel;
252         Model rootModel;
253 
254         List<RemoteRepository> pomRepositories;
255         List<RemoteRepository> externalRepositories;
256         List<RemoteRepository> repositories;
257 
258         ModelBuilderSessionState(ModelBuilderRequest request) {
259             this(
260                     request.getSession(),
261                     request,
262                     new DefaultModelBuilderResult(),
263                     request.getSession()
264                             .getData()
265                             .computeIfAbsent(SessionData.key(ModelCache.class), modelCacheFactory::newInstance),
266                     new Graph(),
267                     new ConcurrentHashMap<>(64),
268                     List.of(),
269                     repos(request),
270                     repos(request));
271         }
272 
273         static List<RemoteRepository> repos(ModelBuilderRequest request) {
274             return List.copyOf(
275                     request.getRepositories() != null
276                             ? request.getRepositories()
277                             : request.getSession().getRemoteRepositories());
278         }
279 
280         @SuppressWarnings("checkstyle:ParameterNumber")
281         private ModelBuilderSessionState(
282                 Session session,
283                 ModelBuilderRequest request,
284                 DefaultModelBuilderResult result,
285                 ModelCache cache,
286                 Graph dag,
287                 Map<GAKey, Set<ModelSource>> mappedSources,
288                 List<RemoteRepository> pomRepositories,
289                 List<RemoteRepository> externalRepositories,
290                 List<RemoteRepository> repositories) {
291             this.session = session;
292             this.request = request;
293             this.result = result;
294             this.cache = cache;
295             this.dag = dag;
296             this.mappedSources = mappedSources;
297             this.pomRepositories = pomRepositories;
298             this.externalRepositories = externalRepositories;
299             this.repositories = repositories;
300             this.result.setSource(this.request.getSource());
301         }
302 
303         ModelBuilderSessionState derive(ModelSource source) {
304             return derive(source, new DefaultModelBuilderResult(result));
305         }
306 
307         ModelBuilderSessionState derive(ModelSource source, DefaultModelBuilderResult result) {
308             return derive(ModelBuilderRequest.build(request, source), result);
309         }
310 
311         /**
312          * Creates a new session, sharing cached datas and propagating errors.
313          */
314         ModelBuilderSessionState derive(ModelBuilderRequest request) {
315             return derive(request, new DefaultModelBuilderResult(result));
316         }
317 
318         ModelBuilderSessionState derive(ModelBuilderRequest request, DefaultModelBuilderResult result) {
319             if (session != request.getSession()) {
320                 throw new IllegalArgumentException("Session mismatch");
321             }
322             return new ModelBuilderSessionState(
323                     session,
324                     request,
325                     result,
326                     cache,
327                     dag,
328                     mappedSources,
329                     pomRepositories,
330                     externalRepositories,
331                     repositories);
332         }
333 
334         @Override
335         public String toString() {
336             return "ModelBuilderSession[" + "session="
337                     + session + ", " + "request="
338                     + request + ", " + "result="
339                     + result + ", " + "cache="
340                     + cache + ']';
341         }
342 
343         PhasingExecutor createExecutor() {
344             return new PhasingExecutor(Executors.newFixedThreadPool(getParallelism()));
345         }
346 
347         private int getParallelism() {
348             int parallelism = Runtime.getRuntime().availableProcessors() / 2 + 1;
349             try {
350                 String str = request.getUserProperties().get(Constants.MAVEN_MODEL_BUILDER_PARALLELISM);
351                 if (str != null) {
352                     parallelism = Integer.parseInt(str);
353                 }
354             } catch (Exception e) {
355                 // ignore
356             }
357             return Math.max(1, Math.min(parallelism, Runtime.getRuntime().availableProcessors()));
358         }
359 
360         public Model getRawModel(Path from, String groupId, String artifactId) {
361             ModelSource source = getSource(groupId, artifactId);
362             if (source != null) {
363                 if (addEdge(from, source.getPath())) {
364                     return null;
365                 }
366                 try {
367                     return derive(source).readRawModel();
368                 } catch (ModelBuilderException e) {
369                     // gathered with problem collector
370                 }
371             }
372             return null;
373         }
374 
375         public Model getRawModel(Path from, Path path) {
376             if (!Files.isRegularFile(path)) {
377                 throw new IllegalArgumentException("Not a regular file: " + path);
378             }
379             if (addEdge(from, path)) {
380                 return null;
381             }
382             try {
383                 return derive(ModelSource.fromPath(path)).readRawModel();
384             } catch (ModelBuilderException e) {
385                 // gathered with problem collector
386             }
387             return null;
388         }
389 
390         /**
391          * Returns false if the edge was added, true if it caused a cycle.
392          */
393         private boolean addEdge(Path from, Path p) {
394             try {
395                 dag.addEdge(from.toString(), p.toString());
396                 return false;
397             } catch (Graph.CycleDetectedException e) {
398                 add(Severity.FATAL, Version.BASE, "Cycle detected between models at " + from + " and " + p, null, e);
399                 return true;
400             }
401         }
402 
403         public ModelSource getSource(String groupId, String artifactId) {
404             Set<ModelSource> sources = mappedSources.get(new GAKey(groupId, artifactId));
405             if (sources != null) {
406                 return sources.stream()
407                         .reduce((a, b) -> {
408                             throw new IllegalStateException(String.format(
409                                     "No unique Source for %s:%s: %s and %s",
410                                     groupId, artifactId, a.getLocation(), b.getLocation()));
411                         })
412                         .orElse(null);
413             }
414             return null;
415         }
416 
417         public void putSource(String groupId, String artifactId, ModelSource source) {
418             mappedSources
419                     .computeIfAbsent(new GAKey(groupId, artifactId), k -> new HashSet<>())
420                     .add(source);
421             // Also  register the source under the null groupId
422             if (groupId != null) {
423                 putSource(null, artifactId, source);
424             }
425         }
426 
427         public boolean hasFatalErrors() {
428             return result.getProblems().stream().anyMatch(p -> p.getSeverity() == Severity.FATAL);
429         }
430 
431         public boolean hasErrors() {
432             return result.getProblems().stream()
433                     .anyMatch(p -> p.getSeverity() == Severity.FATAL || p.getSeverity() == Severity.ERROR);
434         }
435 
436         @Override
437         public List<ModelProblem> getProblems() {
438             return result.getProblems();
439         }
440 
441         public void setSource(String source) {
442             this.source = source;
443             this.sourceModel = null;
444         }
445 
446         public void setSource(Model source) {
447             this.sourceModel = source;
448             this.source = null;
449 
450             if (rootModel == null) {
451                 rootModel = source;
452             }
453         }
454 
455         public String getSource() {
456             if (source == null && sourceModel != null) {
457                 source = ModelProblemUtils.toPath(sourceModel);
458             }
459             return source;
460         }
461 
462         private String getModelId() {
463             return ModelProblemUtils.toId(sourceModel);
464         }
465 
466         public void setRootModel(Model rootModel) {
467             this.rootModel = rootModel;
468         }
469 
470         public Model getRootModel() {
471             return rootModel;
472         }
473 
474         @Override
475         public void add(ModelProblem problem) {
476             result.addProblem(problem);
477         }
478 
479         @Override
480         public void add(BuilderProblem.Severity severity, ModelProblem.Version version, String message) {
481             add(severity, version, message, null, null);
482         }
483 
484         @Override
485         public void add(
486                 BuilderProblem.Severity severity,
487                 ModelProblem.Version version,
488                 String message,
489                 InputLocation location) {
490             add(severity, version, message, location, null);
491         }
492 
493         @Override
494         public void add(
495                 BuilderProblem.Severity severity, ModelProblem.Version version, String message, Exception exception) {
496             add(severity, version, message, null, exception);
497         }
498 
499         public void add(
500                 BuilderProblem.Severity severity,
501                 ModelProblem.Version version,
502                 String message,
503                 InputLocation location,
504                 Exception exception) {
505             int line = -1;
506             int column = -1;
507             String source = null;
508             String modelId = null;
509 
510             if (location != null) {
511                 line = location.getLineNumber();
512                 column = location.getColumnNumber();
513                 if (location.getSource() != null) {
514                     modelId = location.getSource().getModelId();
515                     source = location.getSource().getLocation();
516                 }
517             }
518 
519             if (modelId == null) {
520                 modelId = getModelId();
521                 source = getSource();
522             }
523 
524             if (line <= 0 && column <= 0 && exception instanceof ModelParserException e) {
525                 line = e.getLineNumber();
526                 column = e.getColumnNumber();
527             }
528 
529             ModelProblem problem =
530                     new DefaultModelProblem(message, severity, version, source, line, column, modelId, exception);
531 
532             add(problem);
533         }
534 
535         public ModelBuilderException newModelBuilderException() {
536             return new ModelBuilderException(result);
537         }
538 
539         public void mergeRepositories(List<Repository> toAdd, boolean replace) {
540             List<RemoteRepository> repos =
541                     toAdd.stream().map(session::createRemoteRepository).toList();
542             if (replace) {
543                 Set<String> ids = repos.stream().map(RemoteRepository::getId).collect(Collectors.toSet());
544                 repositories = repositories.stream()
545                         .filter(r -> !ids.contains(r.getId()))
546                         .toList();
547                 pomRepositories = pomRepositories.stream()
548                         .filter(r -> !ids.contains(r.getId()))
549                         .toList();
550             } else {
551                 Set<String> ids =
552                         pomRepositories.stream().map(RemoteRepository::getId).collect(Collectors.toSet());
553                 repos = repos.stream().filter(r -> !ids.contains(r.getId())).toList();
554             }
555 
556             RepositoryFactory repositoryFactory = session.getService(RepositoryFactory.class);
557             if (request.getRepositoryMerging() == ModelBuilderRequest.RepositoryMerging.REQUEST_DOMINANT) {
558                 repositories = repositoryFactory.aggregate(session, repositories, repos, true);
559                 pomRepositories = repositories;
560             } else {
561                 pomRepositories = repositoryFactory.aggregate(session, pomRepositories, repos, true);
562                 repositories = repositoryFactory.aggregate(session, pomRepositories, externalRepositories, false);
563             }
564         }
565 
566         //
567         // Transform raw model to build pom.
568         // Infer inner reactor dependencies version
569         //
570         Model transformFileToRaw(Model model) {
571             if (model.getDependencies().isEmpty()) {
572                 return model;
573             }
574             List<Dependency> newDeps = new ArrayList<>(model.getDependencies().size());
575             boolean changed = false;
576             for (Dependency dep : model.getDependencies()) {
577                 Dependency newDep = null;
578                 if (dep.getVersion() == null) {
579                     newDep = inferDependencyVersion(model, dep);
580                     if (newDep != null) {
581                         changed = true;
582                     }
583                 }
584                 newDeps.add(newDep == null ? dep : newDep);
585             }
586             return changed ? model.withDependencies(newDeps) : model;
587         }
588 
589         private Dependency inferDependencyVersion(Model model, Dependency dep) {
590             Model depModel = getRawModel(model.getPomFile(), dep.getGroupId(), dep.getArtifactId());
591             if (depModel == null) {
592                 return null;
593             }
594             Dependency.Builder depBuilder = Dependency.newBuilder(dep);
595             String version = depModel.getVersion();
596             InputLocation versionLocation = depModel.getLocation("version");
597             if (version == null && depModel.getParent() != null) {
598                 version = depModel.getParent().getVersion();
599                 versionLocation = depModel.getParent().getLocation("version");
600             }
601             depBuilder.version(version).location("version", versionLocation);
602             if (dep.getGroupId() == null) {
603                 String depGroupId = depModel.getGroupId();
604                 InputLocation groupIdLocation = depModel.getLocation("groupId");
605                 if (depGroupId == null && depModel.getParent() != null) {
606                     depGroupId = depModel.getParent().getGroupId();
607                     groupIdLocation = depModel.getParent().getLocation("groupId");
608                 }
609                 depBuilder.groupId(depGroupId).location("groupId", groupIdLocation);
610             }
611             return depBuilder.build();
612         }
613 
614         String replaceCiFriendlyVersion(Map<String, String> properties, String version) {
615             // TODO: we're using a simple regex here, but we should probably use
616             //       a proper interpolation service to do the replacements
617             //       once one is available in maven-api-impl
618             //       https://issues.apache.org/jira/browse/MNG-8262
619             if (version != null) {
620                 Matcher matcher = REGEX.matcher(version);
621                 if (matcher.find()) {
622                     StringBuilder result = new StringBuilder();
623                     do {
624                         // extract the key inside ${}
625                         String key = matcher.group(1);
626                         // get replacement from the map, or use the original ${xy} if not found
627                         String replacement = properties.getOrDefault(key, "\\" + matcher.group(0));
628                         matcher.appendReplacement(result, replacement);
629                     } while (matcher.find());
630                     matcher.appendTail(result); // Append the remaining part of the string
631                     return result.toString();
632                 }
633             }
634             return version;
635         }
636 
637         private void buildBuildPom() throws ModelBuilderException {
638             // Retrieve and normalize the source path, ensuring it's non-null and in absolute form
639             Path top = request.getSource().getPath();
640             if (top == null) {
641                 throw new IllegalStateException("Recursive build requested but source has no path");
642             }
643             top = top.toAbsolutePath().normalize();
644 
645             // Obtain the root directory, resolving it if necessary
646             Path rootDirectory;
647             try {
648                 rootDirectory = session.getRootDirectory();
649             } catch (IllegalStateException e) {
650                 rootDirectory = session.getService(RootLocator.class).findMandatoryRoot(top);
651             }
652 
653             // Locate and normalize the root POM if it exists, fallback to top otherwise
654             Path root = modelProcessor.locateExistingPom(rootDirectory);
655             if (root != null) {
656                 root = root.toAbsolutePath().normalize();
657             } else {
658                 root = top;
659             }
660 
661             // Load all models starting from the root
662             loadFromRoot(root, top);
663 
664             // Check for errors after loading models
665             if (hasErrors()) {
666                 throw newModelBuilderException();
667             }
668 
669             // For the top model and all its children, build the effective model.
670             // This is done through the phased executor
671             var allResults = results(result).toList();
672             List<RuntimeException> exceptions = new CopyOnWriteArrayList<>();
673             try (PhasingExecutor executor = createExecutor()) {
674                 for (DefaultModelBuilderResult r : allResults) {
675                     executor.execute(() -> {
676                         ModelBuilderSessionState mbs = derive(r.getSource(), r);
677                         try {
678                             mbs.buildEffectiveModel(new LinkedHashSet<>());
679                         } catch (ModelBuilderException e) {
680                             // gathered with problem collector
681                         } catch (RuntimeException t) {
682                             exceptions.add(t);
683                         }
684                     });
685                 }
686             }
687 
688             // Check for errors again after execution
689             if (exceptions.size() == 1) {
690                 throw exceptions.get(0);
691             } else if (exceptions.size() > 1) {
692                 MavenException fatalException = new MavenException("Multiple fatal exceptions occurred");
693                 exceptions.forEach(fatalException::addSuppressed);
694                 throw fatalException;
695             } else if (hasErrors()) {
696                 throw newModelBuilderException();
697             }
698         }
699 
700         /**
701          * Generates a stream of DefaultModelBuilderResult objects, starting with the provided
702          * result and recursively including all its child results.
703          *
704          * @param r The initial DefaultModelBuilderResult object from which to generate the stream.
705          * @return A Stream of DefaultModelBuilderResult objects, starting with the provided result
706          *         and including all its child results.
707          */
708         Stream<DefaultModelBuilderResult> results(DefaultModelBuilderResult r) {
709             return Stream.concat(Stream.of(r), r.getChildren().stream().flatMap(this::results));
710         }
711 
712         private void loadFromRoot(Path root, Path top) {
713             try (PhasingExecutor executor = createExecutor()) {
714                 DefaultModelBuilderResult r = Objects.equals(top, root) ? result : new DefaultModelBuilderResult();
715                 loadFilePom(executor, top, root, Set.of(), r);
716             }
717             if (result.getFileModel() == null && !Objects.equals(top, root)) {
718                 logger.warn(
719                         "The top project ({}) cannot be found in the reactor from root project ({}). "
720                                 + "Make sure the root directory is correct (a missing '.mvn' directory in the root "
721                                 + "project is the most common cause) and the project is correctly included "
722                                 + "in the reactor (missing activated profiles, command line options, etc.). For this "
723                                 + "build, the top project will be used as the root project.",
724                         top,
725                         root);
726                 cache.clear();
727                 mappedSources.clear();
728                 loadFromRoot(top, top);
729             }
730         }
731 
732         private void loadFilePom(
733                 Executor executor, Path top, Path pom, Set<Path> parents, DefaultModelBuilderResult r) {
734             try {
735                 Path pomDirectory = Files.isDirectory(pom) ? pom : pom.getParent();
736                 ModelSource src = ModelSource.fromPath(pom);
737                 Model model = derive(src, r).readFileModel();
738                 // keep all loaded file models in memory, those will be needed
739                 // during the raw to build transformation
740                 putSource(getGroupId(model), model.getArtifactId(), src);
741                 Model activated = activateFileModel(model);
742                 for (String subproject : getSubprojects(activated)) {
743                     if (subproject == null || subproject.isEmpty()) {
744                         continue;
745                     }
746 
747                     subproject = subproject.replace('\\', File.separatorChar).replace('/', File.separatorChar);
748 
749                     Path rawSubprojectFile = modelProcessor.locateExistingPom(pomDirectory.resolve(subproject));
750 
751                     if (rawSubprojectFile == null) {
752                         ModelProblem problem = new DefaultModelProblem(
753                                 "Child subproject " + subproject + " of " + pomDirectory + " does not exist",
754                                 Severity.ERROR,
755                                 Version.BASE,
756                                 model,
757                                 -1,
758                                 -1,
759                                 null);
760                         r.addProblem(problem);
761                         continue;
762                     }
763 
764                     Path subprojectFile = rawSubprojectFile.toAbsolutePath().normalize();
765 
766                     if (parents.contains(subprojectFile)) {
767                         StringBuilder buffer = new StringBuilder(256);
768                         for (Path aggregatorFile : parents) {
769                             buffer.append(aggregatorFile).append(" -> ");
770                         }
771                         buffer.append(subprojectFile);
772 
773                         ModelProblem problem = new DefaultModelProblem(
774                                 "Child subproject " + subprojectFile + " of " + pom + " forms aggregation cycle "
775                                         + buffer,
776                                 Severity.ERROR,
777                                 Version.BASE,
778                                 model,
779                                 -1,
780                                 -1,
781                                 null);
782                         r.addProblem(problem);
783                         continue;
784                     }
785 
786                     DefaultModelBuilderResult cr =
787                             Objects.equals(top, subprojectFile) ? result : new DefaultModelBuilderResult(r);
788                     if (request.isRecursive()) {
789                         r.getChildren().add(cr);
790                     }
791 
792                     executor.execute(() -> loadFilePom(executor, top, subprojectFile, concat(parents, pom), cr));
793                 }
794             } catch (ModelBuilderException e) {
795                 // gathered with problem collector
796                 add(Severity.ERROR, Version.V40, "Failed to load project " + pom, e);
797             }
798             if (r != result) {
799                 r.getProblems().forEach(result::addProblem);
800             }
801         }
802 
803         static <T> Set<T> concat(Set<T> a, T b) {
804             Set<T> result = new HashSet<>(a);
805             result.add(b);
806             return Set.copyOf(result);
807         }
808 
809         void buildEffectiveModel(Collection<String> importIds) throws ModelBuilderException {
810             Model resultModel = readEffectiveModel();
811             setSource(resultModel);
812             setRootModel(resultModel);
813 
814             // model path translation
815             resultModel =
816                     modelPathTranslator.alignToBaseDirectory(resultModel, resultModel.getProjectDirectory(), request);
817 
818             // plugin management injection
819             resultModel = pluginManagementInjector.injectManagement(resultModel, request, this);
820 
821             // lifecycle bindings injection
822             if (request.getRequestType() != ModelBuilderRequest.RequestType.CONSUMER_DEPENDENCY) {
823                 org.apache.maven.api.services.ModelTransformer lifecycleBindingsInjector =
824                         request.getLifecycleBindingsInjector();
825                 if (lifecycleBindingsInjector != null) {
826                     resultModel = lifecycleBindingsInjector.transform(resultModel, request, this);
827                 }
828             }
829 
830             // dependency management import
831             resultModel = importDependencyManagement(resultModel, importIds);
832 
833             // dependency management injection
834             resultModel = dependencyManagementInjector.injectManagement(resultModel, request, this);
835 
836             resultModel = modelNormalizer.injectDefaultValues(resultModel, request, this);
837 
838             if (request.getRequestType() != ModelBuilderRequest.RequestType.CONSUMER_DEPENDENCY) {
839                 // plugins configuration
840                 resultModel = pluginConfigurationExpander.expandPluginConfiguration(resultModel, request, this);
841             }
842 
843             for (var transformer : transformers) {
844                 resultModel = transformer.transformEffectiveModel(resultModel);
845             }
846 
847             result.setEffectiveModel(resultModel);
848             // Set the default relative path for the parent in the file model
849             if (result.getFileModel().getParent() != null
850                     && result.getFileModel().getParent().getRelativePath() == null) {
851                 result.setFileModel(result.getFileModel()
852                         .withParent(result.getFileModel()
853                                 .getParent()
854                                 .withRelativePath(resultModel.getParent().getRelativePath())));
855             }
856 
857             // effective model validation
858             modelValidator.validateEffectiveModel(
859                     resultModel,
860                     isBuildRequest() ? ModelValidator.VALIDATION_LEVEL_STRICT : ModelValidator.VALIDATION_LEVEL_MINIMAL,
861                     this);
862 
863             if (hasErrors()) {
864                 throw newModelBuilderException();
865             }
866         }
867 
868         Model readParent(Model childModel, DefaultProfileActivationContext profileActivationContext) {
869             Model parentModel;
870 
871             Parent parent = childModel.getParent();
872             if (parent != null) {
873                 parentModel = resolveParent(childModel, profileActivationContext);
874 
875                 if (!"pom".equals(parentModel.getPackaging())) {
876                     add(
877                             Severity.ERROR,
878                             Version.BASE,
879                             "Invalid packaging for parent POM " + ModelProblemUtils.toSourceHint(parentModel)
880                                     + ", must be \"pom\" but is \"" + parentModel.getPackaging() + "\"",
881                             parentModel.getLocation("packaging"));
882                 }
883                 result.setParentModel(parentModel);
884             } else {
885                 String superModelVersion = childModel.getModelVersion();
886                 if (superModelVersion == null || !VALID_MODEL_VERSIONS.contains(superModelVersion)) {
887                     // Maven 3.x is always using 4.0.0 version to load the supermodel, so
888                     // do the same when loading a dependency.  The model validator will also
889                     // check that field later.
890                     superModelVersion = MODEL_VERSION_4_0_0;
891                 }
892                 parentModel = getSuperModel(superModelVersion);
893             }
894 
895             return parentModel;
896         }
897 
898         private Model resolveParent(Model childModel, DefaultProfileActivationContext profileActivationContext)
899                 throws ModelBuilderException {
900             Model parentModel = null;
901             if (isBuildRequest()) {
902                 parentModel = readParentLocally(childModel, profileActivationContext);
903             }
904             if (parentModel == null) {
905                 parentModel = resolveAndReadParentExternally(childModel, profileActivationContext);
906             }
907             return parentModel;
908         }
909 
910         private Model readParentLocally(Model childModel, DefaultProfileActivationContext profileActivationContext)
911                 throws ModelBuilderException {
912             ModelSource candidateSource;
913 
914             Parent parent = childModel.getParent();
915             String parentPath = parent.getRelativePath();
916             if (request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_PROJECT) {
917                 if (parentPath != null && !parentPath.isEmpty()) {
918                     candidateSource = request.getSource().resolve(modelProcessor::locateExistingPom, parentPath);
919                     if (candidateSource == null) {
920                         wrongParentRelativePath(childModel);
921                         return null;
922                     }
923                 } else {
924                     candidateSource =
925                             resolveReactorModel(parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
926                     if (candidateSource == null && parentPath == null) {
927                         candidateSource = request.getSource().resolve(modelProcessor::locateExistingPom, "..");
928                     }
929                 }
930             } else {
931                 candidateSource = resolveReactorModel(parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
932                 if (candidateSource == null) {
933                     if (parentPath == null) {
934                         parentPath = "..";
935                     }
936                     if (!parentPath.isEmpty()) {
937                         candidateSource = request.getSource().resolve(modelProcessor::locateExistingPom, parentPath);
938                     }
939                 }
940             }
941 
942             if (candidateSource == null) {
943                 return null;
944             }
945 
946             ModelBuilderSessionState derived = derive(candidateSource);
947             Model candidateModel = derived.readAsParentModel(profileActivationContext);
948             addActivePomProfiles(derived.result.getActivePomProfiles());
949 
950             String groupId = getGroupId(candidateModel);
951             String artifactId = candidateModel.getArtifactId();
952             String version = getVersion(candidateModel);
953 
954             // Ensure that relative path and GA match, if both are provided
955             if (groupId == null
956                     || !groupId.equals(parent.getGroupId())
957                     || artifactId == null
958                     || !artifactId.equals(parent.getArtifactId())) {
959                 mismatchRelativePathAndGA(childModel, groupId, artifactId);
960                 return null;
961             }
962 
963             if (version != null && parent.getVersion() != null && !version.equals(parent.getVersion())) {
964                 try {
965                     VersionRange parentRange = versionParser.parseVersionRange(parent.getVersion());
966                     if (!parentRange.contains(versionParser.parseVersion(version))) {
967                         // version skew drop back to resolution from the repository
968                         return null;
969                     }
970 
971                     // Validate versions aren't inherited when using parent ranges the same way as when read externally.
972                     String rawChildModelVersion = childModel.getVersion();
973 
974                     if (rawChildModelVersion == null) {
975                         // Message below is checked for in the MNG-2199 core IT.
976                         add(Severity.FATAL, Version.V31, "Version must be a constant", childModel.getLocation(""));
977 
978                     } else {
979                         if (rawChildVersionReferencesParent(rawChildModelVersion)) {
980                             // Message below is checked for in the MNG-2199 core IT.
981                             add(
982                                     Severity.FATAL,
983                                     Version.V31,
984                                     "Version must be a constant",
985                                     childModel.getLocation("version"));
986                         }
987                     }
988 
989                     // MNG-2199: What else to check here ?
990                 } catch (VersionParserException e) {
991                     // invalid version range, so drop back to resolution from the repository
992                     return null;
993                 }
994             }
995             return candidateModel;
996         }
997 
998         private void mismatchRelativePathAndGA(Model childModel, String groupId, String artifactId) {
999             Parent parent = childModel.getParent();
1000             StringBuilder buffer = new StringBuilder(256);
1001             buffer.append("'parent.relativePath'");
1002             if (childModel != getRootModel()) {
1003                 buffer.append(" of POM ").append(ModelProblemUtils.toSourceHint(childModel));
1004             }
1005             buffer.append(" points at ").append(groupId).append(':').append(artifactId);
1006             buffer.append(" instead of ").append(parent.getGroupId()).append(':');
1007             buffer.append(parent.getArtifactId()).append(", please verify your project structure");
1008 
1009             setSource(childModel);
1010             boolean warn = MODEL_VERSION_4_0_0.equals(childModel.getModelVersion())
1011                     || childModel.getParent().getRelativePath() == null;
1012             add(warn ? Severity.WARNING : Severity.FATAL, Version.BASE, buffer.toString(), parent.getLocation(""));
1013         }
1014 
1015         private void wrongParentRelativePath(Model childModel) {
1016             Parent parent = childModel.getParent();
1017             String parentPath = parent.getRelativePath();
1018             StringBuilder buffer = new StringBuilder(256);
1019             buffer.append("'parent.relativePath'");
1020             if (childModel != getRootModel()) {
1021                 buffer.append(" of POM ").append(ModelProblemUtils.toSourceHint(childModel));
1022             }
1023             buffer.append(" points at '").append(parentPath);
1024             buffer.append("' but no POM could be found, please verify your project structure");
1025 
1026             setSource(childModel);
1027             add(Severity.FATAL, Version.BASE, buffer.toString(), parent.getLocation(""));
1028         }
1029 
1030         Model resolveAndReadParentExternally(Model childModel, DefaultProfileActivationContext profileActivationContext)
1031                 throws ModelBuilderException {
1032             ModelBuilderRequest request = this.request;
1033             setSource(childModel);
1034 
1035             Parent parent = childModel.getParent();
1036 
1037             String groupId = parent.getGroupId();
1038             String artifactId = parent.getArtifactId();
1039             String version = parent.getVersion();
1040 
1041             // add repositories specified by the current model so that we can resolve the parent
1042             if (!childModel.getRepositories().isEmpty()) {
1043                 var previousRepositories = repositories;
1044                 mergeRepositories(childModel.getRepositories(), false);
1045                 if (!Objects.equals(previousRepositories, repositories)) {
1046                     if (logger.isDebugEnabled()) {
1047                         logger.debug("Merging repositories from " + childModel.getId() + "\n"
1048                                 + repositories.stream()
1049                                         .map(Object::toString)
1050                                         .collect(Collectors.joining("\n", "    ", "")));
1051                     }
1052                 }
1053             }
1054 
1055             ModelSource modelSource;
1056             try {
1057                 modelSource = resolveReactorModel(parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
1058                 if (modelSource == null) {
1059                     AtomicReference<Parent> modified = new AtomicReference<>();
1060                     modelSource = modelResolver.resolveModel(request.getSession(), repositories, parent, modified);
1061                     if (modified.get() != null) {
1062                         parent = modified.get();
1063                     }
1064                 }
1065             } catch (ModelResolverException e) {
1066                 // Message below is checked for in the MNG-2199 core IT.
1067                 StringBuilder buffer = new StringBuilder(256);
1068                 buffer.append("Non-resolvable parent POM");
1069                 if (!containsCoordinates(e.getMessage(), groupId, artifactId, version)) {
1070                     buffer.append(' ').append(ModelProblemUtils.toId(groupId, artifactId, version));
1071                 }
1072                 if (childModel != getRootModel()) {
1073                     buffer.append(" for ").append(ModelProblemUtils.toId(childModel));
1074                 }
1075                 buffer.append(": ").append(e.getMessage());
1076                 if (request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_PROJECT) {
1077                     buffer.append(" and parent could not be found in reactor");
1078                 }
1079 
1080                 add(Severity.FATAL, Version.BASE, buffer.toString(), parent.getLocation(""), e);
1081                 throw newModelBuilderException();
1082             }
1083 
1084             ModelBuilderRequest lenientRequest = ModelBuilderRequest.builder(request)
1085                     .requestType(ModelBuilderRequest.RequestType.CONSUMER_PARENT)
1086                     .source(modelSource)
1087                     .build();
1088 
1089             Model parentModel = derive(lenientRequest).readAsParentModel(profileActivationContext);
1090 
1091             if (!parent.getVersion().equals(version)) {
1092                 String rawChildModelVersion = childModel.getVersion();
1093 
1094                 if (rawChildModelVersion == null) {
1095                     // Message below is checked for in the MNG-2199 core IT.
1096                     add(Severity.FATAL, Version.V31, "Version must be a constant", childModel.getLocation(""));
1097                 } else {
1098                     if (rawChildVersionReferencesParent(rawChildModelVersion)) {
1099                         // Message below is checked for in the MNG-2199 core IT.
1100                         add(
1101                                 Severity.FATAL,
1102                                 Version.V31,
1103                                 "Version must be a constant",
1104                                 childModel.getLocation("version"));
1105                     }
1106                 }
1107 
1108                 // MNG-2199: What else to check here ?
1109             }
1110 
1111             return parentModel;
1112         }
1113 
1114         Model activateFileModel(Model inputModel) throws ModelBuilderException {
1115             setRootModel(inputModel);
1116 
1117             // profile activation
1118             DefaultProfileActivationContext profileActivationContext = getProfileActivationContext(request, inputModel);
1119 
1120             setSource("(external profiles)");
1121             List<Profile> activeExternalProfiles = getActiveProfiles(request.getProfiles(), profileActivationContext);
1122 
1123             result.setActiveExternalProfiles(activeExternalProfiles);
1124 
1125             if (!activeExternalProfiles.isEmpty()) {
1126                 Map<String, String> profileProps = new HashMap<>();
1127                 for (Profile profile : activeExternalProfiles) {
1128                     profileProps.putAll(profile.getProperties());
1129                 }
1130                 profileProps.putAll(request.getUserProperties());
1131                 profileActivationContext.setUserProperties(profileProps);
1132             }
1133 
1134             profileActivationContext.setModel(inputModel);
1135             setSource(inputModel);
1136             List<Profile> activePomProfiles = getActiveProfiles(inputModel.getProfiles(), profileActivationContext);
1137 
1138             // model normalization
1139             setSource(inputModel);
1140             inputModel = modelNormalizer.mergeDuplicates(inputModel, request, this);
1141 
1142             Map<String, Activation> interpolatedActivations = getProfileActivations(inputModel);
1143             inputModel = injectProfileActivations(inputModel, interpolatedActivations);
1144 
1145             // profile injection
1146             inputModel = profileInjector.injectProfiles(inputModel, activePomProfiles, request, this);
1147             inputModel = profileInjector.injectProfiles(inputModel, activeExternalProfiles, request, this);
1148 
1149             return inputModel;
1150         }
1151 
1152         @SuppressWarnings("checkstyle:methodlength")
1153         private Model readEffectiveModel() throws ModelBuilderException {
1154             Model inputModel = readRawModel();
1155             if (hasFatalErrors()) {
1156                 throw newModelBuilderException();
1157             }
1158 
1159             setRootModel(inputModel);
1160 
1161             Model activatedFileModel = activateFileModel(inputModel);
1162 
1163             // profile activation
1164             DefaultProfileActivationContext profileActivationContext =
1165                     getProfileActivationContext(request, activatedFileModel);
1166 
1167             List<Profile> activeExternalProfiles = result.getActiveExternalProfiles();
1168 
1169             if (!activeExternalProfiles.isEmpty()) {
1170                 Map<String, String> profileProps = new HashMap<>();
1171                 for (Profile profile : activeExternalProfiles) {
1172                     profileProps.putAll(profile.getProperties());
1173                 }
1174                 profileProps.putAll(request.getUserProperties());
1175                 profileActivationContext.setUserProperties(profileProps);
1176             }
1177 
1178             Model parentModel = readParent(activatedFileModel, profileActivationContext);
1179 
1180             // Now that we have read the parent, we can set the relative
1181             // path correctly if it was not set in the input model
1182             if (inputModel.getParent() != null && inputModel.getParent().getRelativePath() == null) {
1183                 String relPath;
1184                 if (parentModel.getPomFile() != null && isBuildRequest()) {
1185                     relPath = inputModel
1186                             .getPomFile()
1187                             .getParent()
1188                             .toAbsolutePath()
1189                             .relativize(
1190                                     parentModel.getPomFile().toAbsolutePath().getParent())
1191                             .toString();
1192                 } else {
1193                     relPath = "..";
1194                 }
1195                 inputModel = inputModel.withParent(inputModel.getParent().withRelativePath(relPath));
1196             }
1197 
1198             Model model = inheritanceAssembler.assembleModelInheritance(inputModel, parentModel, request, this);
1199 
1200             // model normalization
1201             model = modelNormalizer.mergeDuplicates(model, request, this);
1202 
1203             // profile activation
1204             profileActivationContext.setModel(model);
1205 
1206             List<Profile> interpolatedProfiles =
1207                     interpolateActivations(model.getProfiles(), profileActivationContext, this);
1208 
1209             // profile injection
1210             List<Profile> activePomProfiles = getActiveProfiles(interpolatedProfiles, profileActivationContext);
1211             model = profileInjector.injectProfiles(model, activePomProfiles, request, this);
1212             model = profileInjector.injectProfiles(model, activeExternalProfiles, request, this);
1213 
1214             addActivePomProfiles(activePomProfiles);
1215 
1216             // model interpolation
1217             Model resultModel = model;
1218             resultModel = interpolateModel(resultModel, request, this);
1219 
1220             // url normalization
1221             resultModel = modelUrlNormalizer.normalize(resultModel, request);
1222 
1223             // Now the fully interpolated model is available: reconfigure the resolver
1224             if (!resultModel.getRepositories().isEmpty()) {
1225                 List<String> oldRepos =
1226                         repositories.stream().map(Object::toString).toList();
1227                 mergeRepositories(resultModel.getRepositories(), true);
1228                 List<String> newRepos =
1229                         repositories.stream().map(Object::toString).toList();
1230                 if (!Objects.equals(oldRepos, newRepos)) {
1231                     logger.debug("Replacing repositories from " + resultModel.getId() + "\n"
1232                             + newRepos.stream().map(s -> "    " + s).collect(Collectors.joining("\n")));
1233                 }
1234             }
1235 
1236             return resultModel;
1237         }
1238 
1239         private void addActivePomProfiles(List<Profile> activePomProfiles) {
1240             if (activePomProfiles != null) {
1241                 if (result.getActivePomProfiles() == null) {
1242                     result.setActivePomProfiles(new ArrayList<>());
1243                 }
1244                 result.getActivePomProfiles().addAll(activePomProfiles);
1245             }
1246         }
1247 
1248         private List<Profile> getActiveProfiles(
1249                 Collection<Profile> interpolatedProfiles, DefaultProfileActivationContext profileActivationContext) {
1250             if (isBuildRequestWithActivation()) {
1251                 return profileSelector.getActiveProfiles(interpolatedProfiles, profileActivationContext, this);
1252             } else {
1253                 return List.of();
1254             }
1255         }
1256 
1257         Model readFileModel() throws ModelBuilderException {
1258             Model model = cache(request.getSource(), FILE, this::doReadFileModel);
1259             // set the file model in the result outside the cache
1260             result.setFileModel(model);
1261             return model;
1262         }
1263 
1264         @SuppressWarnings("checkstyle:methodlength")
1265         Model doReadFileModel() throws ModelBuilderException {
1266             ModelSource modelSource = request.getSource();
1267             Model model;
1268             Path rootDirectory;
1269             setSource(modelSource.getLocation());
1270             logger.debug("Reading file model from " + modelSource.getLocation());
1271             try {
1272                 boolean strict = isBuildRequest();
1273                 try {
1274                     rootDirectory = request.getSession().getRootDirectory();
1275                 } catch (IllegalStateException ignore) {
1276                     rootDirectory = modelSource.getPath();
1277                     while (rootDirectory != null && !Files.isDirectory(rootDirectory)) {
1278                         rootDirectory = rootDirectory.getParent();
1279                     }
1280                 }
1281                 try (InputStream is = modelSource.openStream()) {
1282                     model = modelProcessor.read(XmlReaderRequest.builder()
1283                             .strict(strict)
1284                             .location(modelSource.getLocation())
1285                             .path(modelSource.getPath())
1286                             .rootDirectory(rootDirectory)
1287                             .inputStream(is)
1288                             .build());
1289                 } catch (XmlReaderException e) {
1290                     if (!strict) {
1291                         throw e;
1292                     }
1293                     try (InputStream is = modelSource.openStream()) {
1294                         model = modelProcessor.read(XmlReaderRequest.builder()
1295                                 .strict(false)
1296                                 .location(modelSource.getLocation())
1297                                 .path(modelSource.getPath())
1298                                 .rootDirectory(rootDirectory)
1299                                 .inputStream(is)
1300                                 .build());
1301                     } catch (XmlReaderException ne) {
1302                         // still unreadable even in non-strict mode, rethrow original error
1303                         throw e;
1304                     }
1305 
1306                     add(
1307                             Severity.ERROR,
1308                             Version.V20,
1309                             "Malformed POM " + modelSource.getLocation() + ": " + e.getMessage(),
1310                             e);
1311                 }
1312 
1313                 InputLocation loc = model.getLocation("");
1314                 InputSource v4src = loc != null ? loc.getSource() : null;
1315                 if (v4src != null) {
1316                     try {
1317                         Field field = InputSource.class.getDeclaredField("modelId");
1318                         field.setAccessible(true);
1319                         field.set(v4src, ModelProblemUtils.toId(model));
1320                     } catch (Throwable t) {
1321                         // TODO: use a lazy source ?
1322                         throw new IllegalStateException("Unable to set modelId on InputSource", t);
1323                     }
1324                 }
1325             } catch (XmlReaderException e) {
1326                 add(
1327                         Severity.FATAL,
1328                         Version.BASE,
1329                         "Non-parseable POM " + modelSource.getLocation() + ": " + e.getMessage(),
1330                         e);
1331                 throw newModelBuilderException();
1332             } catch (IOException e) {
1333                 String msg = e.getMessage();
1334                 if (msg == null || msg.isEmpty()) {
1335                     // NOTE: There's java.nio.charset.MalformedInputException and sun.io.MalformedInputException
1336                     if (e.getClass().getName().endsWith("MalformedInputException")) {
1337                         msg = "Some input bytes do not match the file encoding.";
1338                     } else {
1339                         msg = e.getClass().getSimpleName();
1340                     }
1341                 }
1342                 add(Severity.FATAL, Version.BASE, "Non-readable POM " + modelSource.getLocation() + ": " + msg, e);
1343                 throw newModelBuilderException();
1344             }
1345 
1346             if (model.getModelVersion() == null) {
1347                 String namespace = model.getNamespaceUri();
1348                 if (namespace != null && namespace.startsWith(NAMESPACE_PREFIX)) {
1349                     model = model.withModelVersion(namespace.substring(NAMESPACE_PREFIX.length()));
1350                 }
1351             }
1352 
1353             if (isBuildRequest()) {
1354                 model = model.withPomFile(modelSource.getPath());
1355 
1356                 Parent parent = model.getParent();
1357                 if (parent != null) {
1358                     String groupId = parent.getGroupId();
1359                     String artifactId = parent.getArtifactId();
1360                     String version = parent.getVersion();
1361                     String path = parent.getRelativePath();
1362                     if ((groupId == null || artifactId == null || version == null)
1363                             && (path == null || !path.isEmpty())) {
1364                         Path pomFile = model.getPomFile();
1365                         Path relativePath = Paths.get(path != null ? path : "..");
1366                         Path pomPath = pomFile.resolveSibling(relativePath).normalize();
1367                         if (Files.isDirectory(pomPath)) {
1368                             pomPath = modelProcessor.locateExistingPom(pomPath);
1369                         }
1370                         if (pomPath != null && Files.isRegularFile(pomPath)) {
1371                             Model parentModel =
1372                                     derive(ModelSource.fromPath(pomPath)).readFileModel();
1373                             String parentGroupId = getGroupId(parentModel);
1374                             String parentArtifactId = parentModel.getArtifactId();
1375                             String parentVersion = getVersion(parentModel);
1376                             if ((groupId == null || groupId.equals(parentGroupId))
1377                                     && (artifactId == null || artifactId.equals(parentArtifactId))
1378                                     && (version == null || version.equals(parentVersion))) {
1379                                 model = model.withParent(parent.with()
1380                                         .groupId(parentGroupId)
1381                                         .artifactId(parentArtifactId)
1382                                         .version(parentVersion)
1383                                         .build());
1384                             } else {
1385                                 mismatchRelativePathAndGA(model, parentGroupId, parentArtifactId);
1386                             }
1387                         } else {
1388                             if (!MODEL_VERSION_4_0_0.equals(model.getModelVersion()) && path != null) {
1389                                 wrongParentRelativePath(model);
1390                             }
1391                         }
1392                     }
1393                 }
1394 
1395                 // subprojects discovery
1396                 if (getSubprojects(model).isEmpty()
1397                         // only discover subprojects if POM > 4.0.0
1398                         && !MODEL_VERSION_4_0_0.equals(model.getModelVersion())
1399                         // and if packaging is POM (we check type, but the session is not yet available,
1400                         // we would require the project realm if we want to support extensions
1401                         && Type.POM.equals(model.getPackaging())) {
1402                     List<String> subprojects = new ArrayList<>();
1403                     try (Stream<Path> files = Files.list(model.getProjectDirectory())) {
1404                         for (Path f : files.toList()) {
1405                             if (Files.isDirectory(f)) {
1406                                 Path subproject = modelProcessor.locateExistingPom(f);
1407                                 if (subproject != null) {
1408                                     subprojects.add(f.getFileName().toString());
1409                                 }
1410                             }
1411                         }
1412                         if (!subprojects.isEmpty()) {
1413                             model = model.withSubprojects(subprojects);
1414                         }
1415                     } catch (IOException e) {
1416                         add(Severity.FATAL, Version.V41, "Error discovering subprojects", e);
1417                     }
1418                 }
1419 
1420                 // CI friendly version
1421                 // All expressions are interpolated using user properties and properties
1422                 // defined on the root project.
1423                 Map<String, String> properties = new HashMap<>();
1424                 if (!Objects.equals(rootDirectory, model.getProjectDirectory())) {
1425                     Path rootModelPath = modelProcessor.locateExistingPom(rootDirectory);
1426                     if (rootModelPath != null) {
1427                         Model rootModel =
1428                                 derive(ModelSource.fromPath(rootModelPath)).readFileModel();
1429                         properties.putAll(rootModel.getProperties());
1430                     }
1431                 } else {
1432                     properties.putAll(model.getProperties());
1433                 }
1434                 properties.putAll(session.getUserProperties());
1435                 model = model.with()
1436                         .version(replaceCiFriendlyVersion(properties, model.getVersion()))
1437                         .parent(
1438                                 model.getParent() != null
1439                                         ? model.getParent()
1440                                                 .withVersion(replaceCiFriendlyVersion(
1441                                                         properties,
1442                                                         model.getParent().getVersion()))
1443                                         : null)
1444                         .build();
1445             }
1446 
1447             for (var transformer : transformers) {
1448                 model = transformer.transformFileModel(model);
1449             }
1450 
1451             setSource(model);
1452             modelValidator.validateFileModel(
1453                     model,
1454                     isBuildRequest() ? ModelValidator.VALIDATION_LEVEL_STRICT : ModelValidator.VALIDATION_LEVEL_MINIMAL,
1455                     this);
1456             if (hasFatalErrors()) {
1457                 throw newModelBuilderException();
1458             }
1459 
1460             return model;
1461         }
1462 
1463         Model readRawModel() throws ModelBuilderException {
1464             // ensure file model is available
1465             readFileModel();
1466             Model model = cache(request.getSource(), RAW, this::doReadRawModel);
1467             // set the raw model in the result outside the cache
1468             result.setRawModel(model);
1469             return model;
1470         }
1471 
1472         private Model doReadRawModel() throws ModelBuilderException {
1473             Model rawModel = readFileModel();
1474 
1475             if (!MODEL_VERSION_4_0_0.equals(rawModel.getModelVersion()) && isBuildRequest()) {
1476                 rawModel = transformFileToRaw(rawModel);
1477             }
1478 
1479             for (var transformer : transformers) {
1480                 rawModel = transformer.transformRawModel(rawModel);
1481             }
1482 
1483             modelValidator.validateRawModel(
1484                     rawModel,
1485                     isBuildRequest() ? ModelValidator.VALIDATION_LEVEL_STRICT : ModelValidator.VALIDATION_LEVEL_MINIMAL,
1486                     this);
1487 
1488             if (hasFatalErrors()) {
1489                 throw newModelBuilderException();
1490             }
1491 
1492             return rawModel;
1493         }
1494 
1495         /**
1496          * Reads the request source's parent.
1497          */
1498         Model readAsParentModel(DefaultProfileActivationContext profileActivationContext) throws ModelBuilderException {
1499             Map<DefaultProfileActivationContext.Record, Model> parentsPerContext =
1500                     cache(request.getSource(), PARENT, ConcurrentHashMap::new);
1501             for (Map.Entry<DefaultProfileActivationContext.Record, Model> e : parentsPerContext.entrySet()) {
1502                 if (e.getKey().matches(profileActivationContext)) {
1503                     return e.getValue();
1504                 }
1505             }
1506             DefaultProfileActivationContext.Record prev = profileActivationContext.start();
1507             Model model = doReadAsParentModel(profileActivationContext);
1508             DefaultProfileActivationContext.Record record = profileActivationContext.stop(prev);
1509             parentsPerContext.put(record, model);
1510             return model;
1511         }
1512 
1513         private Model doReadAsParentModel(DefaultProfileActivationContext profileActivationContext)
1514                 throws ModelBuilderException {
1515             Model raw = readRawModel();
1516             Model parentData = readParent(raw, profileActivationContext);
1517             Model parent = new DefaultInheritanceAssembler(new DefaultInheritanceAssembler.InheritanceModelMerger() {
1518                         @Override
1519                         protected void mergeModel_Modules(
1520                                 Model.Builder builder,
1521                                 Model target,
1522                                 Model source,
1523                                 boolean sourceDominant,
1524                                 Map<Object, Object> context) {}
1525 
1526                         @Override
1527                         protected void mergeModel_Subprojects(
1528                                 Model.Builder builder,
1529                                 Model target,
1530                                 Model source,
1531                                 boolean sourceDominant,
1532                                 Map<Object, Object> context) {}
1533                     })
1534                     .assembleModelInheritance(raw, parentData, request, this);
1535 
1536             // activate profiles
1537             List<Profile> parentInterpolatedProfiles =
1538                     interpolateActivations(parent.getProfiles(), profileActivationContext, this);
1539             // profile injection
1540             List<Profile> parentActivePomProfiles =
1541                     getActiveProfiles(parentInterpolatedProfiles, profileActivationContext);
1542             Model injectedParentModel = profileInjector
1543                     .injectProfiles(parent, parentActivePomProfiles, request, this)
1544                     .withProfiles(List.of());
1545             addActivePomProfiles(parentActivePomProfiles);
1546 
1547             return injectedParentModel.withParent(null);
1548         }
1549 
1550         private Model importDependencyManagement(Model model, Collection<String> importIds) {
1551             DependencyManagement depMgmt = model.getDependencyManagement();
1552 
1553             if (depMgmt == null) {
1554                 return model;
1555             }
1556 
1557             String importing = model.getGroupId() + ':' + model.getArtifactId() + ':' + model.getVersion();
1558 
1559             importIds.add(importing);
1560 
1561             List<DependencyManagement> importMgmts = null;
1562 
1563             List<Dependency> deps = new ArrayList<>(depMgmt.getDependencies());
1564             for (Iterator<Dependency> it = deps.iterator(); it.hasNext(); ) {
1565                 Dependency dependency = it.next();
1566 
1567                 if (!("pom".equals(dependency.getType()) && "import".equals(dependency.getScope()))
1568                         || "bom".equals(dependency.getType())) {
1569                     continue;
1570                 }
1571 
1572                 it.remove();
1573 
1574                 DependencyManagement importMgmt = loadDependencyManagement(dependency, importIds);
1575 
1576                 if (importMgmt != null) {
1577                     if (importMgmts == null) {
1578                         importMgmts = new ArrayList<>();
1579                     }
1580 
1581                     importMgmts.add(importMgmt);
1582                 }
1583             }
1584 
1585             importIds.remove(importing);
1586 
1587             model = model.withDependencyManagement(
1588                     model.getDependencyManagement().withDependencies(deps));
1589 
1590             return dependencyManagementImporter.importManagement(model, importMgmts, request, this);
1591         }
1592 
1593         private DependencyManagement loadDependencyManagement(Dependency dependency, Collection<String> importIds) {
1594             String groupId = dependency.getGroupId();
1595             String artifactId = dependency.getArtifactId();
1596             String version = dependency.getVersion();
1597 
1598             if (groupId == null || groupId.isEmpty()) {
1599                 add(
1600                         Severity.ERROR,
1601                         Version.BASE,
1602                         "'dependencyManagement.dependencies.dependency.groupId' for " + dependency.getManagementKey()
1603                                 + " is missing.",
1604                         dependency.getLocation(""));
1605                 return null;
1606             }
1607             if (artifactId == null || artifactId.isEmpty()) {
1608                 add(
1609                         Severity.ERROR,
1610                         Version.BASE,
1611                         "'dependencyManagement.dependencies.dependency.artifactId' for " + dependency.getManagementKey()
1612                                 + " is missing.",
1613                         dependency.getLocation(""));
1614                 return null;
1615             }
1616             if (version == null || version.isEmpty()) {
1617                 add(
1618                         Severity.ERROR,
1619                         Version.BASE,
1620                         "'dependencyManagement.dependencies.dependency.version' for " + dependency.getManagementKey()
1621                                 + " is missing.",
1622                         dependency.getLocation(""));
1623                 return null;
1624             }
1625 
1626             String imported = groupId + ':' + artifactId + ':' + version;
1627 
1628             if (importIds.contains(imported)) {
1629                 StringBuilder message =
1630                         new StringBuilder("The dependencies of type=pom and with scope=import form a cycle: ");
1631                 for (String modelId : importIds) {
1632                     message.append(modelId).append(" -> ");
1633                 }
1634                 message.append(imported);
1635                 add(Severity.ERROR, Version.BASE, message.toString());
1636                 return null;
1637             }
1638 
1639             Model importModel = cache(
1640                     groupId,
1641                     artifactId,
1642                     version,
1643                     IMPORT,
1644                     () -> doLoadDependencyManagement(dependency, groupId, artifactId, version, importIds));
1645             DependencyManagement importMgmt = importModel != null ? importModel.getDependencyManagement() : null;
1646             if (importMgmt == null) {
1647                 importMgmt = DependencyManagement.newInstance();
1648             }
1649 
1650             // [MNG-5600] Dependency management import should support exclusions.
1651             List<Exclusion> exclusions = dependency.getExclusions();
1652             if (importMgmt != null && !exclusions.isEmpty()) {
1653                 // Dependency excluded from import.
1654                 List<Dependency> dependencies = importMgmt.getDependencies().stream()
1655                         .filter(candidate -> exclusions.stream().noneMatch(exclusion -> match(exclusion, candidate)))
1656                         .map(candidate -> addExclusions(candidate, exclusions))
1657                         .collect(Collectors.toList());
1658                 importMgmt = importMgmt.withDependencies(dependencies);
1659             }
1660 
1661             return importMgmt;
1662         }
1663 
1664         @SuppressWarnings("checkstyle:parameternumber")
1665         private Model doLoadDependencyManagement(
1666                 Dependency dependency,
1667                 String groupId,
1668                 String artifactId,
1669                 String version,
1670                 Collection<String> importIds) {
1671             Model importModel;
1672             ModelSource importSource;
1673             try {
1674                 importSource = resolveReactorModel(groupId, artifactId, version);
1675                 if (importSource == null) {
1676                     importSource = modelResolver.resolveModel(
1677                             request.getSession(), repositories, dependency, new AtomicReference<>());
1678                 }
1679             } catch (ModelBuilderException | ModelResolverException e) {
1680                 StringBuilder buffer = new StringBuilder(256);
1681                 buffer.append("Non-resolvable import POM");
1682                 if (!containsCoordinates(e.getMessage(), groupId, artifactId, version)) {
1683                     buffer.append(' ').append(ModelProblemUtils.toId(groupId, artifactId, version));
1684                 }
1685                 buffer.append(": ").append(e.getMessage());
1686 
1687                 add(Severity.ERROR, Version.BASE, buffer.toString(), dependency.getLocation(""), e);
1688                 return null;
1689             }
1690 
1691             Path rootDirectory;
1692             try {
1693                 rootDirectory = request.getSession().getRootDirectory();
1694             } catch (IllegalStateException e) {
1695                 rootDirectory = null;
1696             }
1697             if (request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_PROJECT && rootDirectory != null) {
1698                 Path sourcePath = importSource.getPath();
1699                 if (sourcePath != null && sourcePath.startsWith(rootDirectory)) {
1700                     add(
1701                             Severity.WARNING,
1702                             Version.BASE,
1703                             "BOM imports from within reactor should be avoided",
1704                             dependency.getLocation(""));
1705                 }
1706             }
1707 
1708             final ModelBuilderResult importResult;
1709             try {
1710                 ModelBuilderRequest importRequest = ModelBuilderRequest.builder()
1711                         .session(request.getSession())
1712                         .requestType(ModelBuilderRequest.RequestType.CONSUMER_DEPENDENCY)
1713                         .systemProperties(request.getSystemProperties())
1714                         .userProperties(request.getUserProperties())
1715                         .source(importSource)
1716                         .repositories(repositories)
1717                         .build();
1718                 ModelBuilderSessionState modelBuilderSession = derive(importRequest);
1719                 // build the effective model
1720                 modelBuilderSession.buildEffectiveModel(importIds);
1721                 importResult = modelBuilderSession.result;
1722             } catch (ModelBuilderException e) {
1723                 e.getResult().getProblems().forEach(this::add);
1724                 return null;
1725             }
1726 
1727             importResult.getProblems().forEach(this::add);
1728 
1729             importModel = importResult.getEffectiveModel();
1730 
1731             return importModel;
1732         }
1733 
1734         ModelSource resolveReactorModel(String groupId, String artifactId, String version)
1735                 throws ModelBuilderException {
1736             Set<ModelSource> sources = mappedSources.get(new GAKey(groupId, artifactId));
1737             if (sources != null) {
1738                 for (ModelSource source : sources) {
1739                     Model model = derive(source).readRawModel();
1740                     if (Objects.equals(getVersion(model), version)) {
1741                         return source;
1742                     }
1743                 }
1744                 // TODO: log a warning ?
1745             }
1746             return null;
1747         }
1748 
1749         private <T> T cache(String groupId, String artifactId, String version, String tag, Supplier<T> supplier) {
1750             return cache.computeIfAbsent(groupId, artifactId, version, tag, supplier);
1751         }
1752 
1753         private <T> T cache(Source source, String tag, Supplier<T> supplier) throws ModelBuilderException {
1754             return cache.computeIfAbsent(source, tag, supplier);
1755         }
1756 
1757         boolean isBuildRequest() {
1758             return request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_PROJECT
1759                     || request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_EFFECTIVE
1760                     || request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_CONSUMER;
1761         }
1762 
1763         boolean isBuildRequestWithActivation() {
1764             return request.getRequestType() != ModelBuilderRequest.RequestType.BUILD_CONSUMER;
1765         }
1766 
1767         private List<Profile> interpolateActivations(
1768                 List<Profile> profiles, DefaultProfileActivationContext context, ModelProblemCollector problems) {
1769             if (profiles.stream()
1770                     .map(org.apache.maven.api.model.Profile::getActivation)
1771                     .noneMatch(Objects::nonNull)) {
1772                 return profiles;
1773             }
1774             Interpolator interpolator = request.getSession().getService(Interpolator.class);
1775 
1776             class ProfileInterpolator extends MavenTransformer implements UnaryOperator<Profile> {
1777                 ProfileInterpolator() {
1778                     super(s -> {
1779                         try {
1780                             return interpolator.interpolate(
1781                                     s, Interpolator.chain(context::getUserProperty, context::getSystemProperty));
1782                         } catch (InterpolatorException e) {
1783                             problems.add(Severity.ERROR, Version.BASE, e.getMessage(), e);
1784                         }
1785                         return s;
1786                     });
1787                 }
1788 
1789                 @Override
1790                 public Profile apply(Profile p) {
1791                     return Profile.newBuilder(p)
1792                             .activation(transformActivation(p.getActivation()))
1793                             .build();
1794                 }
1795 
1796                 @Override
1797                 protected Activation.Builder transformActivation_Condition(
1798                         Supplier<? extends Activation.Builder> creator, Activation.Builder builder, Activation target) {
1799                     // do not interpolate the condition activation
1800                     return builder;
1801                 }
1802 
1803                 @Override
1804                 protected ActivationFile.Builder transformActivationFile_Missing(
1805                         Supplier<? extends ActivationFile.Builder> creator,
1806                         ActivationFile.Builder builder,
1807                         ActivationFile target) {
1808                     String path = target.getMissing();
1809                     String xformed = transformPath(path, target, "missing");
1810                     return xformed != path ? (builder != null ? builder : creator.get()).missing(xformed) : builder;
1811                 }
1812 
1813                 @Override
1814                 protected ActivationFile.Builder transformActivationFile_Exists(
1815                         Supplier<? extends ActivationFile.Builder> creator,
1816                         ActivationFile.Builder builder,
1817                         ActivationFile target) {
1818                     final String path = target.getExists();
1819                     final String xformed = transformPath(path, target, "exists");
1820                     return xformed != path ? (builder != null ? builder : creator.get()).exists(xformed) : builder;
1821                 }
1822 
1823                 private String transformPath(String path, ActivationFile target, String locationKey) {
1824                     try {
1825                         return context.interpolatePath(path);
1826                     } catch (InterpolatorException e) {
1827                         problems.add(
1828                                 Severity.ERROR,
1829                                 Version.BASE,
1830                                 "Failed to interpolate file location " + path + ": " + e.getMessage(),
1831                                 target.getLocation(locationKey),
1832                                 e);
1833                     }
1834                     return path;
1835                 }
1836             }
1837             return profiles.stream().map(new ProfileInterpolator()).toList();
1838         }
1839     }
1840 
1841     @SuppressWarnings("deprecation")
1842     private static List<String> getSubprojects(Model activated) {
1843         List<String> subprojects = activated.getSubprojects();
1844         if (subprojects.isEmpty()) {
1845             subprojects = activated.getModules();
1846         }
1847         return subprojects;
1848     }
1849 
1850     public Model buildRawModel(ModelBuilderRequest request) throws ModelBuilderException {
1851         ModelBuilderSessionState build = new ModelBuilderSessionState(request);
1852         Model model = build.readRawModel();
1853         if (build.hasErrors()) {
1854             throw build.newModelBuilderException();
1855         }
1856         return model;
1857     }
1858 
1859     static String getGroupId(Model model) {
1860         String groupId = model.getGroupId();
1861         if (groupId == null && model.getParent() != null) {
1862             groupId = model.getParent().getGroupId();
1863         }
1864         return groupId;
1865     }
1866 
1867     static String getVersion(Model model) {
1868         String version = model.getVersion();
1869         if (version == null && model.getParent() != null) {
1870             version = model.getParent().getVersion();
1871         }
1872         return version;
1873     }
1874 
1875     private DefaultProfileActivationContext getProfileActivationContext(ModelBuilderRequest request, Model model) {
1876         DefaultProfileActivationContext context =
1877                 new DefaultProfileActivationContext(pathTranslator, rootLocator, interpolator);
1878 
1879         context.setActiveProfileIds(request.getActiveProfileIds());
1880         context.setInactiveProfileIds(request.getInactiveProfileIds());
1881         context.setSystemProperties(request.getSystemProperties());
1882         context.setUserProperties(request.getUserProperties());
1883         context.setModel(model);
1884 
1885         return context;
1886     }
1887 
1888     private Map<String, Activation> getProfileActivations(Model model) {
1889         return model.getProfiles().stream()
1890                 .filter(p -> p.getActivation() != null)
1891                 .collect(Collectors.toMap(Profile::getId, Profile::getActivation));
1892     }
1893 
1894     private Model injectProfileActivations(Model model, Map<String, Activation> activations) {
1895         List<Profile> profiles = new ArrayList<>();
1896         boolean modified = false;
1897         for (Profile profile : model.getProfiles()) {
1898             Activation activation = profile.getActivation();
1899             if (activation != null) {
1900                 // restore activation
1901                 profile = profile.withActivation(activations.get(profile.getId()));
1902                 modified = true;
1903             }
1904             profiles.add(profile);
1905         }
1906         return modified ? model.withProfiles(profiles) : model;
1907     }
1908 
1909     private Model interpolateModel(Model model, ModelBuilderRequest request, ModelProblemCollector problems) {
1910         Model interpolatedModel =
1911                 modelInterpolator.interpolateModel(model, model.getProjectDirectory(), request, problems);
1912         if (interpolatedModel.getParent() != null) {
1913             Map<String, String> map1 = request.getSession().getUserProperties();
1914             Map<String, String> map2 = model.getProperties();
1915             Map<String, String> map3 = request.getSession().getSystemProperties();
1916             Function<String, String> cb = Interpolator.chain(map1::get, map2::get, map3::get);
1917             try {
1918                 String interpolated =
1919                         interpolator.interpolate(interpolatedModel.getParent().getVersion(), cb);
1920                 interpolatedModel = interpolatedModel.withParent(
1921                         interpolatedModel.getParent().withVersion(interpolated));
1922             } catch (Exception e) {
1923                 problems.add(
1924                         Severity.ERROR,
1925                         Version.BASE,
1926                         "Failed to interpolate field: "
1927                                 + interpolatedModel.getParent().getVersion()
1928                                 + " on class: ",
1929                         e);
1930             }
1931         }
1932         interpolatedModel = interpolatedModel.withPomFile(model.getPomFile());
1933         return interpolatedModel;
1934     }
1935 
1936     private boolean rawChildVersionReferencesParent(String rawChildModelVersion) {
1937         return rawChildModelVersion.equals("${pom.version}")
1938                 || rawChildModelVersion.equals("${project.version}")
1939                 || rawChildModelVersion.equals("${pom.parent.version}")
1940                 || rawChildModelVersion.equals("${project.parent.version}");
1941     }
1942 
1943     private Model getSuperModel(String modelVersion) {
1944         return superPomProvider.getSuperPom(modelVersion);
1945     }
1946 
1947     private static org.apache.maven.api.model.Dependency addExclusions(
1948             org.apache.maven.api.model.Dependency candidate, List<Exclusion> exclusions) {
1949         return candidate.withExclusions(Stream.concat(candidate.getExclusions().stream(), exclusions.stream())
1950                 .toList());
1951     }
1952 
1953     private boolean match(Exclusion exclusion, Dependency candidate) {
1954         return match(exclusion.getGroupId(), candidate.getGroupId())
1955                 && match(exclusion.getArtifactId(), candidate.getArtifactId());
1956     }
1957 
1958     private boolean match(String match, String text) {
1959         return match.equals("*") || match.equals(text);
1960     }
1961 
1962     private boolean containsCoordinates(String message, String groupId, String artifactId, String version) {
1963         return message != null
1964                 && (groupId == null || message.contains(groupId))
1965                 && (artifactId == null || message.contains(artifactId))
1966                 && (version == null || message.contains(version));
1967     }
1968 
1969     record GAKey(String groupId, String artifactId) {}
1970 }