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