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         private boolean isProjectPom(Path pomFile) {
443             List<org.apache.maven.api.Project> projects = session.getProjects();
444             if (pomFile == null || projects == null) { // no reactor yet while the session is being set up
445                 return false;
446             }
447             Path path = pomFile.toAbsolutePath().normalize();
448             return projects.stream()
449                     .filter(Objects::nonNull)
450                     .anyMatch(p -> p.getPomPath().toAbsolutePath().normalize().equals(path));
451         }
452 
453         ProjectBuildingResult build(
454                 boolean parent, Artifact artifact, boolean allowStubModel, List<ArtifactRepository> repositories)
455                 throws ProjectBuildingException {
456             org.eclipse.aether.artifact.Artifact pomArtifact = RepositoryUtils.toArtifact(artifact);
457             pomArtifact = ArtifactDescriptorUtils.toPomArtifact(pomArtifact);
458 
459             boolean localProject;
460 
461             try {
462                 ArtifactCoordinates coordinates = session.createArtifactCoordinates(session.getArtifact(pomArtifact));
463                 // Use provided repositories if available, otherwise fall back to request repositories
464                 ArtifactResolverRequest req = ArtifactResolverRequest.builder()
465                         .session(session)
466                         .repositories(repositories.stream()
467                                 .map(RepositoryUtils::toRepo)
468                                 .map(session::getRemoteRepository)
469                                 .toList())
470                         .coordinates(List.of(coordinates))
471                         .build();
472                 ArtifactResolverResult res =
473                         session.getService(ArtifactResolver.class).resolve(req);
474                 ArtifactResolverResult.ResultItem resItem = res.getResult(coordinates);
475 
476                 pomArtifact = InternalMavenSession.from(session).toArtifact(resItem.getArtifact());
477                 // the workspace also serves copies from the project-local repository; only a POM that belongs to
478                 // a project of this session is a checkout with a basedir, the rest is treated like a repository
479                 localProject = resItem.getRepository() instanceof org.apache.maven.api.WorkspaceRepository
480                         && isProjectPom(pomArtifact.getPath());
481             } catch (ArtifactResolverException e) {
482                 if (e.getResult().getResults().values().iterator().next().isMissing() && allowStubModel) {
483                     return build(parent, null, createStubModelSource(artifact));
484                 }
485                 throw new ProjectBuildingException(
486                         artifact.getId(), "Error resolving project artifact: " + e.getMessage(), e);
487             }
488 
489             Path pomFile = pomArtifact.getPath();
490 
491             if (!artifact.isResolved() && "pom".equals(artifact.getType())) {
492                 artifact.selectVersion(pomArtifact.getVersion());
493                 artifact.setFile(pomFile.toFile());
494                 artifact.setResolved(true);
495             }
496 
497             if (localProject) {
498                 return build(parent, pomFile, Sources.buildSource(pomFile));
499             } else {
500                 return build(
501                         parent,
502                         null,
503                         Sources.resolvedSource(
504                                 pomFile,
505                                 artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getVersion()));
506             }
507         }
508 
509         List<ProjectBuildingResult> build(List<File> pomFiles, boolean recursive) throws ProjectBuildingException {
510             List<ProjectBuildingResult> results = doBuild(pomFiles, recursive);
511             if (results.stream()
512                     .flatMap(r -> r.getProblems().stream())
513                     .anyMatch(p -> p.getSeverity() != org.apache.maven.model.building.ModelProblem.Severity.WARNING)) {
514                 org.apache.maven.model.building.ModelProblem cycle = results.stream()
515                         .flatMap(r -> r.getProblems().stream())
516                         .filter(p -> p.getException() instanceof CycleDetectedException)
517                         .findAny()
518                         .orElse(null);
519                 if (cycle != null) {
520                     final CycleDetectedException cde = (CycleDetectedException) cycle.getException();
521                     throw new ProjectBuildingException(
522                             null,
523                             "The projects in the reactor contain a cyclic reference: " + cycle.getMessage(),
524                             null,
525                             cde);
526                 }
527 
528                 throw new ProjectBuildingException(results);
529             }
530 
531             return results;
532         }
533 
534         List<ProjectBuildingResult> doBuild(List<File> pomFiles, boolean recursive) {
535             ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
536             try {
537                 return pomFiles.stream()
538                         .map(pomFile -> build(pomFile, recursive))
539                         .flatMap(List::stream)
540                         .toList();
541             } finally {
542                 Thread.currentThread().setContextClassLoader(oldContextClassLoader);
543             }
544         }
545 
546         @SuppressWarnings("checkstyle:parameternumber")
547         private List<ProjectBuildingResult> build(File pomFile, boolean recursive) {
548             ModelBuilderResult result;
549             try {
550                 ModelTransformer injector = (m, r, p) -> {
551                     MavenProject project = projectIndex.computeIfAbsent(m.getId(), f -> new MavenProject());
552                     return injectLifecycleBindings(m, r, p, project, request);
553                 };
554                 ModelBuilderRequest modelBuildingRequest = getModelBuildingRequest()
555                         .source(Sources.buildSource(pomFile.toPath()))
556                         .requestType(ModelBuilderRequest.RequestType.BUILD_PROJECT)
557                         .locationTracking(true)
558                         .recursive(recursive)
559                         .lifecycleBindingsInjector(injector)
560                         .build();
561                 result = modelBuilderSession.build(modelBuildingRequest);
562             } catch (ModelBuilderException e) {
563                 result = e.getResult();
564                 if (result == null || result.getEffectiveModel() == null) {
565                     return List.of(new DefaultProjectBuildingResult(
566                             e.getModelId(), pomFile, convert(e.getProblemCollector())));
567                 }
568             }
569 
570             List<ProjectBuildingResult> results = new ArrayList<>();
571             List<ModelBuilderResult> allModels = results(result).toList();
572             for (ModelBuilderResult r : allModels) {
573                 if (r.getEffectiveModel() != null) {
574                     File pom = r.getSource().getPath().toFile();
575                     MavenProject project =
576                             projectIndex.get(r.getEffectiveModel().getId());
577                     project.setRootDirectory(
578                             rootLocator.findRoot(pom.getParentFile().toPath()));
579                     project.setFile(pom);
580                     project.setExecutionRoot(pom.equals(pomFile));
581                     initProject(project, r);
582                     project.setCollectedProjects(results(r)
583                             .filter(cr -> cr != r && cr.getEffectiveModel() != null)
584                             .map(cr -> projectIndex.get(cr.getEffectiveModel().getId()))
585                             .toList());
586 
587                     DependencyResolutionResult resolutionResult = null;
588                     if (request.isResolveDependencies()) {
589                         resolutionResult = resolveDependencies(project);
590                     }
591                     results.add(new DefaultProjectBuildingResult(
592                             project, convert(r.getProblemCollector()), resolutionResult));
593                 } else {
594                     // Extract project identification even when effective model is null
595                     String projectId = extractProjectId(r);
596                     File sourcePomFile = r.getSource() != null && r.getSource().getPath() != null
597                             ? r.getSource().getPath().toFile()
598                             : null;
599                     results.add(new DefaultProjectBuildingResult(
600                             projectId, sourcePomFile, convert(r.getProblemCollector())));
601                 }
602             }
603             return results;
604         }
605 
606         private Stream<ModelBuilderResult> results(ModelBuilderResult result) {
607             return Stream.concat(result.getChildren().stream().flatMap(this::results), Stream.of(result));
608         }
609 
610         private List<org.apache.maven.model.building.ModelProblem> convert(
611                 ProblemCollector<ModelProblem> problemCollector) {
612             if (problemCollector == null) {
613                 return null;
614             }
615             ArrayList<org.apache.maven.model.building.ModelProblem> problems = new ArrayList<>();
616             problemCollector.problems().map(BuildSession::convert).forEach(problems::add);
617             if (problemCollector.problemsOverflow()) {
618                 problems.add(
619                         0,
620                         new DefaultModelProblem(
621                                 "Too many model problems reported (listed problems are just a subset of reported problems)",
622                                 org.apache.maven.model.building.ModelProblem.Severity.WARNING,
623                                 null,
624                                 null,
625                                 -1,
626                                 -1,
627                                 null,
628                                 null));
629                 return new ArrayList<>(problems) {
630                     @Override
631                     public int size() {
632                         return problemCollector.totalProblemsReported();
633                     }
634                 };
635             } else {
636                 return problems;
637             }
638         }
639 
640         private static org.apache.maven.model.building.ModelProblem convert(ModelProblem p) {
641             return new DefaultModelProblem(
642                     p.getMessage(),
643                     org.apache.maven.model.building.ModelProblem.Severity.valueOf(
644                             p.getSeverity().name()),
645                     org.apache.maven.model.building.ModelProblem.Version.valueOf(
646                             p.getVersion().name()),
647                     p.getSource(),
648                     p.getLineNumber(),
649                     p.getColumnNumber(),
650                     p.getModelId(),
651                     p.getException());
652         }
653 
654         @SuppressWarnings({"checkstyle:methodlength", "deprecation"})
655         private void initProject(MavenProject project, ModelBuilderResult result) {
656             project.setModel(new org.apache.maven.model.Model(result.getEffectiveModel()));
657             project.setOriginalModel(new org.apache.maven.model.Model(result.getFileModel()));
658 
659             initParent(project, result);
660 
661             Artifact projectArtifact = repositorySystem.createArtifact(
662                     project.getGroupId(), project.getArtifactId(), project.getVersion(), null, project.getPackaging());
663             project.setArtifact(projectArtifact);
664 
665             // only set those on 2nd phase, ignore on 1st pass
666             if (project.getFile() != null) {
667                 Build build = project.getBuild().getDelegate();
668                 Path baseDir = project.getBaseDirectory();
669                 Function<ProjectScope, String> outputDirectory = (scope) -> {
670                     if (scope == ProjectScope.MAIN) {
671                         return build.getOutputDirectory();
672                     } else if (scope == ProjectScope.TEST) {
673                         return build.getTestOutputDirectory();
674                     } else {
675                         return build.getDirectory();
676                     }
677                 };
678                 // Create source handling context for unified tracking of all lang/scope combinations
679                 final SourceHandlingContext sourceContext = new SourceHandlingContext(project, result);
680 
681                 // Process all sources, tracking enabled ones and detecting duplicates
682                 for (org.apache.maven.api.model.Source source : sourceContext.sources) {
683                     var sourceRoot = DefaultSourceRoot.fromModel(session, baseDir, outputDirectory, source);
684                     // Track enabled sources for duplicate detection and hasSources() queries
685                     // Only add source if it's not a duplicate enabled source (first enabled wins)
686                     if (sourceContext.shouldAddSource(sourceRoot)) {
687                         project.addSourceRoot(sourceRoot);
688                     }
689                 }
690 
691                 /*
692                   Source directory handling depends on project type and <sources> configuration:
693 
694                   1. CLASSIC projects (no <sources>):
695                      - All legacy directories are used
696 
697                   2. MODULAR projects (have <module> in <sources>):
698                      - ALL legacy directories cause the build to fail (cannot dispatch
699                        between modules)
700                      - The build also fails if default directories (src/main/java)
701                        physically exist on the filesystem
702 
703                   3. NON-MODULAR projects with <sources>:
704                      - Explicit legacy directories (differ from default) always cause
705                        the build to fail
706                      - Legacy directories for scopes where <sources> defines Java are ignored
707                      - Legacy directories for scopes where <sources> has no Java serve as
708                        implicit fallback (only if they match the default, e.g., inherited)
709                      - This allows incremental adoption (e.g., custom resources + default Java)
710                 */
711                 if (sourceContext.sources.isEmpty()) {
712                     // Classic fallback: no <sources> configured, use legacy directories
713                     project.addScriptSourceRoot(build.getScriptSourceDirectory());
714                     project.addCompileSourceRoot(build.getSourceDirectory());
715                     project.addTestCompileSourceRoot(build.getTestSourceDirectory());
716                     // Handle resources using legacy configuration
717                     sourceContext.handleResourceConfiguration(ProjectScope.MAIN);
718                     sourceContext.handleResourceConfiguration(ProjectScope.TEST);
719                 } else {
720                     // Add script source root if no <sources lang="script"> configured
721                     if (!sourceContext.hasSources(Language.SCRIPT, ProjectScope.MAIN)) {
722                         project.addScriptSourceRoot(build.getScriptSourceDirectory());
723                     }
724                     if (sourceContext.usesModuleSourceHierarchy()) {
725                         // Modular: reject ALL legacy directory configurations
726                         failIfLegacyDirectoryPresent(
727                                 build.getSourceDirectory(),
728                                 baseDir.resolve("src/main/java"),
729                                 "<sourceDirectory>",
730                                 project.getId(),
731                                 result,
732                                 true); // check physical presence
733                         failIfLegacyDirectoryPresent(
734                                 build.getTestSourceDirectory(),
735                                 baseDir.resolve("src/test/java"),
736                                 "<testSourceDirectory>",
737                                 project.getId(),
738                                 result,
739                                 true); // check physical presence
740                     } else {
741                         // Non-modular: always validate legacy directories (error if differs from default)
742                         Path mainDefault = baseDir.resolve("src/main/java");
743                         Path testDefault = baseDir.resolve("src/test/java");
744 
745                         failIfLegacyDirectoryPresent(
746                                 build.getSourceDirectory(),
747                                 mainDefault,
748                                 "<sourceDirectory>",
749                                 project.getId(),
750                                 result,
751                                 false); // no physical presence check
752                         failIfLegacyDirectoryPresent(
753                                 build.getTestSourceDirectory(),
754                                 testDefault,
755                                 "<testSourceDirectory>",
756                                 project.getId(),
757                                 result,
758                                 false); // no physical presence check
759 
760                         // Use legacy as fallback only if:
761                         // 1. <sources> doesn't have Java for this scope
762                         // 2. Legacy matches default (otherwise error was reported above)
763                         if (!sourceContext.hasSources(Language.JAVA_FAMILY, ProjectScope.MAIN)) {
764                             Path configuredMain = Path.of(build.getSourceDirectory())
765                                     .toAbsolutePath()
766                                     .normalize();
767                             if (configuredMain.equals(
768                                     mainDefault.toAbsolutePath().normalize())) {
769                                 project.addCompileSourceRoot(build.getSourceDirectory());
770                             }
771                         }
772                         if (!sourceContext.hasSources(Language.JAVA_FAMILY, ProjectScope.TEST)) {
773                             Path configuredTest = Path.of(build.getTestSourceDirectory())
774                                     .toAbsolutePath()
775                                     .normalize();
776                             if (configuredTest.equals(
777                                     testDefault.toAbsolutePath().normalize())) {
778                                 project.addTestCompileSourceRoot(build.getTestSourceDirectory());
779                             }
780                         }
781                     }
782 
783                     // Fail if modular and classic sources are mixed within <sources>
784                     sourceContext.failIfMixedModularAndClassicSources();
785 
786                     // Handle main and test resources using unified source handling
787                     sourceContext.handleResourceConfiguration(ProjectScope.MAIN);
788                     sourceContext.handleResourceConfiguration(ProjectScope.TEST);
789                 }
790 
791                 // When resources are defined via <sources> (4.1.0 model), sync them to
792                 // the model's Build so project.getBuild().getResources() is consistent.
793                 // For legacy <resources>, the model already has the correct resources.
794                 if (sourceContext.hasSources(Language.RESOURCES, ProjectScope.MAIN)
795                         || sourceContext.hasSources(Language.RESOURCES, ProjectScope.TEST)) {
796                     project.syncBuildResources();
797                 }
798             }
799 
800             project.setActiveProfiles(
801                     Stream.concat(result.getActivePomProfiles().stream(), result.getActiveExternalProfiles().stream())
802                             .map(org.apache.maven.model.Profile::new)
803                             .toList());
804 
805             project.setInjectedProfileIds("external", getProfileIds(result.getActiveExternalProfiles()));
806 
807             // Track profile sources correctly by using the per-model profile tracking
808             Map<String, List<org.apache.maven.api.model.Profile>> profilesByModel =
809                     result.getActivePomProfilesByModel();
810 
811             if (profilesByModel.isEmpty()) {
812                 // Fallback to old behavior if map is empty
813                 // This happens when no profiles are active or there's an issue with profile tracking
814                 project.setInjectedProfileIds(
815                         getModelDataId(result.getEffectiveModel()), getProfileIds(result.getActivePomProfiles()));
816             } else {
817                 for (Map.Entry<String, List<org.apache.maven.api.model.Profile>> entry : profilesByModel.entrySet()) {
818                     project.setInjectedProfileIds(entry.getKey(), getProfileIds(entry.getValue()));
819                 }
820             }
821 
822             //
823             // All the parts that were taken out of MavenProject for Maven 4.0.0
824             //
825 
826             project.setProjectBuildingRequest(request);
827 
828             // pluginArtifacts
829             Set<Artifact> pluginArtifacts = new HashSet<>();
830             for (Plugin plugin : project.getModel().getDelegate().getBuild().getPlugins()) {
831                 Artifact artifact = repositorySystem.createPluginArtifact(new org.apache.maven.model.Plugin(plugin));
832 
833                 if (artifact != null) {
834                     pluginArtifacts.add(artifact);
835                 }
836             }
837             project.setPluginArtifacts(pluginArtifacts);
838 
839             // reportArtifacts
840             Set<Artifact> reportArtifacts = new HashSet<>();
841             for (ReportPlugin report :
842                     project.getModel().getDelegate().getReporting().getPlugins()) {
843                 Plugin pp = Plugin.newBuilder()
844                         .groupId(report.getGroupId())
845                         .artifactId(report.getArtifactId())
846                         .version(report.getVersion())
847                         .build();
848 
849                 Artifact artifact = repositorySystem.createPluginArtifact(new org.apache.maven.model.Plugin(pp));
850 
851                 if (artifact != null) {
852                     reportArtifacts.add(artifact);
853                 }
854             }
855             project.setReportArtifacts(reportArtifacts);
856 
857             // extensionArtifacts
858             Set<Artifact> extensionArtifacts = new HashSet<>();
859             List<Extension> extensions =
860                     project.getModel().getDelegate().getBuild().getExtensions();
861             if (extensions != null) {
862                 for (Extension ext : extensions) {
863                     String version;
864                     if (ext.getVersion() == null || ext.getVersion().isEmpty()) {
865                         version = "RELEASE";
866                     } else {
867                         version = ext.getVersion();
868                     }
869 
870                     Artifact artifact = repositorySystem.createArtifact(
871                             ext.getGroupId(), ext.getArtifactId(), version, null, "jar");
872 
873                     if (artifact != null) {
874                         extensionArtifacts.add(artifact);
875                     }
876                 }
877             }
878             project.setExtensionArtifacts(extensionArtifacts);
879 
880             // managedVersionMap
881             Map<String, Artifact> map = Collections.emptyMap();
882             final DependencyManagement dependencyManagement =
883                     project.getModel().getDelegate().getDependencyManagement();
884             if (dependencyManagement != null
885                     && dependencyManagement.getDependencies() != null
886                     && !dependencyManagement.getDependencies().isEmpty()) {
887                 map = new LazyMap<>(() -> {
888                     Map<String, Artifact> tmp = new HashMap<>();
889                     for (Dependency d : dependencyManagement.getDependencies()) {
890                         Artifact artifact =
891                                 repositorySystem.createDependencyArtifact(new org.apache.maven.model.Dependency(d));
892                         if (artifact != null) {
893                             tmp.put(d.getManagementKey(), artifact);
894                         }
895                     }
896                     return Collections.unmodifiableMap(tmp);
897                 });
898             }
899             project.setManagedVersionMap(map);
900 
901             // release artifact repository
902             if (project.getDistributionManagement() != null
903                     && project.getDistributionManagement().getRepository() != null) {
904                 try {
905                     DeploymentRepository r = project.getModel()
906                             .getDelegate()
907                             .getDistributionManagement()
908                             .getRepository();
909                     if (r.getId() != null
910                             && !r.getId().isEmpty()
911                             && r.getUrl() != null
912                             && !r.getUrl().isEmpty()) {
913                         ArtifactRepository repo = MavenRepositorySystem.buildArtifactRepository(
914                                 new org.apache.maven.model.DeploymentRepository(r));
915                         repositorySystem.injectProxy(request.getRepositorySession(), List.of(repo));
916                         repositorySystem.injectAuthentication(request.getRepositorySession(), List.of(repo));
917                         project.setReleaseArtifactRepository(repo);
918                     }
919                 } catch (InvalidRepositoryException e) {
920                     throw new IllegalStateException(
921                             "Failed to create release distribution repository for " + project.getId(), e);
922                 }
923             }
924 
925             // snapshot artifact repository
926             if (project.getDistributionManagement() != null
927                     && project.getDistributionManagement().getSnapshotRepository() != null) {
928                 try {
929                     DeploymentRepository r = project.getModel()
930                             .getDelegate()
931                             .getDistributionManagement()
932                             .getSnapshotRepository();
933                     if (r.getId() != null
934                             && !r.getId().isEmpty()
935                             && r.getUrl() != null
936                             && !r.getUrl().isEmpty()) {
937                         ArtifactRepository repo = MavenRepositorySystem.buildArtifactRepository(
938                                 new org.apache.maven.model.DeploymentRepository(r));
939                         repositorySystem.injectProxy(request.getRepositorySession(), List.of(repo));
940                         repositorySystem.injectAuthentication(request.getRepositorySession(), List.of(repo));
941                         project.setSnapshotArtifactRepository(repo);
942                     }
943                 } catch (InvalidRepositoryException e) {
944                     throw new IllegalStateException(
945                             "Failed to create snapshot distribution repository for " + project.getId(), e);
946                 }
947             }
948 
949             // remote repositories
950             List<ArtifactRepository> remoteRepositories = request.getRemoteRepositories();
951             try {
952                 remoteRepositories = projectBuildingHelper.createArtifactRepositories(
953                         project.getModel().getRepositories(), remoteRepositories, request);
954             } catch (Exception e) {
955                 result.getProblemCollector()
956                         .reportProblem(new org.apache.maven.impl.model.DefaultModelProblem(
957                                 "",
958                                 Severity.ERROR,
959                                 Version.BASE,
960                                 project.getModel().getDelegate(),
961                                 -1,
962                                 -1,
963                                 e));
964             }
965             project.setRemoteArtifactRepositories(remoteRepositories);
966         }
967 
968         /**
969          * Emulates Maven 3.x {@code ModelData#getId} method, that unlike model, returned {@code GAV} and not {@code GAPV}.
970          */
971         private static String getModelDataId(Model model) {
972             return ((model.getGroupId() == null) ? "[inherited]" : model.getGroupId()) + ":"
973                     + model.getArtifactId()
974                     + ":"
975                     + ((model.getVersion() == null) ? "[inherited]" : model.getVersion());
976         }
977 
978         /**
979          * Validates that legacy directory configuration does not conflict with {@code <sources>}.
980          * <p>
981          * When {@code <sources>} is configured, the build fails if:
982          * <ul>
983          *   <li><strong>Configuration presence</strong>: an explicit legacy configuration differs from the default</li>
984          *   <li><strong>Physical presence</strong>: the default directory exists on the filesystem (only checked
985          *       when {@code checkPhysicalPresence} is true, typically for modular projects where
986          *       {@code <source>} elements use different paths like {@code src/<module>/main/java})</li>
987          * </ul>
988          * <p>
989          * The presence of {@code <sources>} is the trigger for this validation, not whether the
990          * project is modular or non-modular.
991          * <p>
992          * This ensures consistency with resource handling.
993          *
994          * @param configuredDir the configured legacy directory value
995          * @param defaultDir the default legacy directory path
996          * @param elementName the XML element name for error messages
997          * @param projectId the project ID for error messages
998          * @param result the model builder result for reporting problems
999          * @param checkPhysicalPresence whether to check for physical presence of the default directory
1000          * @see SourceHandlingContext#handleResourceConfiguration(ProjectScope)
1001          */
1002         private void failIfLegacyDirectoryPresent(
1003                 String configuredDir,
1004                 Path defaultDir,
1005                 String elementName,
1006                 String projectId,
1007                 ModelBuilderResult result,
1008                 boolean checkPhysicalPresence) {
1009             if (configuredDir != null) {
1010                 Path configuredPath = Path.of(configuredDir).toAbsolutePath().normalize();
1011                 Path defaultPath = defaultDir.toAbsolutePath().normalize();
1012                 if (!configuredPath.equals(defaultPath)) {
1013                     // Configuration presence: explicit config differs from default
1014                     String message = String.format(
1015                             "Legacy %s cannot be used in project %s because sources are configured via <sources>. "
1016                                     + "Remove the %s configuration.",
1017                             elementName, projectId, elementName);
1018                     logger.error(message);
1019                     result.getProblemCollector()
1020                             .reportProblem(new org.apache.maven.impl.model.DefaultModelProblem(
1021                                     message, Severity.ERROR, Version.V41, null, -1, -1, null));
1022                 } else if (checkPhysicalPresence && Files.isDirectory(defaultPath)) {
1023                     // Physical presence: default directory exists but would be ignored
1024                     String message = String.format(
1025                             "Legacy directory '%s' exists but cannot be used in project %s "
1026                                     + "because sources are configured via <sources>. "
1027                                     + "Remove or rename the directory.",
1028                             defaultPath, projectId);
1029                     logger.error(message);
1030                     result.getProblemCollector()
1031                             .reportProblem(new org.apache.maven.impl.model.DefaultModelProblem(
1032                                     message, Severity.ERROR, Version.V41, null, -1, -1, null));
1033                 }
1034             }
1035         }
1036 
1037         private void initParent(MavenProject project, ModelBuilderResult result) {
1038             Model parentModel = result.getParentModel();
1039 
1040             if (parentModel != null) {
1041                 final String parentGroupId = getGroupId(parentModel);
1042                 final String parentVersion = getVersion(parentModel);
1043 
1044                 project.setParentArtifact(repositorySystem.createProjectArtifact(
1045                         parentGroupId, parentModel.getArtifactId(), parentVersion));
1046 
1047                 MavenProject parent = projectIndex.get(parentModel.getId());
1048                 if (parent == null) {
1049                     //
1050                     // At this point the DefaultModelBuildingListener has fired, and it populates the
1051                     // remote repositories with those found in the pom.xml, along with the existing externally
1052                     // defined repositories.
1053                     //
1054                     // Compute merged repositories for this project and store in session
1055                     // instead of mutating the shared request to avoid leakage between projects
1056                     List<ArtifactRepository> mergedRepositories;
1057                     switch (request.getRepositoryMerging()) {
1058                         case POM_DOMINANT -> {
1059                             LinkedHashSet<ArtifactRepository> reposes =
1060                                     new LinkedHashSet<>(project.getRemoteArtifactRepositories());
1061                             reposes.addAll(request.getRemoteRepositories());
1062                             mergedRepositories = List.copyOf(reposes);
1063                         }
1064                         case REQUEST_DOMINANT -> {
1065                             LinkedHashSet<ArtifactRepository> reposes =
1066                                     new LinkedHashSet<>(request.getRemoteRepositories());
1067                             reposes.addAll(project.getRemoteArtifactRepositories());
1068                             mergedRepositories = List.copyOf(reposes);
1069                         }
1070                         default ->
1071                             throw new IllegalArgumentException(
1072                                     "Unsupported repository merging: " + request.getRepositoryMerging());
1073                     }
1074 
1075                     // Store the computed repositories for this project in BuildSession storage
1076                     // to avoid mutating the shared request and causing leakage between projects
1077                     projectRepositories.put(project.getId(), mergedRepositories);
1078 
1079                     Path parentPomFile = parentModel.getPomFile();
1080                     if (parentPomFile != null) {
1081                         project.setParentFile(parentPomFile.toFile());
1082                         try {
1083                             parent = build(true, parentPomFile, Sources.buildSource(parentPomFile))
1084                                     .getProject();
1085                         } catch (ProjectBuildingException e) {
1086                             // MNG-4488 where let invalid parents slide on by
1087                             if (logger.isDebugEnabled()) {
1088                                 // Message below is checked for in the MNG-2199 core IT.
1089                                 logger.warn("Failed to build parent project for " + project.getId(), e);
1090                             } else {
1091                                 // Message below is checked for in the MNG-2199 core IT.
1092                                 logger.warn("Failed to build parent project for " + project.getId());
1093                             }
1094                         }
1095                     } else {
1096                         Artifact parentArtifact = project.getParentArtifact();
1097                         try {
1098                             parent = build(true, parentArtifact, false, getEffectiveRepositories(project.getId()))
1099                                     .getProject();
1100                         } catch (ProjectBuildingException e) {
1101                             // MNG-4488 where let invalid parents slide on by
1102                             if (logger.isDebugEnabled()) {
1103                                 // Message below is checked for in the MNG-2199 core IT.
1104                                 logger.warn("Failed to build parent project for " + project.getId(), e);
1105                             } else {
1106                                 // Message below is checked for in the MNG-2199 core IT.
1107                                 logger.warn("Failed to build parent project for " + project.getId());
1108                             }
1109                         }
1110                     }
1111                 }
1112                 project.setParent(parent);
1113                 if (project.getParentFile() == null && parent != null) {
1114                     project.setParentFile(parent.getFile());
1115                 }
1116             }
1117         }
1118 
1119         private ModelBuilderRequest.ModelBuilderRequestBuilder getModelBuildingRequest() {
1120             ModelBuilderRequest.ModelBuilderRequestBuilder modelBuildingRequest = ModelBuilderRequest.builder();
1121 
1122             modelBuildingRequest.session(session);
1123             modelBuildingRequest.requestType(ModelBuilderRequest.RequestType.BUILD_PROJECT);
1124             modelBuildingRequest.profiles(
1125                     request.getProfiles() != null
1126                             ? request.getProfiles().stream()
1127                                     .map(org.apache.maven.model.Profile::getDelegate)
1128                                     .toList()
1129                             : null);
1130             modelBuildingRequest.activeProfileIds(request.getActiveProfileIds());
1131             modelBuildingRequest.inactiveProfileIds(request.getInactiveProfileIds());
1132             modelBuildingRequest.systemProperties(toMap(request.getSystemProperties()));
1133             modelBuildingRequest.userProperties(toMap(request.getUserProperties()));
1134             modelBuildingRequest.repositoryMerging(ModelBuilderRequest.RepositoryMerging.valueOf(
1135                     request.getRepositoryMerging().name()));
1136             modelBuildingRequest.repositories(request.getRemoteRepositories().stream()
1137                     .map(r -> session.getRemoteRepository(RepositoryUtils.toRepo(r)))
1138                     .toList());
1139             return modelBuildingRequest;
1140         }
1141 
1142         private DependencyResolutionResult resolveDependencies(MavenProject project) {
1143             DependencyResolutionResult resolutionResult;
1144 
1145             RepositorySystemSession session = this.session.getSession();
1146             try {
1147                 DefaultDependencyResolutionRequest resolution =
1148                         new DefaultDependencyResolutionRequest(project, session);
1149                 resolutionResult = dependencyResolver.resolve(resolution);
1150             } catch (DependencyResolutionException e) {
1151                 resolutionResult = e.getResult();
1152             }
1153 
1154             Set<Artifact> artifacts = new LinkedHashSet<>();
1155             if (resolutionResult.getDependencyGraph() != null) {
1156                 RepositoryUtils.toArtifacts(
1157                         artifacts,
1158                         resolutionResult.getDependencyGraph().getChildren(),
1159                         Collections.singletonList(project.getArtifact().getId()),
1160                         null);
1161 
1162                 // Maven 2.x quirk: an artifact always points at the local repo, regardless whether resolved or not
1163                 LocalRepositoryManager lrm = session.getLocalRepositoryManager();
1164                 for (Artifact artifact : artifacts) {
1165                     if (!artifact.isResolved()) {
1166                         Path path = lrm.getAbsolutePathForLocalArtifact(RepositoryUtils.toArtifact(artifact));
1167                         artifact.setFile(path.toFile());
1168                     }
1169                 }
1170             }
1171             project.setResolvedArtifacts(artifacts);
1172             project.setArtifacts(artifacts);
1173 
1174             return resolutionResult;
1175         }
1176     }
1177 
1178     private static List<String> getProfileIds(List<Profile> profiles) {
1179         return profiles.stream().map(Profile::getId).toList();
1180     }
1181 
1182     private static ModelSource createStubModelSource(Artifact artifact) {
1183         String xml = "<?xml version='1.0'?>" + "<project>"
1184                 + "<modelVersion>4.0.0</modelVersion>"
1185                 + "<groupId>"
1186                 + artifact.getGroupId() + "</groupId>" + "<artifactId>"
1187                 + artifact.getArtifactId() + "</artifactId>" + "<version>"
1188                 + artifact.getBaseVersion() + "</version>" + "<packaging>"
1189                 + artifact.getType() + "</packaging>" + "</project>";
1190         return new StubModelSource(xml, artifact);
1191     }
1192 
1193     /**
1194      * Extracts project identification from ModelBuilderResult, falling back to rawModel or fileModel
1195      * when effectiveModel is null, similar to ModelBuilderException.getModelId().
1196      */
1197     private static String extractProjectId(ModelBuilderResult result) {
1198         Model model = null;
1199         if (result.getEffectiveModel() != null) {
1200             model = result.getEffectiveModel();
1201         } else if (result.getRawModel() != null) {
1202             model = result.getRawModel();
1203         } else if (result.getFileModel() != null) {
1204             model = result.getFileModel();
1205         }
1206 
1207         if (model != null) {
1208             return model.getId();
1209         }
1210 
1211         return "";
1212     }
1213 
1214     static String getGroupId(Model model) {
1215         String groupId = model.getGroupId();
1216         if (groupId == null && model.getParent() != null) {
1217             groupId = model.getParent().getGroupId();
1218         }
1219         return groupId;
1220     }
1221 
1222     static String getVersion(Model model) {
1223         String version = model.getVersion();
1224         if (version == null && model.getParent() != null) {
1225             version = model.getParent().getVersion();
1226         }
1227         return version;
1228     }
1229 
1230     private static Map<String, String> toMap(Properties properties) {
1231         if (properties != null && !properties.isEmpty()) {
1232             return properties.entrySet().stream()
1233                     .collect(Collectors.toMap(e -> String.valueOf(e.getKey()), e -> String.valueOf(e.getValue())));
1234         } else {
1235             return null;
1236         }
1237     }
1238 
1239     static class LazyMap<K, V> extends AbstractMap<K, V> {
1240         private final Supplier<Map<K, V>> supplier;
1241         private volatile Map<K, V> delegate;
1242 
1243         LazyMap(Supplier<Map<K, V>> supplier) {
1244             this.supplier = supplier;
1245         }
1246 
1247         @Override
1248         public Set<Entry<K, V>> entrySet() {
1249             if (delegate == null) {
1250                 synchronized (this) {
1251                     if (delegate == null) {
1252                         delegate = supplier.get();
1253                     }
1254                 }
1255             }
1256             return delegate.entrySet();
1257         }
1258     }
1259 
1260     private Model injectLifecycleBindings(
1261             Model model,
1262             ModelBuilderRequest request,
1263             ModelProblemCollector problems,
1264             MavenProject project,
1265             ProjectBuildingRequest projectBuildingRequest) {
1266         org.apache.maven.model.Model model3 = new org.apache.maven.model.Model(model);
1267         List<ArtifactRepository> remoteRepositories = projectBuildingRequest.getRemoteRepositories();
1268         List<ArtifactRepository> pluginRepositories = projectBuildingRequest.getPluginArtifactRepositories();
1269         try {
1270             pluginRepositories = projectBuildingHelper.createArtifactRepositories(
1271                     model3.getPluginRepositories(), pluginRepositories, projectBuildingRequest);
1272         } catch (Exception e) {
1273             problems.add(Severity.ERROR, Version.BASE, "Invalid plugin repository: " + e.getMessage(), e);
1274         }
1275         project.setPluginArtifactRepositories(pluginRepositories);
1276 
1277         if (request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_PROJECT) {
1278             try {
1279                 ProjectRealmCache.CacheRecord record =
1280                         projectBuildingHelper.createProjectRealm(project, model3, projectBuildingRequest);
1281 
1282                 project.setClassRealm(record.getRealm());
1283                 project.setExtensionDependencyFilter(record.getExtensionArtifactFilter());
1284             } catch (PluginResolutionException | PluginManagerException | PluginVersionResolutionException e) {
1285 
1286                 problems.add(Severity.ERROR, Version.BASE, "Unresolvable build extension: " + e.getMessage(), e);
1287             }
1288             projectBuildingHelper.selectProjectRealm(project);
1289         }
1290 
1291         // (re)build the regular repos after extensions are loaded to allow for custom layouts
1292         try {
1293             remoteRepositories = projectBuildingHelper.createArtifactRepositories(
1294                     model3.getRepositories(), remoteRepositories, projectBuildingRequest);
1295         } catch (Exception e) {
1296             problems.add(Severity.ERROR, Version.BASE, "Invalid artifact repository: " + e.getMessage(), e);
1297         }
1298         project.setRemoteArtifactRepositories(remoteRepositories);
1299 
1300         if (projectBuildingRequest.isProcessPlugins()) {
1301             return lifecycleBindingsInjector.injectLifecycleBindings(model3.getDelegate(), request, problems);
1302         } else {
1303             return model3.getDelegate();
1304         }
1305     }
1306 }