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