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.Files;
31  import java.nio.file.Path;
32  import java.util.AbstractMap;
33  import java.util.ArrayList;
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.Objects;
41  import java.util.Properties;
42  import java.util.Set;
43  import java.util.concurrent.ConcurrentHashMap;
44  import java.util.function.Function;
45  import java.util.function.Supplier;
46  import java.util.stream.Collectors;
47  import java.util.stream.Stream;
48  
49  import org.apache.maven.RepositoryUtils;
50  import org.apache.maven.api.ArtifactCoordinates;
51  import org.apache.maven.api.Language;
52  import org.apache.maven.api.LocalRepository;
53  import org.apache.maven.api.ProjectScope;
54  import org.apache.maven.api.SessionData;
55  import org.apache.maven.api.annotations.Nonnull;
56  import org.apache.maven.api.annotations.Nullable;
57  import org.apache.maven.api.model.Build;
58  import org.apache.maven.api.model.Dependency;
59  import org.apache.maven.api.model.DependencyManagement;
60  import org.apache.maven.api.model.DeploymentRepository;
61  import org.apache.maven.api.model.Extension;
62  import org.apache.maven.api.model.Model;
63  import org.apache.maven.api.model.Plugin;
64  import org.apache.maven.api.model.Profile;
65  import org.apache.maven.api.model.ReportPlugin;
66  import org.apache.maven.api.services.ArtifactResolver;
67  import org.apache.maven.api.services.ArtifactResolverException;
68  import org.apache.maven.api.services.ArtifactResolverRequest;
69  import org.apache.maven.api.services.ArtifactResolverResult;
70  import org.apache.maven.api.services.BuilderProblem.Severity;
71  import org.apache.maven.api.services.ModelBuilder;
72  import org.apache.maven.api.services.ModelBuilderException;
73  import org.apache.maven.api.services.ModelBuilderRequest;
74  import org.apache.maven.api.services.ModelBuilderResult;
75  import org.apache.maven.api.services.ModelProblem;
76  import org.apache.maven.api.services.ModelProblem.Version;
77  import org.apache.maven.api.services.ModelProblemCollector;
78  import org.apache.maven.api.services.ModelSource;
79  import org.apache.maven.api.services.ModelTransformer;
80  import org.apache.maven.api.services.ProblemCollector;
81  import org.apache.maven.api.services.Source;
82  import org.apache.maven.api.services.Sources;
83  import org.apache.maven.api.services.model.LifecycleBindingsInjector;
84  import org.apache.maven.artifact.Artifact;
85  import org.apache.maven.artifact.InvalidRepositoryException;
86  import org.apache.maven.artifact.repository.ArtifactRepository;
87  import org.apache.maven.bridge.MavenRepositorySystem;
88  import org.apache.maven.impl.DefaultSourceRoot;
89  import org.apache.maven.impl.InternalSession;
90  import org.apache.maven.impl.resolver.ArtifactDescriptorUtils;
91  import org.apache.maven.internal.impl.InternalMavenSession;
92  import org.apache.maven.model.building.DefaultModelProblem;
93  import org.apache.maven.model.building.FileModelSource;
94  import org.apache.maven.model.building.ModelBuildingRequest;
95  import org.apache.maven.model.building.ModelSource2;
96  import org.apache.maven.model.root.RootLocator;
97  import org.apache.maven.plugin.PluginManagerException;
98  import org.apache.maven.plugin.PluginResolutionException;
99  import org.apache.maven.plugin.version.PluginVersionResolutionException;
100 import org.eclipse.aether.RepositorySystem;
101 import org.eclipse.aether.RepositorySystemSession;
102 import org.eclipse.aether.repository.LocalRepositoryManager;
103 import org.slf4j.Logger;
104 import org.slf4j.LoggerFactory;
105 
106 /**
107  * DefaultProjectBuilder
108  */
109 @Named
110 @Singleton
111 public class DefaultProjectBuilder implements ProjectBuilder {
112 
113     private final Logger logger = LoggerFactory.getLogger(getClass());
114     private final ModelBuilder modelBuilder;
115     private final ProjectBuildingHelper projectBuildingHelper;
116     private final MavenRepositorySystem repositorySystem;
117     private final ProjectDependenciesResolver dependencyResolver;
118     private final RootLocator rootLocator;
119     private final LifecycleBindingsInjector lifecycleBindingsInjector;
120 
121     @SuppressWarnings("checkstyle:ParameterNumber")
122     @Inject
123     public DefaultProjectBuilder(
124             ModelBuilder modelBuilder,
125             ProjectBuildingHelper projectBuildingHelper,
126             MavenRepositorySystem repositorySystem,
127             RepositorySystem repoSystem,
128             ProjectDependenciesResolver dependencyResolver,
129             RootLocator rootLocator,
130             LifecycleBindingsInjector lifecycleBindingsInjector) {
131         this.modelBuilder = modelBuilder;
132         this.projectBuildingHelper = projectBuildingHelper;
133         this.repositorySystem = repositorySystem;
134         this.dependencyResolver = dependencyResolver;
135         this.rootLocator = rootLocator;
136         this.lifecycleBindingsInjector = lifecycleBindingsInjector;
137     }
138     // ----------------------------------------------------------------------
139     // MavenProjectBuilder Implementation
140     // ----------------------------------------------------------------------
141 
142     @Override
143     public ProjectBuildingResult build(File pomFile, ProjectBuildingRequest request) throws ProjectBuildingException {
144         try (BuildSession bs = new BuildSession(request)) {
145             Path path = pomFile.toPath();
146             return bs.build(false, path, Sources.buildSource(path));
147         }
148     }
149 
150     @Deprecated
151     @Override
152     public ProjectBuildingResult build(
153             org.apache.maven.model.building.ModelSource modelSource, ProjectBuildingRequest request)
154             throws ProjectBuildingException {
155         return build(toSource(modelSource), request);
156     }
157 
158     @Deprecated
159     static ModelSource toSource(org.apache.maven.model.building.ModelSource modelSource) {
160         if (modelSource instanceof FileModelSource fms) {
161             return Sources.buildSource(fms.getPath());
162         } else {
163             return new WrapModelSource(modelSource);
164         }
165     }
166 
167     @Override
168     public ProjectBuildingResult build(ModelSource modelSource, ProjectBuildingRequest request)
169             throws ProjectBuildingException {
170         try (BuildSession bs = new BuildSession(request)) {
171             return bs.build(false, null, modelSource);
172         }
173     }
174 
175     @Override
176     public ProjectBuildingResult build(Artifact artifact, ProjectBuildingRequest request)
177             throws ProjectBuildingException {
178         return build(artifact, false, request);
179     }
180 
181     @Override
182     public ProjectBuildingResult build(Artifact artifact, boolean allowStubModel, ProjectBuildingRequest request)
183             throws ProjectBuildingException {
184         try (BuildSession bs = new BuildSession(request)) {
185             return bs.build(false, artifact, allowStubModel, request.getRemoteRepositories());
186         }
187     }
188 
189     @Override
190     public List<ProjectBuildingResult> build(List<File> pomFiles, boolean recursive, ProjectBuildingRequest request)
191             throws ProjectBuildingException {
192         try (BuildSession bs = new BuildSession(request)) {
193             return bs.build(pomFiles, recursive);
194         }
195     }
196 
197     private static class StubModelSource implements ModelSource {
198         private final String xml;
199         private final Artifact artifact;
200 
201         StubModelSource(String xml, Artifact artifact) {
202             this.xml = xml;
203             this.artifact = artifact;
204         }
205 
206         @Override
207         @Nullable
208         public ModelSource resolve(@Nonnull ModelLocator modelLocator, @Nonnull String relative) {
209             return null;
210         }
211 
212         @Override
213         @Nullable
214         public Path getPath() {
215             return null;
216         }
217 
218         @Override
219         @Nonnull
220         public InputStream openStream() throws IOException {
221             return new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
222         }
223 
224         @Override
225         @Nonnull
226         public String getLocation() {
227             return artifact.getId();
228         }
229 
230         @Override
231         @Nullable
232         public Source resolve(@Nonnull String relative) {
233             return null;
234         }
235 
236         @Override
237         public boolean equals(Object o) {
238             if (this == o) {
239                 return true;
240             }
241             if (o == null || getClass() != o.getClass()) {
242                 return false;
243             }
244             StubModelSource that = (StubModelSource) o;
245             return Objects.equals(xml, that.xml) && Objects.equals(artifact, that.artifact);
246         }
247 
248         @Override
249         public int hashCode() {
250             return Objects.hash(xml, artifact);
251         }
252     }
253 
254     private static class WrapModelSource implements ModelSource {
255         private final org.apache.maven.model.building.ModelSource modelSource;
256 
257         WrapModelSource(org.apache.maven.model.building.ModelSource modelSource) {
258             this.modelSource = modelSource;
259         }
260 
261         @Override
262         @Nullable
263         public ModelSource resolve(@Nonnull ModelLocator modelLocator, @Nonnull String relative) {
264             return null;
265         }
266 
267         @Override
268         @Nullable
269         public Path getPath() {
270             return null;
271         }
272 
273         @Override
274         @Nonnull
275         public InputStream openStream() throws IOException {
276             return modelSource.getInputStream();
277         }
278 
279         @Override
280         @Nonnull
281         public String getLocation() {
282             return modelSource.getLocation();
283         }
284 
285         @Override
286         @Nullable
287         public Source resolve(@Nonnull String relative) {
288             if (modelSource instanceof ModelSource2 ms) {
289                 return toSource(ms.getRelatedSource(relative));
290             } else {
291                 return null;
292             }
293         }
294 
295         @Override
296         public boolean equals(Object o) {
297             if (this == o) {
298                 return true;
299             }
300             if (o == null || getClass() != o.getClass()) {
301                 return false;
302             }
303             WrapModelSource that = (WrapModelSource) o;
304             return Objects.equals(modelSource, that.modelSource);
305         }
306 
307         @Override
308         public int hashCode() {
309             return Objects.hashCode(modelSource);
310         }
311     }
312 
313     class BuildSession implements AutoCloseable {
314         private final ProjectBuildingRequest request;
315         private final InternalSession session;
316         private final ModelBuilder.ModelBuilderSession modelBuilderSession;
317         private final Map<String, MavenProject> projectIndex = new ConcurrentHashMap<>(256);
318 
319         // Store computed repositories per project to avoid leakage between projects
320         private final Map<String, List<ArtifactRepository>> projectRepositories = new ConcurrentHashMap<>();
321 
322         /**
323          * Get the effective repositories for a project. If project-specific repositories
324          * have been computed and stored, use those; otherwise fall back to request repositories.
325          */
326         private List<ArtifactRepository> getEffectiveRepositories(String projectId) {
327             List<ArtifactRepository> stored = projectRepositories.get(projectId);
328             return stored != null ? stored : request.getRemoteRepositories();
329         }
330 
331         BuildSession(ProjectBuildingRequest request) {
332             this.request = request;
333             InternalSession session = InternalSession.from(request.getRepositorySession());
334             Path basedir = request.getLocalRepository() != null
335                     ? request.getLocalRepository().getBasedirPath()
336                     : null;
337             if (basedir != null) {
338                 LocalRepository localRepository = session.createLocalRepository(basedir);
339                 session = InternalSession.from(session.withLocalRepository(localRepository));
340             }
341             this.session = session;
342             this.modelBuilderSession = modelBuilder.newSession();
343             // Save the ModelBuilderSession for later retrieval by the DefaultConsumerPomBuilder.
344             // Use replace(key, null, value) to make sure the *main* session, i.e. the one used
345             // to load the projects, is stored. This is to avoid the session being overwritten
346             // if a plugin uses the ProjectBuilder.
347             this.session
348                     .getData()
349                     .replace(SessionData.key(ModelBuilder.ModelBuilderSession.class), null, modelBuilderSession);
350         }
351 
352         @Override
353         public void close() {}
354 
355         ProjectBuildingResult build(boolean parent, Path pomFile, ModelSource modelSource)
356                 throws ProjectBuildingException {
357             ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
358 
359             try {
360                 MavenProject project = request.getProject();
361 
362                 ProblemCollector<ModelProblem> problemCollector = null;
363                 Throwable error = null;
364 
365                 if (project == null) {
366                     project = new MavenProject();
367                     project.setFile(pomFile != null ? pomFile.toFile() : null);
368 
369                     boolean reactorMember = pomFile != null
370                             && session.getProjects() != null // this is for UTs
371                             && session.getProjects().stream()
372                                     .anyMatch(
373                                             p -> p.getPomPath().toAbsolutePath().equals(pomFile.toAbsolutePath()));
374                     boolean isStandalone = pomFile == null
375                             && modelSource != null
376                             && modelSource.getLocation().startsWith("jar:")
377                             && modelSource.getLocation().endsWith("/org/apache/maven/project/standalone.xml");
378 
379                     ModelBuilderRequest.ModelBuilderRequestBuilder builder = getModelBuildingRequest();
380                     ModelBuilderRequest.RequestType type = reactorMember
381                                     || isStandalone
382                                     || (pomFile != null
383                                             && this.request.isProcessPlugins()
384                                             && this.request.getValidationLevel()
385                                                     == ModelBuildingRequest.VALIDATION_LEVEL_STRICT)
386                             ? ModelBuilderRequest.RequestType.BUILD_EFFECTIVE
387                             : (parent
388                                     ? ModelBuilderRequest.RequestType.CONSUMER_PARENT
389                                     : ModelBuilderRequest.RequestType.CONSUMER_DEPENDENCY);
390                     MavenProject theProject = project;
391                     ModelBuilderRequest request = builder.source(modelSource)
392                             .requestType(type)
393                             .locationTracking(true)
394                             .lifecycleBindingsInjector(
395                                     (m, r, p) -> injectLifecycleBindings(m, r, p, theProject, this.request))
396                             .build();
397 
398                     if (pomFile != null) {
399                         project.setRootDirectory(rootLocator.findRoot(pomFile.getParent()));
400                     }
401 
402                     ModelBuilderResult result;
403                     try {
404                         result = modelBuilderSession.build(request);
405                     } catch (ModelBuilderException e) {
406                         result = e.getResult();
407                         if (result == null || result.getEffectiveModel() == null) {
408                             throw new ProjectBuildingException(
409                                     e.getModelId(), e.getMessage(), pomFile != null ? pomFile.toFile() : null, e);
410                         }
411                         // validation error, continue project building and delay failing to help IDEs
412                         error = e;
413                     }
414 
415                     problemCollector = result.getProblemCollector();
416 
417                     initProject(project, result);
418                 }
419 
420                 DependencyResolutionResult resolutionResult = null;
421 
422                 if (request.isResolveDependencies()) {
423                     projectBuildingHelper.selectProjectRealm(project);
424                     resolutionResult = resolveDependencies(project);
425                 }
426 
427                 ProjectBuildingResult result =
428                         new DefaultProjectBuildingResult(project, convert(problemCollector), resolutionResult);
429 
430                 if (error != null) {
431                     ProjectBuildingException e = new ProjectBuildingException(List.of(result));
432                     e.initCause(error);
433                     throw e;
434                 }
435 
436                 return result;
437             } finally {
438                 Thread.currentThread().setContextClassLoader(oldContextClassLoader);
439             }
440         }
441 
442         ProjectBuildingResult build(
443                 boolean parent, Artifact artifact, boolean allowStubModel, List<ArtifactRepository> repositories)
444                 throws ProjectBuildingException {
445             org.eclipse.aether.artifact.Artifact pomArtifact = RepositoryUtils.toArtifact(artifact);
446             pomArtifact = ArtifactDescriptorUtils.toPomArtifact(pomArtifact);
447 
448             boolean localProject;
449 
450             try {
451                 ArtifactCoordinates coordinates = session.createArtifactCoordinates(session.getArtifact(pomArtifact));
452                 // Use provided repositories if available, otherwise fall back to request repositories
453                 ArtifactResolverRequest req = ArtifactResolverRequest.builder()
454                         .session(session)
455                         .repositories(repositories.stream()
456                                 .map(RepositoryUtils::toRepo)
457                                 .map(session::getRemoteRepository)
458                                 .toList())
459                         .coordinates(List.of(coordinates))
460                         .build();
461                 ArtifactResolverResult res =
462                         session.getService(ArtifactResolver.class).resolve(req);
463                 ArtifactResolverResult.ResultItem resItem = res.getResult(coordinates);
464 
465                 pomArtifact = InternalMavenSession.from(session).toArtifact(resItem.getArtifact());
466                 localProject = resItem.getRepository() instanceof org.apache.maven.api.WorkspaceRepository;
467             } catch (ArtifactResolverException e) {
468                 if (e.getResult().getResults().values().iterator().next().isMissing() && allowStubModel) {
469                     return build(parent, null, createStubModelSource(artifact));
470                 }
471                 throw new ProjectBuildingException(
472                         artifact.getId(), "Error resolving project artifact: " + e.getMessage(), e);
473             }
474 
475             Path pomFile = pomArtifact.getPath();
476 
477             if (!artifact.isResolved() && "pom".equals(artifact.getType())) {
478                 artifact.selectVersion(pomArtifact.getVersion());
479                 artifact.setFile(pomFile.toFile());
480                 artifact.setResolved(true);
481             }
482 
483             if (localProject) {
484                 return build(parent, pomFile, Sources.buildSource(pomFile));
485             } else {
486                 return build(
487                         parent,
488                         null,
489                         Sources.resolvedSource(
490                                 pomFile,
491                                 artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getVersion()));
492             }
493         }
494 
495         List<ProjectBuildingResult> build(List<File> pomFiles, boolean recursive) throws ProjectBuildingException {
496             List<ProjectBuildingResult> results = doBuild(pomFiles, recursive);
497             if (results.stream()
498                     .flatMap(r -> r.getProblems().stream())
499                     .anyMatch(p -> p.getSeverity() != org.apache.maven.model.building.ModelProblem.Severity.WARNING)) {
500                 org.apache.maven.model.building.ModelProblem cycle = results.stream()
501                         .flatMap(r -> r.getProblems().stream())
502                         .filter(p -> p.getException() instanceof CycleDetectedException)
503                         .findAny()
504                         .orElse(null);
505                 if (cycle != null) {
506                     final CycleDetectedException cde = (CycleDetectedException) cycle.getException();
507                     throw new ProjectBuildingException(
508                             null,
509                             "The projects in the reactor contain a cyclic reference: " + cycle.getMessage(),
510                             null,
511                             cde);
512                 }
513 
514                 throw new ProjectBuildingException(results);
515             }
516 
517             return results;
518         }
519 
520         List<ProjectBuildingResult> doBuild(List<File> pomFiles, boolean recursive) {
521             ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
522             try {
523                 return pomFiles.stream()
524                         .map(pomFile -> build(pomFile, recursive))
525                         .flatMap(List::stream)
526                         .toList();
527             } finally {
528                 Thread.currentThread().setContextClassLoader(oldContextClassLoader);
529             }
530         }
531 
532         @SuppressWarnings("checkstyle:parameternumber")
533         private List<ProjectBuildingResult> build(File pomFile, boolean recursive) {
534             ModelBuilderResult result;
535             try {
536                 ModelTransformer injector = (m, r, p) -> {
537                     MavenProject project = projectIndex.computeIfAbsent(m.getId(), f -> new MavenProject());
538                     return injectLifecycleBindings(m, r, p, project, request);
539                 };
540                 ModelBuilderRequest modelBuildingRequest = getModelBuildingRequest()
541                         .source(Sources.buildSource(pomFile.toPath()))
542                         .requestType(ModelBuilderRequest.RequestType.BUILD_PROJECT)
543                         .locationTracking(true)
544                         .recursive(recursive)
545                         .lifecycleBindingsInjector(injector)
546                         .build();
547                 result = modelBuilderSession.build(modelBuildingRequest);
548             } catch (ModelBuilderException e) {
549                 result = e.getResult();
550                 if (result == null || result.getEffectiveModel() == null) {
551                     return List.of(new DefaultProjectBuildingResult(
552                             e.getModelId(), pomFile, convert(e.getProblemCollector())));
553                 }
554             }
555 
556             List<ProjectBuildingResult> results = new ArrayList<>();
557             List<ModelBuilderResult> allModels = results(result).toList();
558             for (ModelBuilderResult r : allModels) {
559                 if (r.getEffectiveModel() != null) {
560                     File pom = r.getSource().getPath().toFile();
561                     MavenProject project =
562                             projectIndex.get(r.getEffectiveModel().getId());
563                     project.setRootDirectory(
564                             rootLocator.findRoot(pom.getParentFile().toPath()));
565                     project.setFile(pom);
566                     project.setExecutionRoot(pom.equals(pomFile));
567                     initProject(project, r);
568                     project.setCollectedProjects(results(r)
569                             .filter(cr -> cr != r && cr.getEffectiveModel() != null)
570                             .map(cr -> projectIndex.get(cr.getEffectiveModel().getId()))
571                             .toList());
572 
573                     DependencyResolutionResult resolutionResult = null;
574                     if (request.isResolveDependencies()) {
575                         resolutionResult = resolveDependencies(project);
576                     }
577                     results.add(new DefaultProjectBuildingResult(
578                             project, convert(r.getProblemCollector()), resolutionResult));
579                 } else {
580                     // Extract project identification even when effective model is null
581                     String projectId = extractProjectId(r);
582                     File sourcePomFile = r.getSource() != null && r.getSource().getPath() != null
583                             ? r.getSource().getPath().toFile()
584                             : null;
585                     results.add(new DefaultProjectBuildingResult(
586                             projectId, sourcePomFile, convert(r.getProblemCollector())));
587                 }
588             }
589             return results;
590         }
591 
592         private Stream<ModelBuilderResult> results(ModelBuilderResult result) {
593             return Stream.concat(result.getChildren().stream().flatMap(this::results), Stream.of(result));
594         }
595 
596         private List<org.apache.maven.model.building.ModelProblem> convert(
597                 ProblemCollector<ModelProblem> problemCollector) {
598             if (problemCollector == null) {
599                 return null;
600             }
601             ArrayList<org.apache.maven.model.building.ModelProblem> problems = new ArrayList<>();
602             problemCollector.problems().map(BuildSession::convert).forEach(problems::add);
603             if (problemCollector.problemsOverflow()) {
604                 problems.add(
605                         0,
606                         new DefaultModelProblem(
607                                 "Too many model problems reported (listed problems are just a subset of reported problems)",
608                                 org.apache.maven.model.building.ModelProblem.Severity.WARNING,
609                                 null,
610                                 null,
611                                 -1,
612                                 -1,
613                                 null,
614                                 null));
615                 return new ArrayList<>(problems) {
616                     @Override
617                     public int size() {
618                         return problemCollector.totalProblemsReported();
619                     }
620                 };
621             } else {
622                 return problems;
623             }
624         }
625 
626         private static org.apache.maven.model.building.ModelProblem convert(ModelProblem p) {
627             return new DefaultModelProblem(
628                     p.getMessage(),
629                     org.apache.maven.model.building.ModelProblem.Severity.valueOf(
630                             p.getSeverity().name()),
631                     org.apache.maven.model.building.ModelProblem.Version.valueOf(
632                             p.getVersion().name()),
633                     p.getSource(),
634                     p.getLineNumber(),
635                     p.getColumnNumber(),
636                     p.getModelId(),
637                     p.getException());
638         }
639 
640         @SuppressWarnings({"checkstyle:methodlength", "deprecation"})
641         private void initProject(MavenProject project, ModelBuilderResult result) {
642             project.setModel(new org.apache.maven.model.Model(result.getEffectiveModel()));
643             project.setOriginalModel(new org.apache.maven.model.Model(result.getFileModel()));
644 
645             initParent(project, result);
646 
647             Artifact projectArtifact = repositorySystem.createArtifact(
648                     project.getGroupId(), project.getArtifactId(), project.getVersion(), null, project.getPackaging());
649             project.setArtifact(projectArtifact);
650 
651             // only set those on 2nd phase, ignore on 1st pass
652             if (project.getFile() != null) {
653                 Build build = project.getBuild().getDelegate();
654                 Path baseDir = project.getBaseDirectory();
655                 Function<ProjectScope, String> outputDirectory = (scope) -> {
656                     if (scope == ProjectScope.MAIN) {
657                         return build.getOutputDirectory();
658                     } else if (scope == ProjectScope.TEST) {
659                         return build.getTestOutputDirectory();
660                     } else {
661                         return build.getDirectory();
662                     }
663                 };
664                 // Create source handling context for unified tracking of all lang/scope combinations
665                 final SourceHandlingContext sourceContext = new SourceHandlingContext(project, result);
666 
667                 // Process all sources, tracking enabled ones and detecting duplicates
668                 for (org.apache.maven.api.model.Source source : sourceContext.sources) {
669                     var sourceRoot = DefaultSourceRoot.fromModel(session, baseDir, outputDirectory, source);
670                     // Track enabled sources for duplicate detection and hasSources() queries
671                     // Only add source if it's not a duplicate enabled source (first enabled wins)
672                     if (sourceContext.shouldAddSource(sourceRoot)) {
673                         project.addSourceRoot(sourceRoot);
674                     }
675                 }
676 
677                 /*
678                   Source directory handling depends on project type and <sources> configuration:
679 
680                   1. CLASSIC projects (no <sources>):
681                      - All legacy directories are used
682 
683                   2. MODULAR projects (have <module> in <sources>):
684                      - ALL legacy directories cause the build to fail (cannot dispatch
685                        between modules)
686                      - The build also fails if default directories (src/main/java)
687                        physically exist on the filesystem
688 
689                   3. NON-MODULAR projects with <sources>:
690                      - Explicit legacy directories (differ from default) always cause
691                        the build to fail
692                      - Legacy directories for scopes where <sources> defines Java are ignored
693                      - Legacy directories for scopes where <sources> has no Java serve as
694                        implicit fallback (only if they match the default, e.g., inherited)
695                      - This allows incremental adoption (e.g., custom resources + default Java)
696                 */
697                 if (sourceContext.sources.isEmpty()) {
698                     // Classic fallback: no <sources> configured, use legacy directories
699                     project.addScriptSourceRoot(build.getScriptSourceDirectory());
700                     project.addCompileSourceRoot(build.getSourceDirectory());
701                     project.addTestCompileSourceRoot(build.getTestSourceDirectory());
702                     // Handle resources using legacy configuration
703                     sourceContext.handleResourceConfiguration(ProjectScope.MAIN);
704                     sourceContext.handleResourceConfiguration(ProjectScope.TEST);
705                 } else {
706                     // Add script source root if no <sources lang="script"> configured
707                     if (!sourceContext.hasSources(Language.SCRIPT, ProjectScope.MAIN)) {
708                         project.addScriptSourceRoot(build.getScriptSourceDirectory());
709                     }
710                     if (sourceContext.usesModuleSourceHierarchy()) {
711                         // Modular: reject ALL legacy directory configurations
712                         failIfLegacyDirectoryPresent(
713                                 build.getSourceDirectory(),
714                                 baseDir.resolve("src/main/java"),
715                                 "<sourceDirectory>",
716                                 project.getId(),
717                                 result,
718                                 true); // check physical presence
719                         failIfLegacyDirectoryPresent(
720                                 build.getTestSourceDirectory(),
721                                 baseDir.resolve("src/test/java"),
722                                 "<testSourceDirectory>",
723                                 project.getId(),
724                                 result,
725                                 true); // check physical presence
726                     } else {
727                         // Non-modular: always validate legacy directories (error if differs from default)
728                         Path mainDefault = baseDir.resolve("src/main/java");
729                         Path testDefault = baseDir.resolve("src/test/java");
730 
731                         failIfLegacyDirectoryPresent(
732                                 build.getSourceDirectory(),
733                                 mainDefault,
734                                 "<sourceDirectory>",
735                                 project.getId(),
736                                 result,
737                                 false); // no physical presence check
738                         failIfLegacyDirectoryPresent(
739                                 build.getTestSourceDirectory(),
740                                 testDefault,
741                                 "<testSourceDirectory>",
742                                 project.getId(),
743                                 result,
744                                 false); // no physical presence check
745 
746                         // Use legacy as fallback only if:
747                         // 1. <sources> doesn't have Java for this scope
748                         // 2. Legacy matches default (otherwise error was reported above)
749                         if (!sourceContext.hasSources(Language.JAVA_FAMILY, ProjectScope.MAIN)) {
750                             Path configuredMain = Path.of(build.getSourceDirectory())
751                                     .toAbsolutePath()
752                                     .normalize();
753                             if (configuredMain.equals(
754                                     mainDefault.toAbsolutePath().normalize())) {
755                                 project.addCompileSourceRoot(build.getSourceDirectory());
756                             }
757                         }
758                         if (!sourceContext.hasSources(Language.JAVA_FAMILY, ProjectScope.TEST)) {
759                             Path configuredTest = Path.of(build.getTestSourceDirectory())
760                                     .toAbsolutePath()
761                                     .normalize();
762                             if (configuredTest.equals(
763                                     testDefault.toAbsolutePath().normalize())) {
764                                 project.addTestCompileSourceRoot(build.getTestSourceDirectory());
765                             }
766                         }
767                     }
768 
769                     // Fail if modular and classic sources are mixed within <sources>
770                     sourceContext.failIfMixedModularAndClassicSources();
771 
772                     // Handle main and test resources using unified source handling
773                     sourceContext.handleResourceConfiguration(ProjectScope.MAIN);
774                     sourceContext.handleResourceConfiguration(ProjectScope.TEST);
775                 }
776 
777                 // When resources are defined via <sources> (4.1.0 model), sync them to
778                 // the model's Build so project.getBuild().getResources() is consistent.
779                 // For legacy <resources>, the model already has the correct resources.
780                 if (sourceContext.hasSources(Language.RESOURCES, ProjectScope.MAIN)
781                         || sourceContext.hasSources(Language.RESOURCES, ProjectScope.TEST)) {
782                     project.syncBuildResources();
783                 }
784             }
785 
786             project.setActiveProfiles(
787                     Stream.concat(result.getActivePomProfiles().stream(), result.getActiveExternalProfiles().stream())
788                             .map(org.apache.maven.model.Profile::new)
789                             .toList());
790 
791             project.setInjectedProfileIds("external", getProfileIds(result.getActiveExternalProfiles()));
792 
793             // Track profile sources correctly by using the per-model profile tracking
794             Map<String, List<org.apache.maven.api.model.Profile>> profilesByModel =
795                     result.getActivePomProfilesByModel();
796 
797             if (profilesByModel.isEmpty()) {
798                 // Fallback to old behavior if map is empty
799                 // This happens when no profiles are active or there's an issue with profile tracking
800                 project.setInjectedProfileIds(
801                         getModelDataId(result.getEffectiveModel()), getProfileIds(result.getActivePomProfiles()));
802             } else {
803                 for (Map.Entry<String, List<org.apache.maven.api.model.Profile>> entry : profilesByModel.entrySet()) {
804                     project.setInjectedProfileIds(entry.getKey(), getProfileIds(entry.getValue()));
805                 }
806             }
807 
808             //
809             // All the parts that were taken out of MavenProject for Maven 4.0.0
810             //
811 
812             project.setProjectBuildingRequest(request);
813 
814             // pluginArtifacts
815             Set<Artifact> pluginArtifacts = new HashSet<>();
816             for (Plugin plugin : project.getModel().getDelegate().getBuild().getPlugins()) {
817                 Artifact artifact = repositorySystem.createPluginArtifact(new org.apache.maven.model.Plugin(plugin));
818 
819                 if (artifact != null) {
820                     pluginArtifacts.add(artifact);
821                 }
822             }
823             project.setPluginArtifacts(pluginArtifacts);
824 
825             // reportArtifacts
826             Set<Artifact> reportArtifacts = new HashSet<>();
827             for (ReportPlugin report :
828                     project.getModel().getDelegate().getReporting().getPlugins()) {
829                 Plugin pp = Plugin.newBuilder()
830                         .groupId(report.getGroupId())
831                         .artifactId(report.getArtifactId())
832                         .version(report.getVersion())
833                         .build();
834 
835                 Artifact artifact = repositorySystem.createPluginArtifact(new org.apache.maven.model.Plugin(pp));
836 
837                 if (artifact != null) {
838                     reportArtifacts.add(artifact);
839                 }
840             }
841             project.setReportArtifacts(reportArtifacts);
842 
843             // extensionArtifacts
844             Set<Artifact> extensionArtifacts = new HashSet<>();
845             List<Extension> extensions =
846                     project.getModel().getDelegate().getBuild().getExtensions();
847             if (extensions != null) {
848                 for (Extension ext : extensions) {
849                     String version;
850                     if (ext.getVersion() == null || ext.getVersion().isEmpty()) {
851                         version = "RELEASE";
852                     } else {
853                         version = ext.getVersion();
854                     }
855 
856                     Artifact artifact = repositorySystem.createArtifact(
857                             ext.getGroupId(), ext.getArtifactId(), version, null, "jar");
858 
859                     if (artifact != null) {
860                         extensionArtifacts.add(artifact);
861                     }
862                 }
863             }
864             project.setExtensionArtifacts(extensionArtifacts);
865 
866             // managedVersionMap
867             Map<String, Artifact> map = Collections.emptyMap();
868             final DependencyManagement dependencyManagement =
869                     project.getModel().getDelegate().getDependencyManagement();
870             if (dependencyManagement != null
871                     && dependencyManagement.getDependencies() != null
872                     && !dependencyManagement.getDependencies().isEmpty()) {
873                 map = new LazyMap<>(() -> {
874                     Map<String, Artifact> tmp = new HashMap<>();
875                     for (Dependency d : dependencyManagement.getDependencies()) {
876                         Artifact artifact =
877                                 repositorySystem.createDependencyArtifact(new org.apache.maven.model.Dependency(d));
878                         if (artifact != null) {
879                             tmp.put(d.getManagementKey(), artifact);
880                         }
881                     }
882                     return Collections.unmodifiableMap(tmp);
883                 });
884             }
885             project.setManagedVersionMap(map);
886 
887             // release artifact repository
888             if (project.getDistributionManagement() != null
889                     && project.getDistributionManagement().getRepository() != null) {
890                 try {
891                     DeploymentRepository r = project.getModel()
892                             .getDelegate()
893                             .getDistributionManagement()
894                             .getRepository();
895                     if (r.getId() != null
896                             && !r.getId().isEmpty()
897                             && r.getUrl() != null
898                             && !r.getUrl().isEmpty()) {
899                         ArtifactRepository repo = MavenRepositorySystem.buildArtifactRepository(
900                                 new org.apache.maven.model.DeploymentRepository(r));
901                         repositorySystem.injectProxy(request.getRepositorySession(), List.of(repo));
902                         repositorySystem.injectAuthentication(request.getRepositorySession(), List.of(repo));
903                         project.setReleaseArtifactRepository(repo);
904                     }
905                 } catch (InvalidRepositoryException e) {
906                     throw new IllegalStateException(
907                             "Failed to create release distribution repository for " + project.getId(), e);
908                 }
909             }
910 
911             // snapshot artifact repository
912             if (project.getDistributionManagement() != null
913                     && project.getDistributionManagement().getSnapshotRepository() != null) {
914                 try {
915                     DeploymentRepository r = project.getModel()
916                             .getDelegate()
917                             .getDistributionManagement()
918                             .getSnapshotRepository();
919                     if (r.getId() != null
920                             && !r.getId().isEmpty()
921                             && r.getUrl() != null
922                             && !r.getUrl().isEmpty()) {
923                         ArtifactRepository repo = MavenRepositorySystem.buildArtifactRepository(
924                                 new org.apache.maven.model.DeploymentRepository(r));
925                         repositorySystem.injectProxy(request.getRepositorySession(), List.of(repo));
926                         repositorySystem.injectAuthentication(request.getRepositorySession(), List.of(repo));
927                         project.setSnapshotArtifactRepository(repo);
928                     }
929                 } catch (InvalidRepositoryException e) {
930                     throw new IllegalStateException(
931                             "Failed to create snapshot distribution repository for " + project.getId(), e);
932                 }
933             }
934 
935             // remote repositories
936             List<ArtifactRepository> remoteRepositories = request.getRemoteRepositories();
937             try {
938                 remoteRepositories = projectBuildingHelper.createArtifactRepositories(
939                         project.getModel().getRepositories(), remoteRepositories, request);
940             } catch (Exception e) {
941                 result.getProblemCollector()
942                         .reportProblem(new org.apache.maven.impl.model.DefaultModelProblem(
943                                 "",
944                                 Severity.ERROR,
945                                 Version.BASE,
946                                 project.getModel().getDelegate(),
947                                 -1,
948                                 -1,
949                                 e));
950             }
951             project.setRemoteArtifactRepositories(remoteRepositories);
952         }
953 
954         /**
955          * Emulates Maven 3.x {@code ModelData#getId} method, that unlike model, returned {@code GAV} and not {@code GAPV}.
956          */
957         private static String getModelDataId(Model model) {
958             return ((model.getGroupId() == null) ? "[inherited]" : model.getGroupId()) + ":"
959                     + model.getArtifactId()
960                     + ":"
961                     + ((model.getVersion() == null) ? "[inherited]" : model.getVersion());
962         }
963 
964         /**
965          * Validates that legacy directory configuration does not conflict with {@code <sources>}.
966          * <p>
967          * When {@code <sources>} is configured, the build fails if:
968          * <ul>
969          *   <li><strong>Configuration presence</strong>: an explicit legacy configuration differs from the default</li>
970          *   <li><strong>Physical presence</strong>: the default directory exists on the filesystem (only checked
971          *       when {@code checkPhysicalPresence} is true, typically for modular projects where
972          *       {@code <source>} elements use different paths like {@code src/<module>/main/java})</li>
973          * </ul>
974          * <p>
975          * The presence of {@code <sources>} is the trigger for this validation, not whether the
976          * project is modular or non-modular.
977          * <p>
978          * This ensures consistency with resource handling.
979          *
980          * @param configuredDir the configured legacy directory value
981          * @param defaultDir the default legacy directory path
982          * @param elementName the XML element name for error messages
983          * @param projectId the project ID for error messages
984          * @param result the model builder result for reporting problems
985          * @param checkPhysicalPresence whether to check for physical presence of the default directory
986          * @see SourceHandlingContext#handleResourceConfiguration(ProjectScope)
987          */
988         private void failIfLegacyDirectoryPresent(
989                 String configuredDir,
990                 Path defaultDir,
991                 String elementName,
992                 String projectId,
993                 ModelBuilderResult result,
994                 boolean checkPhysicalPresence) {
995             if (configuredDir != null) {
996                 Path configuredPath = Path.of(configuredDir).toAbsolutePath().normalize();
997                 Path defaultPath = defaultDir.toAbsolutePath().normalize();
998                 if (!configuredPath.equals(defaultPath)) {
999                     // Configuration presence: explicit config differs from default
1000                     String message = String.format(
1001                             "Legacy %s cannot be used in project %s because sources are configured via <sources>. "
1002                                     + "Remove the %s configuration.",
1003                             elementName, projectId, elementName);
1004                     logger.error(message);
1005                     result.getProblemCollector()
1006                             .reportProblem(new org.apache.maven.impl.model.DefaultModelProblem(
1007                                     message, Severity.ERROR, Version.V41, null, -1, -1, null));
1008                 } else if (checkPhysicalPresence && Files.isDirectory(defaultPath)) {
1009                     // Physical presence: default directory exists but would be ignored
1010                     String message = String.format(
1011                             "Legacy directory '%s' exists but cannot be used in project %s "
1012                                     + "because sources are configured via <sources>. "
1013                                     + "Remove or rename the directory.",
1014                             defaultPath, projectId);
1015                     logger.error(message);
1016                     result.getProblemCollector()
1017                             .reportProblem(new org.apache.maven.impl.model.DefaultModelProblem(
1018                                     message, Severity.ERROR, Version.V41, null, -1, -1, null));
1019                 }
1020             }
1021         }
1022 
1023         private void initParent(MavenProject project, ModelBuilderResult result) {
1024             Model parentModel = result.getParentModel();
1025 
1026             if (parentModel != null) {
1027                 final String parentGroupId = getGroupId(parentModel);
1028                 final String parentVersion = getVersion(parentModel);
1029 
1030                 project.setParentArtifact(repositorySystem.createProjectArtifact(
1031                         parentGroupId, parentModel.getArtifactId(), parentVersion));
1032 
1033                 MavenProject parent = projectIndex.get(parentModel.getId());
1034                 if (parent == null) {
1035                     //
1036                     // At this point the DefaultModelBuildingListener has fired, and it populates the
1037                     // remote repositories with those found in the pom.xml, along with the existing externally
1038                     // defined repositories.
1039                     //
1040                     // Compute merged repositories for this project and store in session
1041                     // instead of mutating the shared request to avoid leakage between projects
1042                     List<ArtifactRepository> mergedRepositories;
1043                     switch (request.getRepositoryMerging()) {
1044                         case POM_DOMINANT -> {
1045                             LinkedHashSet<ArtifactRepository> reposes =
1046                                     new LinkedHashSet<>(project.getRemoteArtifactRepositories());
1047                             reposes.addAll(request.getRemoteRepositories());
1048                             mergedRepositories = List.copyOf(reposes);
1049                         }
1050                         case REQUEST_DOMINANT -> {
1051                             LinkedHashSet<ArtifactRepository> reposes =
1052                                     new LinkedHashSet<>(request.getRemoteRepositories());
1053                             reposes.addAll(project.getRemoteArtifactRepositories());
1054                             mergedRepositories = List.copyOf(reposes);
1055                         }
1056                         default ->
1057                             throw new IllegalArgumentException(
1058                                     "Unsupported repository merging: " + request.getRepositoryMerging());
1059                     }
1060 
1061                     // Store the computed repositories for this project in BuildSession storage
1062                     // to avoid mutating the shared request and causing leakage between projects
1063                     projectRepositories.put(project.getId(), mergedRepositories);
1064 
1065                     Path parentPomFile = parentModel.getPomFile();
1066                     if (parentPomFile != null) {
1067                         project.setParentFile(parentPomFile.toFile());
1068                         try {
1069                             parent = build(true, parentPomFile, Sources.buildSource(parentPomFile))
1070                                     .getProject();
1071                         } catch (ProjectBuildingException e) {
1072                             // MNG-4488 where let invalid parents slide on by
1073                             if (logger.isDebugEnabled()) {
1074                                 // Message below is checked for in the MNG-2199 core IT.
1075                                 logger.warn("Failed to build parent project for " + project.getId(), e);
1076                             } else {
1077                                 // Message below is checked for in the MNG-2199 core IT.
1078                                 logger.warn("Failed to build parent project for " + project.getId());
1079                             }
1080                         }
1081                     } else {
1082                         Artifact parentArtifact = project.getParentArtifact();
1083                         try {
1084                             parent = build(true, parentArtifact, false, getEffectiveRepositories(project.getId()))
1085                                     .getProject();
1086                         } catch (ProjectBuildingException e) {
1087                             // MNG-4488 where let invalid parents slide on by
1088                             if (logger.isDebugEnabled()) {
1089                                 // Message below is checked for in the MNG-2199 core IT.
1090                                 logger.warn("Failed to build parent project for " + project.getId(), e);
1091                             } else {
1092                                 // Message below is checked for in the MNG-2199 core IT.
1093                                 logger.warn("Failed to build parent project for " + project.getId());
1094                             }
1095                         }
1096                     }
1097                 }
1098                 project.setParent(parent);
1099                 if (project.getParentFile() == null && parent != null) {
1100                     project.setParentFile(parent.getFile());
1101                 }
1102             }
1103         }
1104 
1105         private ModelBuilderRequest.ModelBuilderRequestBuilder getModelBuildingRequest() {
1106             ModelBuilderRequest.ModelBuilderRequestBuilder modelBuildingRequest = ModelBuilderRequest.builder();
1107 
1108             modelBuildingRequest.session(session);
1109             modelBuildingRequest.requestType(ModelBuilderRequest.RequestType.BUILD_PROJECT);
1110             modelBuildingRequest.profiles(
1111                     request.getProfiles() != null
1112                             ? request.getProfiles().stream()
1113                                     .map(org.apache.maven.model.Profile::getDelegate)
1114                                     .toList()
1115                             : null);
1116             modelBuildingRequest.activeProfileIds(request.getActiveProfileIds());
1117             modelBuildingRequest.inactiveProfileIds(request.getInactiveProfileIds());
1118             modelBuildingRequest.systemProperties(toMap(request.getSystemProperties()));
1119             modelBuildingRequest.userProperties(toMap(request.getUserProperties()));
1120             modelBuildingRequest.repositoryMerging(ModelBuilderRequest.RepositoryMerging.valueOf(
1121                     request.getRepositoryMerging().name()));
1122             modelBuildingRequest.repositories(request.getRemoteRepositories().stream()
1123                     .map(r -> session.getRemoteRepository(RepositoryUtils.toRepo(r)))
1124                     .toList());
1125             return modelBuildingRequest;
1126         }
1127 
1128         private DependencyResolutionResult resolveDependencies(MavenProject project) {
1129             DependencyResolutionResult resolutionResult;
1130 
1131             RepositorySystemSession session = this.session.getSession();
1132             try {
1133                 DefaultDependencyResolutionRequest resolution =
1134                         new DefaultDependencyResolutionRequest(project, session);
1135                 resolutionResult = dependencyResolver.resolve(resolution);
1136             } catch (DependencyResolutionException e) {
1137                 resolutionResult = e.getResult();
1138             }
1139 
1140             Set<Artifact> artifacts = new LinkedHashSet<>();
1141             if (resolutionResult.getDependencyGraph() != null) {
1142                 RepositoryUtils.toArtifacts(
1143                         artifacts,
1144                         resolutionResult.getDependencyGraph().getChildren(),
1145                         Collections.singletonList(project.getArtifact().getId()),
1146                         null);
1147 
1148                 // Maven 2.x quirk: an artifact always points at the local repo, regardless whether resolved or not
1149                 LocalRepositoryManager lrm = session.getLocalRepositoryManager();
1150                 for (Artifact artifact : artifacts) {
1151                     if (!artifact.isResolved()) {
1152                         Path path = lrm.getAbsolutePathForLocalArtifact(RepositoryUtils.toArtifact(artifact));
1153                         artifact.setFile(path.toFile());
1154                     }
1155                 }
1156             }
1157             project.setResolvedArtifacts(artifacts);
1158             project.setArtifacts(artifacts);
1159 
1160             return resolutionResult;
1161         }
1162     }
1163 
1164     private static List<String> getProfileIds(List<Profile> profiles) {
1165         return profiles.stream().map(Profile::getId).toList();
1166     }
1167 
1168     private static ModelSource createStubModelSource(Artifact artifact) {
1169         String xml = "<?xml version='1.0'?>" + "<project>"
1170                 + "<modelVersion>4.0.0</modelVersion>"
1171                 + "<groupId>"
1172                 + artifact.getGroupId() + "</groupId>" + "<artifactId>"
1173                 + artifact.getArtifactId() + "</artifactId>" + "<version>"
1174                 + artifact.getBaseVersion() + "</version>" + "<packaging>"
1175                 + artifact.getType() + "</packaging>" + "</project>";
1176         return new StubModelSource(xml, artifact);
1177     }
1178 
1179     /**
1180      * Extracts project identification from ModelBuilderResult, falling back to rawModel or fileModel
1181      * when effectiveModel is null, similar to ModelBuilderException.getModelId().
1182      */
1183     private static String extractProjectId(ModelBuilderResult result) {
1184         Model model = null;
1185         if (result.getEffectiveModel() != null) {
1186             model = result.getEffectiveModel();
1187         } else if (result.getRawModel() != null) {
1188             model = result.getRawModel();
1189         } else if (result.getFileModel() != null) {
1190             model = result.getFileModel();
1191         }
1192 
1193         if (model != null) {
1194             return model.getId();
1195         }
1196 
1197         return "";
1198     }
1199 
1200     static String getGroupId(Model model) {
1201         String groupId = model.getGroupId();
1202         if (groupId == null && model.getParent() != null) {
1203             groupId = model.getParent().getGroupId();
1204         }
1205         return groupId;
1206     }
1207 
1208     static String getVersion(Model model) {
1209         String version = model.getVersion();
1210         if (version == null && model.getParent() != null) {
1211             version = model.getParent().getVersion();
1212         }
1213         return version;
1214     }
1215 
1216     private static Map<String, String> toMap(Properties properties) {
1217         if (properties != null && !properties.isEmpty()) {
1218             return properties.entrySet().stream()
1219                     .collect(Collectors.toMap(e -> String.valueOf(e.getKey()), e -> String.valueOf(e.getValue())));
1220         } else {
1221             return null;
1222         }
1223     }
1224 
1225     static class LazyMap<K, V> extends AbstractMap<K, V> {
1226         private final Supplier<Map<K, V>> supplier;
1227         private volatile Map<K, V> delegate;
1228 
1229         LazyMap(Supplier<Map<K, V>> supplier) {
1230             this.supplier = supplier;
1231         }
1232 
1233         @Override
1234         public Set<Entry<K, V>> entrySet() {
1235             if (delegate == null) {
1236                 synchronized (this) {
1237                     if (delegate == null) {
1238                         delegate = supplier.get();
1239                     }
1240                 }
1241             }
1242             return delegate.entrySet();
1243         }
1244     }
1245 
1246     private Model injectLifecycleBindings(
1247             Model model,
1248             ModelBuilderRequest request,
1249             ModelProblemCollector problems,
1250             MavenProject project,
1251             ProjectBuildingRequest projectBuildingRequest) {
1252         org.apache.maven.model.Model model3 = new org.apache.maven.model.Model(model);
1253         List<ArtifactRepository> remoteRepositories = projectBuildingRequest.getRemoteRepositories();
1254         List<ArtifactRepository> pluginRepositories = projectBuildingRequest.getPluginArtifactRepositories();
1255         try {
1256             pluginRepositories = projectBuildingHelper.createArtifactRepositories(
1257                     model3.getPluginRepositories(), pluginRepositories, projectBuildingRequest);
1258         } catch (Exception e) {
1259             problems.add(Severity.ERROR, Version.BASE, "Invalid plugin repository: " + e.getMessage(), e);
1260         }
1261         project.setPluginArtifactRepositories(pluginRepositories);
1262 
1263         if (request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_PROJECT) {
1264             try {
1265                 ProjectRealmCache.CacheRecord record =
1266                         projectBuildingHelper.createProjectRealm(project, model3, projectBuildingRequest);
1267 
1268                 project.setClassRealm(record.getRealm());
1269                 project.setExtensionDependencyFilter(record.getExtensionArtifactFilter());
1270             } catch (PluginResolutionException | PluginManagerException | PluginVersionResolutionException e) {
1271 
1272                 problems.add(Severity.ERROR, Version.BASE, "Unresolvable build extension: " + e.getMessage(), e);
1273             }
1274             projectBuildingHelper.selectProjectRealm(project);
1275         }
1276 
1277         // (re)build the regular repos after extensions are loaded to allow for custom layouts
1278         try {
1279             remoteRepositories = projectBuildingHelper.createArtifactRepositories(
1280                     model3.getRepositories(), remoteRepositories, projectBuildingRequest);
1281         } catch (Exception e) {
1282             problems.add(Severity.ERROR, Version.BASE, "Invalid artifact repository: " + e.getMessage(), e);
1283         }
1284         project.setRemoteArtifactRepositories(remoteRepositories);
1285 
1286         if (projectBuildingRequest.isProcessPlugins()) {
1287             return lifecycleBindingsInjector.injectLifecycleBindings(model3.getDelegate(), request, problems);
1288         } else {
1289             return model3.getDelegate();
1290         }
1291     }
1292 }