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.project;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.io.ByteArrayInputStream;
26  import java.io.File;
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.nio.charset.StandardCharsets;
30  import java.nio.file.Path;
31  import java.util.AbstractMap;
32  import java.util.ArrayList;
33  import java.util.Collection;
34  import java.util.Collections;
35  import java.util.HashMap;
36  import java.util.HashSet;
37  import java.util.LinkedHashSet;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.Properties;
41  import java.util.Set;
42  import java.util.concurrent.Callable;
43  import java.util.concurrent.ConcurrentHashMap;
44  import java.util.concurrent.ConcurrentMap;
45  import java.util.concurrent.ExecutionException;
46  import java.util.concurrent.ExecutorService;
47  import java.util.concurrent.Future;
48  import java.util.concurrent.LinkedBlockingQueue;
49  import java.util.concurrent.ThreadPoolExecutor;
50  import java.util.concurrent.TimeUnit;
51  import java.util.concurrent.atomic.AtomicInteger;
52  import java.util.concurrent.atomic.AtomicReference;
53  import java.util.function.Consumer;
54  import java.util.function.Supplier;
55  import java.util.stream.Collectors;
56  import java.util.stream.Stream;
57  
58  import org.apache.maven.ProjectCycleException;
59  import org.apache.maven.RepositoryUtils;
60  import org.apache.maven.api.Constants;
61  import org.apache.maven.api.Session;
62  import org.apache.maven.api.SessionData;
63  import org.apache.maven.api.feature.Features;
64  import org.apache.maven.api.model.Build;
65  import org.apache.maven.api.model.Dependency;
66  import org.apache.maven.api.model.DependencyManagement;
67  import org.apache.maven.api.model.DeploymentRepository;
68  import org.apache.maven.api.model.Extension;
69  import org.apache.maven.api.model.Model;
70  import org.apache.maven.api.model.Parent;
71  import org.apache.maven.api.model.Plugin;
72  import org.apache.maven.api.model.Profile;
73  import org.apache.maven.api.model.ReportPlugin;
74  import org.apache.maven.api.services.MavenException;
75  import org.apache.maven.api.services.ModelBuilder;
76  import org.apache.maven.api.services.ModelBuilderException;
77  import org.apache.maven.api.services.ModelBuilderRequest;
78  import org.apache.maven.api.services.ModelBuilderResult;
79  import org.apache.maven.api.services.ModelCache;
80  import org.apache.maven.api.services.ModelProblem;
81  import org.apache.maven.api.services.ModelResolver;
82  import org.apache.maven.api.services.ModelResolverException;
83  import org.apache.maven.api.services.ModelSource;
84  import org.apache.maven.api.services.ModelTransformerContext;
85  import org.apache.maven.api.services.ModelTransformerContextBuilder;
86  import org.apache.maven.api.services.Source;
87  import org.apache.maven.api.services.model.ModelBuildingListener;
88  import org.apache.maven.api.services.model.ModelProcessor;
89  import org.apache.maven.artifact.Artifact;
90  import org.apache.maven.artifact.InvalidArtifactRTException;
91  import org.apache.maven.artifact.InvalidRepositoryException;
92  import org.apache.maven.artifact.repository.ArtifactRepository;
93  import org.apache.maven.bridge.MavenRepositorySystem;
94  import org.apache.maven.internal.impl.InternalSession;
95  import org.apache.maven.internal.impl.resolver.DefaultModelCache;
96  import org.apache.maven.internal.impl.resolver.DefaultModelRepositoryHolder;
97  import org.apache.maven.model.building.DefaultModelProblem;
98  import org.apache.maven.model.building.FileModelSource;
99  import org.apache.maven.model.building.ModelSource2;
100 import org.apache.maven.model.building.ModelSource3;
101 import org.apache.maven.model.resolution.UnresolvableModelException;
102 import org.apache.maven.model.root.RootLocator;
103 import org.apache.maven.repository.internal.ArtifactDescriptorUtils;
104 import org.apache.maven.utils.Os;
105 import org.codehaus.plexus.interpolation.AbstractValueSource;
106 import org.codehaus.plexus.interpolation.InterpolationException;
107 import org.codehaus.plexus.interpolation.StringSearchInterpolator;
108 import org.eclipse.aether.RepositorySystem;
109 import org.eclipse.aether.RepositorySystemSession;
110 import org.eclipse.aether.RequestTrace;
111 import org.eclipse.aether.impl.RemoteRepositoryManager;
112 import org.eclipse.aether.repository.LocalRepositoryManager;
113 import org.eclipse.aether.repository.RemoteRepository;
114 import org.eclipse.aether.repository.WorkspaceRepository;
115 import org.eclipse.aether.resolution.ArtifactRequest;
116 import org.eclipse.aether.resolution.ArtifactResult;
117 import org.slf4j.Logger;
118 import org.slf4j.LoggerFactory;
119 
120 /**
121  * DefaultProjectBuilder
122  */
123 @Named
124 @Singleton
125 public class DefaultProjectBuilder implements ProjectBuilder {
126 
127     public static final int DEFAULT_BUILDER_PARALLELISM = Runtime.getRuntime().availableProcessors() / 2 + 1;
128 
129     private final Logger logger = LoggerFactory.getLogger(getClass());
130     private final ModelBuilder modelBuilder;
131     private final ModelProcessor modelProcessor;
132     private final ProjectBuildingHelper projectBuildingHelper;
133     private final MavenRepositorySystem repositorySystem;
134     private final org.eclipse.aether.RepositorySystem repoSystem;
135     private final RemoteRepositoryManager repositoryManager;
136     private final ProjectDependenciesResolver dependencyResolver;
137 
138     private final RootLocator rootLocator;
139 
140     @SuppressWarnings("checkstyle:ParameterNumber")
141     @Inject
142     public DefaultProjectBuilder(
143             ModelBuilder modelBuilder,
144             ModelProcessor modelProcessor,
145             ProjectBuildingHelper projectBuildingHelper,
146             MavenRepositorySystem repositorySystem,
147             RepositorySystem repoSystem,
148             RemoteRepositoryManager repositoryManager,
149             ProjectDependenciesResolver dependencyResolver,
150             RootLocator rootLocator) {
151         this.modelBuilder = modelBuilder;
152         this.modelProcessor = modelProcessor;
153         this.projectBuildingHelper = projectBuildingHelper;
154         this.repositorySystem = repositorySystem;
155         this.repoSystem = repoSystem;
156         this.repositoryManager = repositoryManager;
157         this.dependencyResolver = dependencyResolver;
158         this.rootLocator = rootLocator;
159     }
160     // ----------------------------------------------------------------------
161     // MavenProjectBuilder Implementation
162     // ----------------------------------------------------------------------
163 
164     @Override
165     public ProjectBuildingResult build(File pomFile, ProjectBuildingRequest request) throws ProjectBuildingException {
166         try (BuildSession bs = new BuildSession(request, false)) {
167             Path path = pomFile.toPath();
168             return bs.build(path, ModelSource.fromPath(path));
169         }
170     }
171 
172     @Deprecated
173     @Override
174     public ProjectBuildingResult build(
175             org.apache.maven.model.building.ModelSource modelSource, ProjectBuildingRequest request)
176             throws ProjectBuildingException {
177         return build(toSource(modelSource), request);
178     }
179 
180     @Deprecated
181     static ModelSource toSource(org.apache.maven.model.building.ModelSource modelSource) {
182         if (modelSource instanceof FileModelSource fms) {
183             return ModelSource.fromPath(fms.getPath());
184         } else {
185             return new ModelSource() {
186                 @Override
187                 public ModelSource resolve(ModelLocator modelLocator, String relative) {
188                     if (modelSource instanceof ModelSource3 ms) {
189                         return toSource(ms.getRelatedSource(
190                                 new org.apache.maven.model.locator.ModelLocator() {
191                                     @Override
192                                     public File locatePom(File projectDirectory) {
193                                         return null;
194                                     }
195 
196                                     @Override
197                                     public Path locatePom(Path projectDirectory) {
198                                         return null;
199                                     }
200 
201                                     @Override
202                                     public Path locateExistingPom(Path project) {
203                                         return modelLocator.locateExistingPom(project);
204                                     }
205                                 },
206                                 relative));
207                     }
208                     return null;
209                 }
210 
211                 @Override
212                 public Path getPath() {
213                     return null;
214                 }
215 
216                 @Override
217                 public InputStream openStream() throws IOException {
218                     return modelSource.getInputStream();
219                 }
220 
221                 @Override
222                 public String getLocation() {
223                     return modelSource.getLocation();
224                 }
225 
226                 @Override
227                 public Source resolve(String relative) {
228                     if (modelSource instanceof ModelSource2 ms) {
229                         return toSource(ms.getRelatedSource(relative));
230                     } else {
231                         return null;
232                     }
233                 }
234             };
235         }
236     }
237 
238     @Override
239     public ProjectBuildingResult build(ModelSource modelSource, ProjectBuildingRequest request)
240             throws ProjectBuildingException {
241         try (BuildSession bs = new BuildSession(request, false)) {
242             return bs.build(null, modelSource);
243         }
244     }
245 
246     @Override
247     public ProjectBuildingResult build(Artifact artifact, ProjectBuildingRequest request)
248             throws ProjectBuildingException {
249         return build(artifact, false, request);
250     }
251 
252     @Override
253     public ProjectBuildingResult build(Artifact artifact, boolean allowStubModel, ProjectBuildingRequest request)
254             throws ProjectBuildingException {
255         try (BuildSession bs = new BuildSession(request, false)) {
256             return bs.build(artifact, allowStubModel);
257         }
258     }
259 
260     @Override
261     public List<ProjectBuildingResult> build(List<File> pomFiles, boolean recursive, ProjectBuildingRequest request)
262             throws ProjectBuildingException {
263         try (BuildSession bs = new BuildSession(request, true)) {
264             return bs.build(pomFiles, recursive);
265         }
266     }
267 
268     static class InterimResult {
269 
270         File pomFile;
271 
272         ModelBuilderRequest request;
273 
274         ModelBuilderResult result;
275 
276         MavenProject project;
277 
278         boolean root;
279 
280         List<InterimResult> subprojects = Collections.emptyList();
281 
282         ProjectBuildingResult projectBuildingResult;
283 
284         InterimResult(
285                 File pomFile,
286                 ModelBuilderRequest request,
287                 ModelBuilderResult result,
288                 MavenProject project,
289                 boolean root) {
290             this.pomFile = pomFile;
291             this.request = request;
292             this.result = result;
293             this.project = project;
294             this.root = root;
295         }
296 
297         InterimResult(ModelBuilderRequest request, ProjectBuildingResult projectBuildingResult) {
298             this.request = request;
299             this.projectBuildingResult = projectBuildingResult;
300             this.pomFile = projectBuildingResult.getPomFile();
301             this.project = projectBuildingResult.getProject();
302         }
303     }
304 
305     class BuildSession implements AutoCloseable {
306         private final ProjectBuildingRequest request;
307         private final RepositorySystemSession session;
308         private final List<RemoteRepository> repositories;
309         private final ReactorModelPool modelPool;
310         private final ConcurrentMap<String, Object> parentCache;
311         private final ModelTransformerContextBuilder transformerContextBuilder;
312         private final ExecutorService executor;
313         private final ModelCache modelCache;
314         private final ModelResolver modelResolver;
315         private final Map<String, String> ciFriendlyVersions = new ConcurrentHashMap<>();
316 
317         BuildSession(ProjectBuildingRequest request, boolean localProjects) {
318             this.request = request;
319             this.session =
320                     RepositoryUtils.overlay(request.getLocalRepository(), request.getRepositorySession(), repoSystem);
321             InternalSession.from(session);
322             this.repositories = RepositoryUtils.toRepos(request.getRemoteRepositories());
323             this.executor = createExecutor(getParallelism(request));
324             if (localProjects) {
325                 this.modelPool = new ReactorModelPool();
326                 this.transformerContextBuilder = modelBuilder.newTransformerContextBuilder();
327             } else {
328                 this.modelPool = null;
329                 this.transformerContextBuilder = null;
330             }
331             this.parentCache = new ConcurrentHashMap<>();
332             this.modelCache = DefaultModelCache.newInstance(session, true);
333             this.modelResolver = new ModelResolverWrapper() {
334                 @Override
335                 protected org.apache.maven.model.resolution.ModelResolver getResolver(
336                         List<RemoteRepository> repositories) {
337                     return new ProjectModelResolver(
338                             session,
339                             RequestTrace.newChild(null, request),
340                             repoSystem,
341                             repositoryManager,
342                             repositories,
343                             request.getRepositoryMerging(),
344                             modelPool,
345                             parentCache);
346                 }
347             };
348         }
349 
350         ExecutorService createExecutor(int parallelism) {
351             //
352             // We need an executor that will not block.
353             // We can't use work stealing, as we are building a graph
354             // and this could lead to cycles where a thread waits for
355             // a task to finish, then execute another one which waits
356             // for the initial task...
357             // In order to work around that problem, we override the
358             // invokeAll method, so that whenever the method is called,
359             // the pool core size will be incremented before submitting
360             // all the tasks, then the thread will block waiting for all
361             // those subtasks to finish.
362             // This ensures the number of running workers is no more than
363             // the defined parallism, while making sure the pool will not
364             // be exhausted
365             //
366             return new ThreadPoolExecutor(
367                     parallelism, Integer.MAX_VALUE, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()) {
368                 final AtomicInteger parked = new AtomicInteger();
369 
370                 @Override
371                 public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
372                         throws InterruptedException {
373                     setCorePoolSize(parallelism + parked.incrementAndGet());
374                     try {
375                         return super.invokeAll(tasks);
376                     } finally {
377                         setCorePoolSize(parallelism + parked.decrementAndGet());
378                     }
379                 }
380             };
381         }
382 
383         @Override
384         public void close() {
385             this.executor.shutdownNow();
386         }
387 
388         private int getParallelism(ProjectBuildingRequest request) {
389             int parallelism = DEFAULT_BUILDER_PARALLELISM;
390             try {
391                 String str = request.getUserProperties().getProperty(Constants.MAVEN_PROJECT_BUILDER_PARALLELISM);
392                 if (str != null) {
393                     parallelism = Integer.parseInt(str);
394                 }
395             } catch (Exception e) {
396                 // ignore
397             }
398             return Math.max(1, Math.min(parallelism, Runtime.getRuntime().availableProcessors()));
399         }
400 
401         ProjectBuildingResult build(Path pomFile, ModelSource modelSource) throws ProjectBuildingException {
402             ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
403 
404             try {
405                 MavenProject project = request.getProject();
406 
407                 List<ModelProblem> modelProblems = null;
408                 Throwable error = null;
409 
410                 if (project == null) {
411                     project = new MavenProject();
412                     project.setFile(pomFile != null ? pomFile.toFile() : null);
413 
414                     ModelBuildingListener listener =
415                             new DefaultModelBuildingListener(project, projectBuildingHelper, this.request);
416 
417                     ModelBuilderRequest.ModelBuilderRequestBuilder builder = getModelBuildingRequest();
418                     ModelBuilderRequest request = builder.projectBuild(modelPool != null)
419                             .source(modelSource)
420                             .projectBuild(true)
421                             .locationTracking(true)
422                             .listener(listener)
423                             .build();
424 
425                     if (pomFile != null) {
426                         project.setRootDirectory(rootLocator.findRoot(pomFile.getParent()));
427                     }
428 
429                     ModelBuilderResult result;
430                     try {
431                         result = modelBuilder.build(request);
432                     } catch (ModelBuilderException e) {
433                         result = e.getResult();
434                         if (result == null || result.getEffectiveModel() == null) {
435                             throw new ProjectBuildingException(
436                                     e.getModelId(), e.getMessage(), pomFile != null ? pomFile.toFile() : null, e);
437                         }
438                         // validation error, continue project building and delay failing to help IDEs
439                         error = e;
440                     }
441 
442                     modelProblems = result.getProblems();
443 
444                     initProject(project, Collections.emptyMap(), result);
445                 } else if (request.isResolveDependencies()) {
446                     projectBuildingHelper.selectProjectRealm(project);
447                 }
448 
449                 DependencyResolutionResult resolutionResult = null;
450 
451                 if (request.isResolveDependencies()) {
452                     resolutionResult = resolveDependencies(project);
453                 }
454 
455                 ProjectBuildingResult result =
456                         new DefaultProjectBuildingResult(project, convert(modelProblems), resolutionResult);
457 
458                 if (error != null) {
459                     ProjectBuildingException e = new ProjectBuildingException(List.of(result));
460                     e.initCause(error);
461                     throw e;
462                 }
463 
464                 return result;
465             } finally {
466                 Thread.currentThread().setContextClassLoader(oldContextClassLoader);
467             }
468         }
469 
470         ProjectBuildingResult build(Artifact artifact, boolean allowStubModel) throws ProjectBuildingException {
471             org.eclipse.aether.artifact.Artifact pomArtifact = RepositoryUtils.toArtifact(artifact);
472             pomArtifact = ArtifactDescriptorUtils.toPomArtifact(pomArtifact);
473 
474             boolean localProject;
475 
476             try {
477                 ArtifactRequest pomRequest = new ArtifactRequest();
478                 pomRequest.setArtifact(pomArtifact);
479                 pomRequest.setRepositories(RepositoryUtils.toRepos(request.getRemoteRepositories()));
480                 ArtifactResult pomResult = repoSystem.resolveArtifact(session, pomRequest);
481 
482                 pomArtifact = pomResult.getArtifact();
483                 localProject = pomResult.getRepository() instanceof WorkspaceRepository;
484             } catch (org.eclipse.aether.resolution.ArtifactResolutionException e) {
485                 if (e.getResults().get(0).isMissing() && allowStubModel) {
486                     return build(null, createStubModelSource(artifact));
487                 }
488                 throw new ProjectBuildingException(
489                         artifact.getId(), "Error resolving project artifact: " + e.getMessage(), e);
490             }
491 
492             Path pomFile = pomArtifact.getPath();
493 
494             if (!artifact.isResolved() && "pom".equals(artifact.getType())) {
495                 artifact.selectVersion(pomArtifact.getVersion());
496                 artifact.setFile(pomFile.toFile());
497                 artifact.setResolved(true);
498             }
499 
500             if (localProject) {
501                 return build(pomFile, ModelSource.fromPath(pomFile));
502             } else {
503                 return build(
504                         null,
505                         ModelSource.fromPath(
506                                 pomFile,
507                                 artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getVersion()));
508             }
509         }
510 
511         List<ProjectBuildingResult> build(List<File> pomFiles, boolean recursive) throws ProjectBuildingException {
512             List<ProjectBuildingResult> results = doBuild(pomFiles, recursive);
513             if (results.stream()
514                     .flatMap(r -> r.getProblems().stream())
515                     .anyMatch(p -> p.getSeverity() != org.apache.maven.model.building.ModelProblem.Severity.WARNING)) {
516                 org.apache.maven.model.building.ModelProblem cycle = results.stream()
517                         .flatMap(r -> r.getProblems().stream())
518                         .filter(p -> p.getException() instanceof CycleDetectedException)
519                         .findAny()
520                         .orElse(null);
521                 if (cycle != null) {
522                     throw new RuntimeException(new ProjectCycleException(
523                             "The projects in the reactor contain a cyclic reference: " + cycle.getMessage(),
524                             (CycleDetectedException) cycle.getException()));
525                 }
526                 throw new ProjectBuildingException(results);
527             }
528 
529             return results;
530         }
531 
532         List<ProjectBuildingResult> doBuild(List<File> pomFiles, boolean recursive) {
533             Map<File, MavenProject> projectIndex = new ConcurrentHashMap<>(256);
534 
535             // phase 1: get file Models from the reactor.
536             List<InterimResult> interimResults = build(projectIndex, pomFiles, new LinkedHashSet<>(), true, recursive);
537 
538             ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
539 
540             try {
541                 // Phase 2: get effective models from the reactor
542                 List<ProjectBuildingResult> results = build(projectIndex, interimResults);
543 
544                 if (Features.buildConsumer(request.getUserProperties())) {
545                     request.getRepositorySession()
546                             .getData()
547                             .set(ModelTransformerContext.KEY, transformerContextBuilder.build());
548                 }
549 
550                 return results;
551             } finally {
552                 Thread.currentThread().setContextClassLoader(oldContextClassLoader);
553             }
554         }
555 
556         @SuppressWarnings("checkstyle:parameternumber")
557         private List<InterimResult> build(
558                 Map<File, MavenProject> projectIndex,
559                 List<File> pomFiles,
560                 Set<File> aggregatorFiles,
561                 boolean root,
562                 boolean recursive) {
563             List<Callable<InterimResult>> tasks = pomFiles.stream()
564                     .map(pomFile -> ((Callable<InterimResult>)
565                             () -> build(projectIndex, pomFile, concat(aggregatorFiles, pomFile), root, recursive)))
566                     .collect(Collectors.toList());
567             try {
568                 List<Future<InterimResult>> futures = executor.invokeAll(tasks);
569                 List<InterimResult> list = new ArrayList<>();
570                 for (Future<InterimResult> future : futures) {
571                     InterimResult interimResult = future.get();
572                     list.add(interimResult);
573                 }
574                 return list;
575             } catch (Exception e) {
576                 throw new RuntimeException(e);
577             }
578         }
579 
580         private <T> Set<T> concat(Set<T> set, T elem) {
581             Set<T> newSet = new HashSet<>(set);
582             newSet.add(elem);
583             return newSet;
584         }
585 
586         @SuppressWarnings("checkstyle:parameternumber")
587         private InterimResult build(
588                 Map<File, MavenProject> projectIndex,
589                 File pomFile,
590                 Set<File> aggregatorFiles,
591                 boolean isRoot,
592                 boolean recursive) {
593             MavenProject project = new MavenProject();
594             project.setFile(pomFile);
595 
596             project.setRootDirectory(
597                     rootLocator.findRoot(pomFile.getParentFile().toPath()));
598 
599             DefaultModelBuildingListener listener =
600                     new DefaultModelBuildingListener(project, projectBuildingHelper, request);
601 
602             ModelBuilderRequest modelBuildingRequest = getModelBuildingRequest()
603                     .source(ModelSource.fromPath(pomFile.toPath()))
604                     .projectBuild(true)
605                     .twoPhaseBuilding(true)
606                     .locationTracking(true)
607                     .listener(listener)
608                     .build();
609 
610             ModelBuilderResult result;
611             try {
612                 result = modelBuilder.build(modelBuildingRequest);
613             } catch (ModelBuilderException e) {
614                 result = e.getResult();
615                 if (result == null || result.getFileModel() == null) {
616                     return new InterimResult(
617                             modelBuildingRequest,
618                             new DefaultProjectBuildingResult(e.getModelId(), pomFile, convert(e.getProblems())));
619                 }
620                 // validation error, continue project building and delay failing to help IDEs
621                 // result.getProblems().addAll(e.getProblems()) ?
622             }
623 
624             Model model = result.getActivatedFileModel();
625 
626             // In case the model is using CI friendly versions, at this point, it will contain uninterpolated version
627             // such as ${revision}${changelist}, so we need to take care of it now
628             Model modelWithVersion = getModelWithInterpolatedVersion(model, result.getProblems()::add);
629             modelPool.put(model.getPomFile(), modelWithVersion);
630 
631             InterimResult interimResult = new InterimResult(pomFile, modelBuildingRequest, result, project, isRoot);
632 
633             if (recursive) {
634                 File basedir = pomFile.getParentFile();
635                 List<String> subprojects = model.getSubprojects();
636                 if (subprojects.isEmpty()) {
637                     subprojects = model.getModules();
638                 }
639                 List<File> subprojectFiles = new ArrayList<>();
640                 for (String subproject : subprojects) {
641                     if (subproject == null || subproject.isEmpty()) {
642                         continue;
643                     }
644 
645                     subproject = subproject.replace('\\', File.separatorChar).replace('/', File.separatorChar);
646 
647                     Path subprojectPath = modelProcessor.locateExistingPom(new File(basedir, subproject).toPath());
648                     File subprojectFile = subprojectPath != null ? subprojectPath.toFile() : null;
649 
650                     if (subprojectFile == null) {
651                         ModelProblem problem = new org.apache.maven.internal.impl.model.DefaultModelProblem(
652                                 "Child subproject " + subprojectFile + " of " + pomFile + " does not exist",
653                                 ModelProblem.Severity.ERROR,
654                                 ModelProblem.Version.BASE,
655                                 model,
656                                 -1,
657                                 -1,
658                                 null);
659                         result.getProblems().add(problem);
660                         continue;
661                     }
662 
663                     if (Os.IS_WINDOWS) {
664                         // we don't canonicalize on unix to avoid interfering with symlinks
665                         try {
666                             subprojectFile = subprojectFile.getCanonicalFile();
667                         } catch (IOException e) {
668                             subprojectFile = subprojectFile.getAbsoluteFile();
669                         }
670                     } else {
671                         subprojectFile = new File(subprojectFile.toURI().normalize());
672                     }
673 
674                     if (aggregatorFiles.contains(subprojectFile)) {
675                         StringBuilder buffer = new StringBuilder(256);
676                         for (File aggregatorFile : aggregatorFiles) {
677                             buffer.append(aggregatorFile).append(" -> ");
678                         }
679                         buffer.append(subprojectFile);
680 
681                         ModelProblem problem = new org.apache.maven.internal.impl.model.DefaultModelProblem(
682                                 "Child subproject " + subprojectFile + " of " + pomFile + " forms aggregation cycle "
683                                         + buffer,
684                                 ModelProblem.Severity.ERROR,
685                                 ModelProblem.Version.BASE,
686                                 model,
687                                 -1,
688                                 -1,
689                                 null);
690                         result.getProblems().add(problem);
691 
692                         continue;
693                     }
694 
695                     subprojectFiles.add(subprojectFile);
696                 }
697 
698                 if (!subprojectFiles.isEmpty()) {
699                     interimResult.subprojects = build(projectIndex, subprojectFiles, aggregatorFiles, false, recursive);
700                 }
701             }
702 
703             projectIndex.put(pomFile, project);
704 
705             return interimResult;
706         }
707 
708         private Model getModelWithInterpolatedVersion(
709                 Model model, Consumer<org.apache.maven.api.services.ModelProblem> problems) {
710             String version = model.getVersion();
711             if (version == null && model.getParent() != null) {
712                 version = model.getParent().getVersion();
713             }
714             if (version != null && version.contains("${")) {
715                 try {
716                     StringSearchInterpolator interpolator = new StringSearchInterpolator();
717                     interpolator.addValueSource(new AbstractValueSource(false) {
718                         @Override
719                         public String getValue(String key) {
720                             String val = request.getUserProperties().getProperty(key);
721                             if (val == null) {
722                                 val = model.getProperties().get(key);
723                                 if (val == null) {
724                                     val = request.getSystemProperties().getProperty(key);
725                                     if (val == null) {
726                                         val = ciFriendlyVersions.get(key);
727                                     }
728                                 }
729                             }
730                             if (val != null) {
731                                 String oldVal = ciFriendlyVersions.put(key, val);
732                                 if (oldVal != null && !val.equals(oldVal)) {
733                                     throw new MavenException("Non unique property value detected for key '" + key
734                                             + "' which is bound to '" + oldVal + "' and '" + val + "'");
735                                 }
736                             }
737                             return val;
738                         }
739                     });
740                     version = interpolator.interpolate(version);
741                 } catch (InterpolationException | MavenException e) {
742                     ModelProblem problem = new org.apache.maven.internal.impl.model.DefaultModelProblem(
743                             "Unable to interpolate ",
744                             ModelProblem.Severity.ERROR,
745                             ModelProblem.Version.BASE,
746                             model,
747                             -1,
748                             -1,
749                             null);
750                     problems.accept(problem);
751                 }
752                 if (model.getVersion() != null) {
753                     return model.withVersion(version);
754                 } else {
755                     return model.withParent(model.getParent().withVersion(version));
756                 }
757             }
758             return model;
759         }
760 
761         private List<ProjectBuildingResult> build(
762                 Map<File, MavenProject> projectIndex, List<InterimResult> interimResults) {
763             // The transformation may need to access dependencies raw models,
764             // which may cause some re-entrance in the build() method and can
765             // actually cause deadlocks.  In order to workaround the problem,
766             // we do a first pass by reading all rawModels in order.
767             List<ProjectBuildingResult> results = new ArrayList<>();
768             boolean failure = false;
769             for (InterimResult r : interimResults) {
770                 DefaultProjectBuildingResult res;
771                 try {
772                     Model model = modelBuilder.buildRawModel(r.request);
773                     res = new DefaultProjectBuildingResult(
774                             model.getId(),
775                             model.getPomFile() != null ? model.getPomFile().toFile() : null,
776                             null);
777                 } catch (ModelBuilderException e) {
778                     failure = true;
779                     res = new DefaultProjectBuildingResult(
780                             e.getModelId(),
781                             r.request.getSource().getPath() != null
782                                     ? r.request.getSource().getPath().toFile()
783                                     : null,
784                             convert(e.getProblems()));
785                 }
786                 results.add(res);
787             }
788             if (failure) {
789                 return results;
790             }
791 
792             List<Callable<List<ProjectBuildingResult>>> callables = interimResults.stream()
793                     .map(interimResult ->
794                             (Callable<List<ProjectBuildingResult>>) () -> doBuild(projectIndex, interimResult))
795                     .collect(Collectors.toList());
796 
797             try {
798                 List<Future<List<ProjectBuildingResult>>> futures = executor.invokeAll(callables);
799                 return futures.stream()
800                         .map(listFuture -> {
801                             try {
802                                 return listFuture.get();
803                             } catch (InterruptedException e) {
804                                 uncheckedThrow(e);
805                                 return null;
806                             } catch (ExecutionException e) {
807                                 uncheckedThrow(e.getCause());
808                                 return null;
809                             }
810                         })
811                         .flatMap(List::stream)
812                         .collect(Collectors.toList());
813             } catch (InterruptedException e) {
814                 uncheckedThrow(e);
815                 return null;
816             }
817         }
818 
819         private List<ProjectBuildingResult> doBuild(Map<File, MavenProject> projectIndex, InterimResult interimResult) {
820             if (interimResult.projectBuildingResult != null) {
821                 return Collections.singletonList(interimResult.projectBuildingResult);
822             }
823             MavenProject project = interimResult.project;
824             try {
825                 ModelBuilderResult result = modelBuilder.build(ModelBuilderRequest.builder(interimResult.request)
826                         .interimResult(interimResult.result)
827                         .build());
828 
829                 // 2nd pass of initialization: resolve and build parent if necessary
830                 List<org.apache.maven.model.building.ModelProblem> problems = convert(result.getProblems());
831                 try {
832                     initProject(project, projectIndex, result);
833                 } catch (InvalidArtifactRTException iarte) {
834                     problems.add(new DefaultModelProblem(
835                             null,
836                             org.apache.maven.model.building.ModelProblem.Severity.ERROR,
837                             null,
838                             new org.apache.maven.model.Model(result.getEffectiveModel()),
839                             -1,
840                             -1,
841                             iarte));
842                 }
843 
844                 List<ProjectBuildingResult> results = build(projectIndex, interimResult.subprojects);
845 
846                 project.setExecutionRoot(interimResult.root);
847                 project.setCollectedProjects(
848                         results.stream().map(ProjectBuildingResult::getProject).collect(Collectors.toList()));
849                 DependencyResolutionResult resolutionResult = null;
850                 if (request.isResolveDependencies()) {
851                     resolutionResult = resolveDependencies(project);
852                 }
853 
854                 results.add(new DefaultProjectBuildingResult(project, problems, resolutionResult));
855 
856                 return results;
857             } catch (ModelBuilderException e) {
858                 DefaultProjectBuildingResult result;
859                 if (project == null || interimResult.result.getEffectiveModel() == null) {
860                     result = new DefaultProjectBuildingResult(
861                             e.getModelId(), interimResult.pomFile, convert(e.getProblems()));
862                 } else {
863                     project.setModel(new org.apache.maven.model.Model(interimResult.result.getEffectiveModel()));
864                     result = new DefaultProjectBuildingResult(project, convert(e.getProblems()), null);
865                 }
866                 return Collections.singletonList(result);
867             }
868         }
869 
870         private List<org.apache.maven.model.building.ModelProblem> convert(List<ModelProblem> problems) {
871             if (problems == null) {
872                 return null;
873             }
874             return problems.stream()
875                     .map(p -> (org.apache.maven.model.building.ModelProblem) new DefaultModelProblem(
876                             p.getMessage(),
877                             org.apache.maven.model.building.ModelProblem.Severity.valueOf(
878                                     p.getSeverity().name()),
879                             org.apache.maven.model.building.ModelProblem.Version.valueOf(
880                                     p.getVersion().name()),
881                             p.getSource(),
882                             p.getLineNumber(),
883                             p.getColumnNumber(),
884                             p.getModelId(),
885                             p.getException()))
886                     .toList();
887         }
888 
889         @SuppressWarnings({"checkstyle:methodlength", "deprecation"})
890         private void initProject(MavenProject project, Map<File, MavenProject> projects, ModelBuilderResult result) {
891             project.setModel(new org.apache.maven.model.Model(result.getEffectiveModel()));
892             project.setOriginalModel(new org.apache.maven.model.Model(result.getFileModel()));
893 
894             initParent(project, projects, result);
895 
896             Artifact projectArtifact = repositorySystem.createArtifact(
897                     project.getGroupId(), project.getArtifactId(), project.getVersion(), null, project.getPackaging());
898             project.setArtifact(projectArtifact);
899 
900             // only set those on 2nd phase, ignore on 1st pass
901             if (project.getFile() != null) {
902                 Build build = project.getBuild().getDelegate();
903                 project.addScriptSourceRoot(build.getScriptSourceDirectory());
904                 project.addCompileSourceRoot(build.getSourceDirectory());
905                 project.addTestCompileSourceRoot(build.getTestSourceDirectory());
906             }
907 
908             project.setActiveProfiles(Stream.concat(
909                             result.getActivePomProfiles(result.getModelIds().get(0)).stream(),
910                             result.getActiveExternalProfiles().stream())
911                     .map(org.apache.maven.model.Profile::new)
912                     .toList());
913 
914             project.setInjectedProfileIds("external", getProfileIds(result.getActiveExternalProfiles()));
915             for (String modelId : result.getModelIds()) {
916                 project.setInjectedProfileIds(modelId, getProfileIds(result.getActivePomProfiles(modelId)));
917             }
918 
919             //
920             // All the parts that were taken out of MavenProject for Maven 4.0.0
921             //
922 
923             project.setProjectBuildingRequest(request);
924 
925             // pluginArtifacts
926             Set<Artifact> pluginArtifacts = new HashSet<>();
927             for (Plugin plugin : project.getModel().getDelegate().getBuild().getPlugins()) {
928                 Artifact artifact = repositorySystem.createPluginArtifact(new org.apache.maven.model.Plugin(plugin));
929 
930                 if (artifact != null) {
931                     pluginArtifacts.add(artifact);
932                 }
933             }
934             project.setPluginArtifacts(pluginArtifacts);
935 
936             // reportArtifacts
937             Set<Artifact> reportArtifacts = new HashSet<>();
938             for (ReportPlugin report :
939                     project.getModel().getDelegate().getReporting().getPlugins()) {
940                 Plugin pp = Plugin.newBuilder()
941                         .groupId(report.getGroupId())
942                         .artifactId(report.getArtifactId())
943                         .version(report.getVersion())
944                         .build();
945 
946                 Artifact artifact = repositorySystem.createPluginArtifact(new org.apache.maven.model.Plugin(pp));
947 
948                 if (artifact != null) {
949                     reportArtifacts.add(artifact);
950                 }
951             }
952             project.setReportArtifacts(reportArtifacts);
953 
954             // extensionArtifacts
955             Set<Artifact> extensionArtifacts = new HashSet<>();
956             List<Extension> extensions =
957                     project.getModel().getDelegate().getBuild().getExtensions();
958             if (extensions != null) {
959                 for (Extension ext : extensions) {
960                     String version;
961                     if (ext.getVersion() == null || ext.getVersion().isEmpty()) {
962                         version = "RELEASE";
963                     } else {
964                         version = ext.getVersion();
965                     }
966 
967                     Artifact artifact = repositorySystem.createArtifact(
968                             ext.getGroupId(), ext.getArtifactId(), version, null, "jar");
969 
970                     if (artifact != null) {
971                         extensionArtifacts.add(artifact);
972                     }
973                 }
974             }
975             project.setExtensionArtifacts(extensionArtifacts);
976 
977             // managedVersionMap
978             Map<String, Artifact> map = Collections.emptyMap();
979             final DependencyManagement dependencyManagement =
980                     project.getModel().getDelegate().getDependencyManagement();
981             if (dependencyManagement != null
982                     && dependencyManagement.getDependencies() != null
983                     && !dependencyManagement.getDependencies().isEmpty()) {
984                 map = new LazyMap<>(() -> {
985                     Map<String, Artifact> tmp = new HashMap<>();
986                     for (Dependency d : dependencyManagement.getDependencies()) {
987                         Artifact artifact =
988                                 repositorySystem.createDependencyArtifact(new org.apache.maven.model.Dependency(d));
989                         if (artifact != null) {
990                             tmp.put(d.getManagementKey(), artifact);
991                         }
992                     }
993                     return Collections.unmodifiableMap(tmp);
994                 });
995             }
996             project.setManagedVersionMap(map);
997 
998             // release artifact repository
999             if (project.getDistributionManagement() != null
1000                     && project.getDistributionManagement().getRepository() != null) {
1001                 try {
1002                     DeploymentRepository r = project.getModel()
1003                             .getDelegate()
1004                             .getDistributionManagement()
1005                             .getRepository();
1006                     if (r.getId() != null
1007                             && !r.getId().isEmpty()
1008                             && r.getUrl() != null
1009                             && !r.getUrl().isEmpty()) {
1010                         ArtifactRepository repo = MavenRepositorySystem.buildArtifactRepository(
1011                                 new org.apache.maven.model.DeploymentRepository(r));
1012                         repositorySystem.injectProxy(request.getRepositorySession(), List.of(repo));
1013                         repositorySystem.injectAuthentication(request.getRepositorySession(), List.of(repo));
1014                         project.setReleaseArtifactRepository(repo);
1015                     }
1016                 } catch (InvalidRepositoryException e) {
1017                     throw new IllegalStateException(
1018                             "Failed to create release distribution repository for " + project.getId(), e);
1019                 }
1020             }
1021 
1022             // snapshot artifact repository
1023             if (project.getDistributionManagement() != null
1024                     && project.getDistributionManagement().getSnapshotRepository() != null) {
1025                 try {
1026                     DeploymentRepository r = project.getModel()
1027                             .getDelegate()
1028                             .getDistributionManagement()
1029                             .getSnapshotRepository();
1030                     if (r.getId() != null
1031                             && !r.getId().isEmpty()
1032                             && r.getUrl() != null
1033                             && !r.getUrl().isEmpty()) {
1034                         ArtifactRepository repo = MavenRepositorySystem.buildArtifactRepository(
1035                                 new org.apache.maven.model.DeploymentRepository(r));
1036                         repositorySystem.injectProxy(request.getRepositorySession(), List.of(repo));
1037                         repositorySystem.injectAuthentication(request.getRepositorySession(), List.of(repo));
1038                         project.setSnapshotArtifactRepository(repo);
1039                     }
1040                 } catch (InvalidRepositoryException e) {
1041                     throw new IllegalStateException(
1042                             "Failed to create snapshot distribution repository for " + project.getId(), e);
1043                 }
1044             }
1045         }
1046 
1047         private void initParent(MavenProject project, Map<File, MavenProject> projects, ModelBuilderResult result) {
1048             Model parentModel = result.getModelIds().size() > 1
1049                             && !result.getModelIds().get(1).isEmpty()
1050                     ? result.getRawModel(result.getModelIds().get(1)).orElse(null)
1051                     : null;
1052 
1053             if (parentModel != null) {
1054                 final String parentGroupId = inheritedGroupId(result, 1);
1055                 final String parentVersion = inheritedVersion(result, 1);
1056 
1057                 project.setParentArtifact(repositorySystem.createProjectArtifact(
1058                         parentGroupId, parentModel.getArtifactId(), parentVersion));
1059 
1060                 // org.apache.maven.its.mng4834:parent:0.1
1061                 String parentModelId = result.getModelIds().get(1);
1062                 Path parentPomFile =
1063                         result.getRawModel(parentModelId).map(Model::getPomFile).orElse(null);
1064                 MavenProject parent = parentPomFile != null ? projects.get(parentPomFile.toFile()) : null;
1065                 if (parent == null) {
1066                     //
1067                     // At this point the DefaultModelBuildingListener has fired and it populates the
1068                     // remote repositories with those found in the pom.xml, along with the existing externally
1069                     // defined repositories.
1070                     //
1071                     request.setRemoteRepositories(project.getRemoteArtifactRepositories());
1072                     if (parentPomFile != null) {
1073                         project.setParentFile(parentPomFile.toFile());
1074                         try {
1075                             parent = build(parentPomFile, ModelSource.fromPath(parentPomFile))
1076                                     .getProject();
1077                         } catch (ProjectBuildingException e) {
1078                             // MNG-4488 where let invalid parents slide on by
1079                             if (logger.isDebugEnabled()) {
1080                                 // Message below is checked for in the MNG-2199 core IT.
1081                                 logger.warn("Failed to build parent project for " + project.getId(), e);
1082                             } else {
1083                                 // Message below is checked for in the MNG-2199 core IT.
1084                                 logger.warn("Failed to build parent project for " + project.getId());
1085                             }
1086                         }
1087                     } else {
1088                         Artifact parentArtifact = project.getParentArtifact();
1089                         try {
1090                             parent = build(parentArtifact, false).getProject();
1091                         } catch (ProjectBuildingException e) {
1092                             // MNG-4488 where let invalid parents slide on by
1093                             if (logger.isDebugEnabled()) {
1094                                 // Message below is checked for in the MNG-2199 core IT.
1095                                 logger.warn("Failed to build parent project for " + project.getId(), e);
1096                             } else {
1097                                 // Message below is checked for in the MNG-2199 core IT.
1098                                 logger.warn("Failed to build parent project for " + project.getId());
1099                             }
1100                         }
1101                     }
1102                 }
1103                 project.setParent(parent);
1104                 if (project.getParentFile() == null && parent != null) {
1105                     project.setParentFile(parent.getFile());
1106                 }
1107             }
1108         }
1109 
1110         private ModelBuilderRequest.ModelBuilderRequestBuilder getModelBuildingRequest() {
1111             ModelBuilderRequest.ModelBuilderRequestBuilder modelBuildingRequest = ModelBuilderRequest.builder();
1112 
1113             InternalSession internalSession = InternalSession.from(session);
1114             modelBuildingRequest.session(internalSession.withRemoteRepositories(request.getRemoteRepositories().stream()
1115                     .map(r -> internalSession.getRemoteRepository(RepositoryUtils.toRepo(r)))
1116                     .toList()));
1117             modelBuildingRequest.validationLevel(request.getValidationLevel());
1118             modelBuildingRequest.processPlugins(request.isProcessPlugins());
1119             modelBuildingRequest.profiles(
1120                     request.getProfiles() != null
1121                             ? request.getProfiles().stream()
1122                                     .map(org.apache.maven.model.Profile::getDelegate)
1123                                     .toList()
1124                             : null);
1125             modelBuildingRequest.activeProfileIds(request.getActiveProfileIds());
1126             modelBuildingRequest.inactiveProfileIds(request.getInactiveProfileIds());
1127             modelBuildingRequest.systemProperties(toMap(request.getSystemProperties()));
1128             modelBuildingRequest.userProperties(toMap(request.getUserProperties()));
1129             // bv4: modelBuildingRequest.setBuildStartTime(request.getBuildStartTime());
1130             modelBuildingRequest.modelResolver(modelResolver);
1131             DefaultModelRepositoryHolder holder = new DefaultModelRepositoryHolder(
1132                     internalSession,
1133                     DefaultModelRepositoryHolder.RepositoryMerging.valueOf(
1134                             request.getRepositoryMerging().name()),
1135                     repositories.stream()
1136                             .map(internalSession::getRemoteRepository)
1137                             .toList());
1138             modelBuildingRequest.modelRepositoryHolder(holder);
1139             modelBuildingRequest.modelCache(modelCache);
1140             modelBuildingRequest.transformerContextBuilder(transformerContextBuilder);
1141             /* TODO: bv4
1142             InternalMavenSession session =
1143                     (InternalMavenSession) this.session.getData().get(InternalMavenSession.class);
1144             if (session != null) {
1145                 try {
1146                     modelBuildingRequest.setRootDirectory(session.getRootDirectory());
1147                 } catch (IllegalStateException e) {
1148                     // can happen if root directory cannot be found, just ignore
1149                 }
1150             }
1151             */
1152             internalSession.getData().set(SessionData.key(ModelResolver.class), modelResolver);
1153 
1154             return modelBuildingRequest;
1155         }
1156 
1157         private DependencyResolutionResult resolveDependencies(MavenProject project) {
1158             DependencyResolutionResult resolutionResult;
1159 
1160             try {
1161                 DefaultDependencyResolutionRequest resolution =
1162                         new DefaultDependencyResolutionRequest(project, session);
1163                 resolutionResult = dependencyResolver.resolve(resolution);
1164             } catch (DependencyResolutionException e) {
1165                 resolutionResult = e.getResult();
1166             }
1167 
1168             Set<Artifact> artifacts = new LinkedHashSet<>();
1169             if (resolutionResult.getDependencyGraph() != null) {
1170                 RepositoryUtils.toArtifacts(
1171                         artifacts,
1172                         resolutionResult.getDependencyGraph().getChildren(),
1173                         Collections.singletonList(project.getArtifact().getId()),
1174                         null);
1175 
1176                 // Maven 2.x quirk: an artifact always points at the local repo, regardless whether resolved or not
1177                 LocalRepositoryManager lrm = session.getLocalRepositoryManager();
1178                 for (Artifact artifact : artifacts) {
1179                     if (!artifact.isResolved()) {
1180                         String path = lrm.getPathForLocalArtifact(RepositoryUtils.toArtifact(artifact));
1181                         artifact.setFile(
1182                                 lrm.getRepository().getBasePath().resolve(path).toFile());
1183                     }
1184                 }
1185             }
1186             project.setResolvedArtifacts(artifacts);
1187             project.setArtifacts(artifacts);
1188 
1189             return resolutionResult;
1190         }
1191     }
1192 
1193     private List<String> getProfileIds(List<Profile> profiles) {
1194         return profiles.stream().map(Profile::getId).collect(Collectors.toList());
1195     }
1196 
1197     private static ModelSource createStubModelSource(Artifact artifact) {
1198         StringBuilder buffer = new StringBuilder(1024);
1199 
1200         buffer.append("<?xml version='1.0'?>");
1201         buffer.append("<project>");
1202         buffer.append("<modelVersion>4.0.0</modelVersion>");
1203         buffer.append("<groupId>").append(artifact.getGroupId()).append("</groupId>");
1204         buffer.append("<artifactId>").append(artifact.getArtifactId()).append("</artifactId>");
1205         buffer.append("<version>").append(artifact.getBaseVersion()).append("</version>");
1206         buffer.append("<packaging>").append(artifact.getType()).append("</packaging>");
1207         buffer.append("</project>");
1208 
1209         return new ModelSource() {
1210             @Override
1211             public ModelSource resolve(ModelLocator modelLocator, String relative) {
1212                 return null;
1213             }
1214 
1215             @Override
1216             public Path getPath() {
1217                 return null;
1218             }
1219 
1220             @Override
1221             public InputStream openStream() throws IOException {
1222                 return new ByteArrayInputStream(buffer.toString().getBytes(StandardCharsets.UTF_8));
1223             }
1224 
1225             @Override
1226             public String getLocation() {
1227                 return artifact.getId();
1228             }
1229 
1230             @Override
1231             public Source resolve(String relative) {
1232                 return null;
1233             }
1234         };
1235     }
1236 
1237     private static String inheritedGroupId(final ModelBuilderResult result, final int modelIndex) {
1238         String groupId = null;
1239         final String modelId = result.getModelIds().get(modelIndex);
1240 
1241         if (!modelId.isEmpty()) {
1242             final Model model = result.getRawModel(modelId).orElseThrow();
1243             groupId = model.getGroupId() != null ? model.getGroupId() : inheritedGroupId(result, modelIndex + 1);
1244         }
1245 
1246         return groupId;
1247     }
1248 
1249     private static String inheritedVersion(final ModelBuilderResult result, final int modelIndex) {
1250         String version = null;
1251         final String modelId = result.getModelIds().get(modelIndex);
1252 
1253         if (!modelId.isEmpty()) {
1254             version = result.getRawModel(modelId).map(Model::getVersion).orElse(null);
1255             if (version == null) {
1256                 version = inheritedVersion(result, modelIndex + 1);
1257             }
1258         }
1259 
1260         return version;
1261     }
1262 
1263     private static Map<String, String> toMap(Properties properties) {
1264         if (properties != null && !properties.isEmpty()) {
1265             return properties.entrySet().stream()
1266                     .collect(Collectors.toMap(e -> String.valueOf(e.getKey()), e -> String.valueOf(e.getValue())));
1267 
1268         } else {
1269             return null;
1270         }
1271     }
1272 
1273     @SuppressWarnings("unchecked")
1274     static <T extends Throwable> void uncheckedThrow(Throwable t) throws T {
1275         throw (T) t; // rely on vacuous cast
1276     }
1277 
1278     static class LazyMap<K, V> extends AbstractMap<K, V> {
1279         private final Supplier<Map<K, V>> supplier;
1280         private volatile Map<K, V> delegate;
1281 
1282         LazyMap(Supplier<Map<K, V>> supplier) {
1283             this.supplier = supplier;
1284         }
1285 
1286         @Override
1287         public Set<Entry<K, V>> entrySet() {
1288             if (delegate == null) {
1289                 synchronized (this) {
1290                     if (delegate == null) {
1291                         delegate = supplier.get();
1292                     }
1293                 }
1294             }
1295             return delegate.entrySet();
1296         }
1297     }
1298 
1299     protected abstract class ModelResolverWrapper implements ModelResolver {
1300 
1301         protected abstract org.apache.maven.model.resolution.ModelResolver getResolver(
1302                 List<RemoteRepository> repositories);
1303 
1304         @Override
1305         public ModelSource resolveModel(
1306                 Session session, String groupId, String artifactId, String version, Consumer<String> resolved)
1307                 throws ModelResolverException {
1308             try {
1309                 InternalSession internalSession = InternalSession.from(session);
1310                 org.apache.maven.model.resolution.ModelResolver resolver =
1311                         getResolver(internalSession.toRepositories(internalSession.getRemoteRepositories()));
1312                 org.apache.maven.model.Parent p = new org.apache.maven.model.Parent(Parent.newBuilder()
1313                         .groupId(groupId)
1314                         .artifactId(artifactId)
1315                         .version(version)
1316                         .build());
1317                 org.apache.maven.model.building.ModelSource modelSource = resolver.resolveModel(p);
1318                 if (!p.getVersion().equals(version)) {
1319                     resolved.accept(p.getVersion());
1320                 }
1321                 return toSource(modelSource);
1322             } catch (UnresolvableModelException e) {
1323                 throw new ModelResolverException(e.getMessage(), e.getGroupId(), e.getArtifactId(), e.getVersion(), e);
1324             }
1325         }
1326 
1327         @Override
1328         public ModelSource resolveModel(Session session, Parent parent, AtomicReference<Parent> modified)
1329                 throws ModelResolverException {
1330             try {
1331                 org.apache.maven.model.Parent p = new org.apache.maven.model.Parent(parent);
1332                 InternalSession internalSession = InternalSession.from(session);
1333                 org.apache.maven.model.resolution.ModelResolver resolver =
1334                         getResolver(internalSession.toRepositories(internalSession.getRemoteRepositories()));
1335                 ModelSource source = toSource(resolver.resolveModel(p));
1336                 if (p.getDelegate() != parent) {
1337                     modified.set(p.getDelegate());
1338                 }
1339                 return source;
1340             } catch (UnresolvableModelException e) {
1341                 throw new ModelResolverException(e.getMessage(), e.getGroupId(), e.getArtifactId(), e.getVersion(), e);
1342             }
1343         }
1344 
1345         @Override
1346         public ModelSource resolveModel(Session session, Dependency dependency, AtomicReference<Dependency> modified)
1347                 throws ModelResolverException {
1348             try {
1349                 org.apache.maven.model.Dependency d = new org.apache.maven.model.Dependency(dependency);
1350                 InternalSession internalSession = InternalSession.from(session);
1351                 org.apache.maven.model.resolution.ModelResolver resolver =
1352                         getResolver(internalSession.toRepositories(internalSession.getRemoteRepositories()));
1353                 ModelSource source = toSource(resolver.resolveModel(d));
1354                 if (d.getDelegate() != dependency) {
1355                     modified.set(d.getDelegate());
1356                 }
1357                 return source;
1358             } catch (UnresolvableModelException e) {
1359                 throw new ModelResolverException(e.getMessage(), e.getGroupId(), e.getArtifactId(), e.getVersion(), e);
1360             }
1361         }
1362     }
1363 }