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