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