View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.project;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.io.ByteArrayInputStream;
26  import java.io.File;
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.nio.charset.StandardCharsets;
30  import java.nio.file.Path;
31  import java.util.AbstractMap;
32  import java.util.ArrayList;
33  import java.util.Collections;
34  import java.util.HashMap;
35  import java.util.HashSet;
36  import java.util.LinkedHashSet;
37  import java.util.List;
38  import java.util.Map;
39  import java.util.Objects;
40  import java.util.Properties;
41  import java.util.Set;
42  import java.util.concurrent.ConcurrentHashMap;
43  import java.util.function.Function;
44  import java.util.function.Supplier;
45  import java.util.stream.Collectors;
46  import java.util.stream.Stream;
47  
48  import org.apache.maven.ProjectCycleException;
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.model.Resource;
67  import org.apache.maven.api.services.ArtifactResolver;
68  import org.apache.maven.api.services.ArtifactResolverException;
69  import org.apache.maven.api.services.ArtifactResolverRequest;
70  import org.apache.maven.api.services.ArtifactResolverResult;
71  import org.apache.maven.api.services.BuilderProblem.Severity;
72  import org.apache.maven.api.services.ModelBuilder;
73  import org.apache.maven.api.services.ModelBuilderException;
74  import org.apache.maven.api.services.ModelBuilderRequest;
75  import org.apache.maven.api.services.ModelBuilderResult;
76  import org.apache.maven.api.services.ModelProblem;
77  import org.apache.maven.api.services.ModelProblem.Version;
78  import org.apache.maven.api.services.ModelProblemCollector;
79  import org.apache.maven.api.services.ModelSource;
80  import org.apache.maven.api.services.ModelTransformer;
81  import org.apache.maven.api.services.ProblemCollector;
82  import org.apache.maven.api.services.Source;
83  import org.apache.maven.api.services.Sources;
84  import org.apache.maven.api.services.model.LifecycleBindingsInjector;
85  import org.apache.maven.artifact.Artifact;
86  import org.apache.maven.artifact.InvalidRepositoryException;
87  import org.apache.maven.artifact.repository.ArtifactRepository;
88  import org.apache.maven.bridge.MavenRepositorySystem;
89  import org.apache.maven.impl.DefaultSourceRoot;
90  import org.apache.maven.impl.InternalSession;
91  import org.apache.maven.impl.resolver.ArtifactDescriptorUtils;
92  import org.apache.maven.internal.impl.InternalMavenSession;
93  import org.apache.maven.model.building.DefaultModelProblem;
94  import org.apache.maven.model.building.FileModelSource;
95  import org.apache.maven.model.building.ModelBuildingRequest;
96  import org.apache.maven.model.building.ModelSource2;
97  import org.apache.maven.model.root.RootLocator;
98  import org.apache.maven.plugin.PluginManagerException;
99  import org.apache.maven.plugin.PluginResolutionException;
100 import org.apache.maven.plugin.version.PluginVersionResolutionException;
101 import org.eclipse.aether.RepositorySystem;
102 import org.eclipse.aether.RepositorySystemSession;
103 import org.eclipse.aether.repository.LocalRepositoryManager;
104 import org.slf4j.Logger;
105 import org.slf4j.LoggerFactory;
106 
107 /**
108  * DefaultProjectBuilder
109  */
110 @Named
111 @Singleton
112 public class DefaultProjectBuilder implements ProjectBuilder {
113 
114     private final Logger logger = LoggerFactory.getLogger(getClass());
115     private final ModelBuilder modelBuilder;
116     private final ProjectBuildingHelper projectBuildingHelper;
117     private final MavenRepositorySystem repositorySystem;
118     private final ProjectDependenciesResolver dependencyResolver;
119     private final RootLocator rootLocator;
120     private final LifecycleBindingsInjector lifecycleBindingsInjector;
121 
122     @SuppressWarnings("checkstyle:ParameterNumber")
123     @Inject
124     public DefaultProjectBuilder(
125             ModelBuilder modelBuilder,
126             ProjectBuildingHelper projectBuildingHelper,
127             MavenRepositorySystem repositorySystem,
128             RepositorySystem repoSystem,
129             ProjectDependenciesResolver dependencyResolver,
130             RootLocator rootLocator,
131             LifecycleBindingsInjector lifecycleBindingsInjector) {
132         this.modelBuilder = modelBuilder;
133         this.projectBuildingHelper = projectBuildingHelper;
134         this.repositorySystem = repositorySystem;
135         this.dependencyResolver = dependencyResolver;
136         this.rootLocator = rootLocator;
137         this.lifecycleBindingsInjector = lifecycleBindingsInjector;
138     }
139     // ----------------------------------------------------------------------
140     // MavenProjectBuilder Implementation
141     // ----------------------------------------------------------------------
142 
143     @Override
144     public ProjectBuildingResult build(File pomFile, ProjectBuildingRequest request) throws ProjectBuildingException {
145         try (BuildSession bs = new BuildSession(request)) {
146             Path path = pomFile.toPath();
147             return bs.build(false, path, Sources.buildSource(path));
148         }
149     }
150 
151     @Deprecated
152     @Override
153     public ProjectBuildingResult build(
154             org.apache.maven.model.building.ModelSource modelSource, ProjectBuildingRequest request)
155             throws ProjectBuildingException {
156         return build(toSource(modelSource), request);
157     }
158 
159     @Deprecated
160     static ModelSource toSource(org.apache.maven.model.building.ModelSource modelSource) {
161         if (modelSource instanceof FileModelSource fms) {
162             return Sources.buildSource(fms.getPath());
163         } else {
164             return new WrapModelSource(modelSource);
165         }
166     }
167 
168     @Override
169     public ProjectBuildingResult build(ModelSource modelSource, ProjectBuildingRequest request)
170             throws ProjectBuildingException {
171         try (BuildSession bs = new BuildSession(request)) {
172             return bs.build(false, null, modelSource);
173         }
174     }
175 
176     @Override
177     public ProjectBuildingResult build(Artifact artifact, ProjectBuildingRequest request)
178             throws ProjectBuildingException {
179         return build(artifact, false, request);
180     }
181 
182     @Override
183     public ProjectBuildingResult build(Artifact artifact, boolean allowStubModel, ProjectBuildingRequest request)
184             throws ProjectBuildingException {
185         try (BuildSession bs = new BuildSession(request)) {
186             return bs.build(false, artifact, allowStubModel, request.getRemoteRepositories());
187         }
188     }
189 
190     @Override
191     public List<ProjectBuildingResult> build(List<File> pomFiles, boolean recursive, ProjectBuildingRequest request)
192             throws ProjectBuildingException {
193         try (BuildSession bs = new BuildSession(request)) {
194             return bs.build(pomFiles, recursive);
195         }
196     }
197 
198     private static class StubModelSource implements ModelSource {
199         private final String xml;
200         private final Artifact artifact;
201 
202         StubModelSource(String xml, Artifact artifact) {
203             this.xml = xml;
204             this.artifact = artifact;
205         }
206 
207         @Override
208         @Nullable
209         public ModelSource resolve(@Nonnull ModelLocator modelLocator, @Nonnull String relative) {
210             return null;
211         }
212 
213         @Override
214         @Nullable
215         public Path getPath() {
216             return null;
217         }
218 
219         @Override
220         @Nonnull
221         public InputStream openStream() throws IOException {
222             return new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
223         }
224 
225         @Override
226         @Nonnull
227         public String getLocation() {
228             return artifact.getId();
229         }
230 
231         @Override
232         @Nullable
233         public Source resolve(@Nonnull String relative) {
234             return null;
235         }
236 
237         @Override
238         public boolean equals(Object o) {
239             if (this == o) {
240                 return true;
241             }
242             if (o == null || getClass() != o.getClass()) {
243                 return false;
244             }
245             StubModelSource that = (StubModelSource) o;
246             return Objects.equals(xml, that.xml) && Objects.equals(artifact, that.artifact);
247         }
248 
249         @Override
250         public int hashCode() {
251             return Objects.hash(xml, artifact);
252         }
253     }
254 
255     private static class WrapModelSource implements ModelSource {
256         private final org.apache.maven.model.building.ModelSource modelSource;
257 
258         WrapModelSource(org.apache.maven.model.building.ModelSource modelSource) {
259             this.modelSource = modelSource;
260         }
261 
262         @Override
263         @Nullable
264         public ModelSource resolve(@Nonnull ModelLocator modelLocator, @Nonnull String relative) {
265             return null;
266         }
267 
268         @Override
269         @Nullable
270         public Path getPath() {
271             return null;
272         }
273 
274         @Override
275         @Nonnull
276         public InputStream openStream() throws IOException {
277             return modelSource.getInputStream();
278         }
279 
280         @Override
281         @Nonnull
282         public String getLocation() {
283             return modelSource.getLocation();
284         }
285 
286         @Override
287         @Nullable
288         public Source resolve(@Nonnull String relative) {
289             if (modelSource instanceof ModelSource2 ms) {
290                 return toSource(ms.getRelatedSource(relative));
291             } else {
292                 return null;
293             }
294         }
295 
296         @Override
297         public boolean equals(Object o) {
298             if (this == o) {
299                 return true;
300             }
301             if (o == null || getClass() != o.getClass()) {
302                 return false;
303             }
304             WrapModelSource that = (WrapModelSource) o;
305             return Objects.equals(modelSource, that.modelSource);
306         }
307 
308         @Override
309         public int hashCode() {
310             return Objects.hashCode(modelSource);
311         }
312     }
313 
314     class BuildSession implements AutoCloseable {
315         private final ProjectBuildingRequest request;
316         private final InternalSession session;
317         private final ModelBuilder.ModelBuilderSession modelBuilderSession;
318         private final Map<String, MavenProject> projectIndex = new ConcurrentHashMap<>(256);
319 
320         // Store computed repositories per project to avoid leakage between projects
321         private final Map<String, List<ArtifactRepository>> projectRepositories = new ConcurrentHashMap<>();
322 
323         /**
324          * Get the effective repositories for a project. If project-specific repositories
325          * have been computed and stored, use those; otherwise fall back to request repositories.
326          */
327         private List<ArtifactRepository> getEffectiveRepositories(String projectId) {
328             List<ArtifactRepository> stored = projectRepositories.get(projectId);
329             return stored != null ? stored : request.getRemoteRepositories();
330         }
331 
332         BuildSession(ProjectBuildingRequest request) {
333             this.request = request;
334             InternalSession session = InternalSession.from(request.getRepositorySession());
335             Path basedir = request.getLocalRepository() != null
336                     ? request.getLocalRepository().getBasedirPath()
337                     : null;
338             if (basedir != null) {
339                 LocalRepository localRepository = session.createLocalRepository(basedir);
340                 session = InternalSession.from(session.withLocalRepository(localRepository));
341             }
342             this.session = session;
343             this.modelBuilderSession = modelBuilder.newSession();
344             // Save the ModelBuilderSession for later retrieval by the DefaultConsumerPomBuilder.
345             // Use replace(key, null, value) to make sure the *main* session, i.e. the one used
346             // to load the projects, is stored. This is to avoid the session being overwritten
347             // if a plugin uses the ProjectBuilder.
348             this.session
349                     .getData()
350                     .replace(SessionData.key(ModelBuilder.ModelBuilderSession.class), null, modelBuilderSession);
351         }
352 
353         @Override
354         public void close() {}
355 
356         ProjectBuildingResult build(boolean parent, Path pomFile, ModelSource modelSource)
357                 throws ProjectBuildingException {
358             ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
359 
360             try {
361                 MavenProject project = request.getProject();
362 
363                 ProblemCollector<ModelProblem> problemCollector = null;
364                 Throwable error = null;
365 
366                 if (project == null) {
367                     project = new MavenProject();
368                     project.setFile(pomFile != null ? pomFile.toFile() : null);
369 
370                     boolean reactorMember = pomFile != null
371                             && session.getProjects() != null // this is for UTs
372                             && session.getProjects().stream()
373                                     .anyMatch(
374                                             p -> p.getPomPath().toAbsolutePath().equals(pomFile.toAbsolutePath()));
375                     boolean isStandalone = pomFile == null
376                             && modelSource != null
377                             && modelSource.getLocation().startsWith("jar:")
378                             && modelSource.getLocation().endsWith("/org/apache/maven/project/standalone.xml");
379 
380                     ModelBuilderRequest.ModelBuilderRequestBuilder builder = getModelBuildingRequest();
381                     ModelBuilderRequest.RequestType type = reactorMember
382                                     || isStandalone
383                                     || (pomFile != null
384                                             && this.request.isProcessPlugins()
385                                             && this.request.getValidationLevel()
386                                                     == ModelBuildingRequest.VALIDATION_LEVEL_STRICT)
387                             ? ModelBuilderRequest.RequestType.BUILD_EFFECTIVE
388                             : (parent
389                                     ? ModelBuilderRequest.RequestType.CONSUMER_PARENT
390                                     : ModelBuilderRequest.RequestType.CONSUMER_DEPENDENCY);
391                     MavenProject theProject = project;
392                     ModelBuilderRequest request = builder.source(modelSource)
393                             .requestType(type)
394                             .locationTracking(true)
395                             .lifecycleBindingsInjector(
396                                     (m, r, p) -> injectLifecycleBindings(m, r, p, theProject, this.request))
397                             .build();
398 
399                     if (pomFile != null) {
400                         project.setRootDirectory(rootLocator.findRoot(pomFile.getParent()));
401                     }
402 
403                     ModelBuilderResult result;
404                     try {
405                         result = modelBuilderSession.build(request);
406                     } catch (ModelBuilderException e) {
407                         result = e.getResult();
408                         if (result == null || result.getEffectiveModel() == null) {
409                             throw new ProjectBuildingException(
410                                     e.getModelId(), e.getMessage(), pomFile != null ? pomFile.toFile() : null, e);
411                         }
412                         // validation error, continue project building and delay failing to help IDEs
413                         error = e;
414                     }
415 
416                     problemCollector = result.getProblemCollector();
417 
418                     initProject(project, result);
419                 }
420 
421                 DependencyResolutionResult resolutionResult = null;
422 
423                 if (request.isResolveDependencies()) {
424                     projectBuildingHelper.selectProjectRealm(project);
425                     resolutionResult = resolveDependencies(project);
426                 }
427 
428                 ProjectBuildingResult result =
429                         new DefaultProjectBuildingResult(project, convert(problemCollector), resolutionResult);
430 
431                 if (error != null) {
432                     ProjectBuildingException e = new ProjectBuildingException(List.of(result));
433                     e.initCause(error);
434                     throw e;
435                 }
436 
437                 return result;
438             } finally {
439                 Thread.currentThread().setContextClassLoader(oldContextClassLoader);
440             }
441         }
442 
443         ProjectBuildingResult build(
444                 boolean parent, Artifact artifact, boolean allowStubModel, List<ArtifactRepository> repositories)
445                 throws ProjectBuildingException {
446             org.eclipse.aether.artifact.Artifact pomArtifact = RepositoryUtils.toArtifact(artifact);
447             pomArtifact = ArtifactDescriptorUtils.toPomArtifact(pomArtifact);
448 
449             boolean localProject;
450 
451             try {
452                 ArtifactCoordinates coordinates = session.createArtifactCoordinates(session.getArtifact(pomArtifact));
453                 // Use provided repositories if available, otherwise fall back to request repositories
454                 ArtifactResolverRequest req = ArtifactResolverRequest.builder()
455                         .session(session)
456                         .repositories(repositories.stream()
457                                 .map(RepositoryUtils::toRepo)
458                                 .map(session::getRemoteRepository)
459                                 .toList())
460                         .coordinates(List.of(coordinates))
461                         .build();
462                 ArtifactResolverResult res =
463                         session.getService(ArtifactResolver.class).resolve(req);
464                 ArtifactResolverResult.ResultItem resItem = res.getResult(coordinates);
465 
466                 pomArtifact = InternalMavenSession.from(session).toArtifact(resItem.getArtifact());
467                 localProject = resItem.getRepository() instanceof org.apache.maven.api.WorkspaceRepository;
468             } catch (ArtifactResolverException e) {
469                 if (e.getResult().getResults().values().iterator().next().isMissing() && allowStubModel) {
470                     return build(parent, null, createStubModelSource(artifact));
471                 }
472                 throw new ProjectBuildingException(
473                         artifact.getId(), "Error resolving project artifact: " + e.getMessage(), e);
474             }
475 
476             Path pomFile = pomArtifact.getPath();
477 
478             if (!artifact.isResolved() && "pom".equals(artifact.getType())) {
479                 artifact.selectVersion(pomArtifact.getVersion());
480                 artifact.setFile(pomFile.toFile());
481                 artifact.setResolved(true);
482             }
483 
484             if (localProject) {
485                 return build(parent, pomFile, Sources.buildSource(pomFile));
486             } else {
487                 return build(
488                         parent,
489                         null,
490                         Sources.resolvedSource(
491                                 pomFile,
492                                 artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getVersion()));
493             }
494         }
495 
496         List<ProjectBuildingResult> build(List<File> pomFiles, boolean recursive) throws ProjectBuildingException {
497             List<ProjectBuildingResult> results = doBuild(pomFiles, recursive);
498             if (results.stream()
499                     .flatMap(r -> r.getProblems().stream())
500                     .anyMatch(p -> p.getSeverity() != org.apache.maven.model.building.ModelProblem.Severity.WARNING)) {
501                 org.apache.maven.model.building.ModelProblem cycle = results.stream()
502                         .flatMap(r -> r.getProblems().stream())
503                         .filter(p -> p.getException() instanceof CycleDetectedException)
504                         .findAny()
505                         .orElse(null);
506                 if (cycle != null) {
507                     throw new RuntimeException(new ProjectCycleException(
508                             "The projects in the reactor contain a cyclic reference: " + cycle.getMessage(),
509                             (CycleDetectedException) cycle.getException()));
510                 }
511                 throw new ProjectBuildingException(results);
512             }
513 
514             return results;
515         }
516 
517         List<ProjectBuildingResult> doBuild(List<File> pomFiles, boolean recursive) {
518             ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
519             try {
520                 return pomFiles.stream()
521                         .map(pomFile -> build(pomFile, recursive))
522                         .flatMap(List::stream)
523                         .collect(Collectors.toList());
524             } finally {
525                 Thread.currentThread().setContextClassLoader(oldContextClassLoader);
526             }
527         }
528 
529         @SuppressWarnings("checkstyle:parameternumber")
530         private List<ProjectBuildingResult> build(File pomFile, boolean recursive) {
531             ModelBuilderResult result;
532             try {
533                 ModelTransformer injector = (m, r, p) -> {
534                     MavenProject project = projectIndex.computeIfAbsent(m.getId(), f -> new MavenProject());
535                     return injectLifecycleBindings(m, r, p, project, request);
536                 };
537                 ModelBuilderRequest modelBuildingRequest = getModelBuildingRequest()
538                         .source(Sources.buildSource(pomFile.toPath()))
539                         .requestType(ModelBuilderRequest.RequestType.BUILD_PROJECT)
540                         .locationTracking(true)
541                         .recursive(recursive)
542                         .lifecycleBindingsInjector(injector)
543                         .build();
544                 result = modelBuilderSession.build(modelBuildingRequest);
545             } catch (ModelBuilderException e) {
546                 result = e.getResult();
547                 if (result == null || result.getEffectiveModel() == null) {
548                     return List.of(new DefaultProjectBuildingResult(
549                             e.getModelId(), pomFile, convert(e.getProblemCollector())));
550                 }
551             }
552 
553             List<ProjectBuildingResult> results = new ArrayList<>();
554             List<ModelBuilderResult> allModels = results(result).toList();
555             for (ModelBuilderResult r : allModels) {
556                 if (r.getEffectiveModel() != null) {
557                     File pom = r.getSource().getPath().toFile();
558                     MavenProject project =
559                             projectIndex.get(r.getEffectiveModel().getId());
560                     Path rootDirectory =
561                             rootLocator.findRoot(pom.getParentFile().toPath());
562                     project.setRootDirectory(rootDirectory);
563                     project.setFile(pom);
564                     project.setExecutionRoot(pom.equals(pomFile));
565                     initProject(project, r);
566                     project.setCollectedProjects(results(r)
567                             .filter(cr -> cr != r && cr.getEffectiveModel() != null)
568                             .map(cr -> projectIndex.get(cr.getEffectiveModel().getId()))
569                             .collect(Collectors.toList()));
570 
571                     DependencyResolutionResult resolutionResult = null;
572                     if (request.isResolveDependencies()) {
573                         resolutionResult = resolveDependencies(project);
574                     }
575                     results.add(new DefaultProjectBuildingResult(
576                             project, convert(r.getProblemCollector()), resolutionResult));
577                 } else {
578                     // Extract project identification even when effective model is null
579                     String projectId = extractProjectId(r);
580                     File sourcePomFile = r.getSource() != null && r.getSource().getPath() != null
581                             ? r.getSource().getPath().toFile()
582                             : null;
583                     results.add(new DefaultProjectBuildingResult(
584                             projectId, sourcePomFile, convert(r.getProblemCollector())));
585                 }
586             }
587             return results;
588         }
589 
590         private Stream<ModelBuilderResult> results(ModelBuilderResult result) {
591             return Stream.concat(result.getChildren().stream().flatMap(this::results), Stream.of(result));
592         }
593 
594         private List<org.apache.maven.model.building.ModelProblem> convert(
595                 ProblemCollector<ModelProblem> problemCollector) {
596             if (problemCollector == null) {
597                 return null;
598             }
599             ArrayList<org.apache.maven.model.building.ModelProblem> problems = new ArrayList<>();
600             problemCollector.problems().map(BuildSession::convert).forEach(problems::add);
601             if (problemCollector.problemsOverflow()) {
602                 problems.add(
603                         0,
604                         new DefaultModelProblem(
605                                 "Too many model problems reported (listed problems are just a subset of reported problems)",
606                                 org.apache.maven.model.building.ModelProblem.Severity.WARNING,
607                                 null,
608                                 null,
609                                 -1,
610                                 -1,
611                                 null,
612                                 null));
613                 return new ArrayList<>(problems) {
614                     @Override
615                     public int size() {
616                         return problemCollector.totalProblemsReported();
617                     }
618                 };
619             } else {
620                 return problems;
621             }
622         }
623 
624         private static org.apache.maven.model.building.ModelProblem convert(ModelProblem p) {
625             return new DefaultModelProblem(
626                     p.getMessage(),
627                     org.apache.maven.model.building.ModelProblem.Severity.valueOf(
628                             p.getSeverity().name()),
629                     org.apache.maven.model.building.ModelProblem.Version.valueOf(
630                             p.getVersion().name()),
631                     p.getSource(),
632                     p.getLineNumber(),
633                     p.getColumnNumber(),
634                     p.getModelId(),
635                     p.getException());
636         }
637 
638         @SuppressWarnings({"checkstyle:methodlength", "deprecation"})
639         private void initProject(MavenProject project, ModelBuilderResult result) {
640             project.setModel(new org.apache.maven.model.Model(result.getEffectiveModel()));
641             project.setOriginalModel(new org.apache.maven.model.Model(result.getFileModel()));
642 
643             initParent(project, result);
644 
645             Artifact projectArtifact = repositorySystem.createArtifact(
646                     project.getGroupId(), project.getArtifactId(), project.getVersion(), null, project.getPackaging());
647             project.setArtifact(projectArtifact);
648 
649             // only set those on 2nd phase, ignore on 1st pass
650             if (project.getFile() != null) {
651                 Build build = project.getBuild().getDelegate();
652                 List<org.apache.maven.api.model.Source> sources = build.getSources();
653                 Path baseDir = project.getBaseDirectory();
654                 Function<ProjectScope, String> outputDirectory = (scope) -> {
655                     if (scope == ProjectScope.MAIN) {
656                         return build.getOutputDirectory();
657                     } else if (scope == ProjectScope.TEST) {
658                         return build.getTestOutputDirectory();
659                     } else {
660                         return build.getDirectory();
661                     }
662                 };
663                 boolean hasScript = false;
664                 boolean hasMain = false;
665                 boolean hasTest = false;
666                 for (var source : sources) {
667                     var src = DefaultSourceRoot.fromModel(session, baseDir, outputDirectory, source);
668                     project.addSourceRoot(src);
669                     Language language = src.language();
670                     if (Language.JAVA_FAMILY.equals(language)) {
671                         ProjectScope scope = src.scope();
672                         if (ProjectScope.MAIN.equals(scope)) {
673                             hasMain = true;
674                         } else {
675                             hasTest |= ProjectScope.TEST.equals(scope);
676                         }
677                     } else {
678                         hasScript |= Language.SCRIPT.equals(language);
679                     }
680                 }
681                 /*
682                  * `sourceDirectory`, `testSourceDirectory` and `scriptSourceDirectory`
683                  * are ignored if the POM file contains at least one <source> element
684                  * for the corresponding scope and language. This rule exists because
685                  * Maven provides default values for those elements which may conflict
686                  * with user's configuration.
687                  */
688                 if (!hasScript) {
689                     project.addScriptSourceRoot(build.getScriptSourceDirectory());
690                 }
691                 if (!hasMain) {
692                     project.addCompileSourceRoot(build.getSourceDirectory());
693                 }
694                 if (!hasTest) {
695                     project.addTestCompileSourceRoot(build.getTestSourceDirectory());
696                 }
697                 for (Resource resource : project.getBuild().getDelegate().getResources()) {
698                     project.addSourceRoot(new DefaultSourceRoot(baseDir, ProjectScope.MAIN, resource));
699                 }
700                 for (Resource resource : project.getBuild().getDelegate().getTestResources()) {
701                     project.addSourceRoot(new DefaultSourceRoot(baseDir, ProjectScope.TEST, resource));
702                 }
703             }
704 
705             project.setActiveProfiles(
706                     Stream.concat(result.getActivePomProfiles().stream(), result.getActiveExternalProfiles().stream())
707                             .map(org.apache.maven.model.Profile::new)
708                             .toList());
709 
710             project.setInjectedProfileIds("external", getProfileIds(result.getActiveExternalProfiles()));
711             project.setInjectedProfileIds(
712                     result.getEffectiveModel().getId(), getProfileIds(result.getActivePomProfiles()));
713 
714             //
715             // All the parts that were taken out of MavenProject for Maven 4.0.0
716             //
717 
718             project.setProjectBuildingRequest(request);
719 
720             // pluginArtifacts
721             Set<Artifact> pluginArtifacts = new HashSet<>();
722             for (Plugin plugin : project.getModel().getDelegate().getBuild().getPlugins()) {
723                 Artifact artifact = repositorySystem.createPluginArtifact(new org.apache.maven.model.Plugin(plugin));
724 
725                 if (artifact != null) {
726                     pluginArtifacts.add(artifact);
727                 }
728             }
729             project.setPluginArtifacts(pluginArtifacts);
730 
731             // reportArtifacts
732             Set<Artifact> reportArtifacts = new HashSet<>();
733             for (ReportPlugin report :
734                     project.getModel().getDelegate().getReporting().getPlugins()) {
735                 Plugin pp = Plugin.newBuilder()
736                         .groupId(report.getGroupId())
737                         .artifactId(report.getArtifactId())
738                         .version(report.getVersion())
739                         .build();
740 
741                 Artifact artifact = repositorySystem.createPluginArtifact(new org.apache.maven.model.Plugin(pp));
742 
743                 if (artifact != null) {
744                     reportArtifacts.add(artifact);
745                 }
746             }
747             project.setReportArtifacts(reportArtifacts);
748 
749             // extensionArtifacts
750             Set<Artifact> extensionArtifacts = new HashSet<>();
751             List<Extension> extensions =
752                     project.getModel().getDelegate().getBuild().getExtensions();
753             if (extensions != null) {
754                 for (Extension ext : extensions) {
755                     String version;
756                     if (ext.getVersion() == null || ext.getVersion().isEmpty()) {
757                         version = "RELEASE";
758                     } else {
759                         version = ext.getVersion();
760                     }
761 
762                     Artifact artifact = repositorySystem.createArtifact(
763                             ext.getGroupId(), ext.getArtifactId(), version, null, "jar");
764 
765                     if (artifact != null) {
766                         extensionArtifacts.add(artifact);
767                     }
768                 }
769             }
770             project.setExtensionArtifacts(extensionArtifacts);
771 
772             // managedVersionMap
773             Map<String, Artifact> map = Collections.emptyMap();
774             final DependencyManagement dependencyManagement =
775                     project.getModel().getDelegate().getDependencyManagement();
776             if (dependencyManagement != null
777                     && dependencyManagement.getDependencies() != null
778                     && !dependencyManagement.getDependencies().isEmpty()) {
779                 map = new LazyMap<>(() -> {
780                     Map<String, Artifact> tmp = new HashMap<>();
781                     for (Dependency d : dependencyManagement.getDependencies()) {
782                         Artifact artifact =
783                                 repositorySystem.createDependencyArtifact(new org.apache.maven.model.Dependency(d));
784                         if (artifact != null) {
785                             tmp.put(d.getManagementKey(), artifact);
786                         }
787                     }
788                     return Collections.unmodifiableMap(tmp);
789                 });
790             }
791             project.setManagedVersionMap(map);
792 
793             // release artifact repository
794             if (project.getDistributionManagement() != null
795                     && project.getDistributionManagement().getRepository() != null) {
796                 try {
797                     DeploymentRepository r = project.getModel()
798                             .getDelegate()
799                             .getDistributionManagement()
800                             .getRepository();
801                     if (r.getId() != null
802                             && !r.getId().isEmpty()
803                             && r.getUrl() != null
804                             && !r.getUrl().isEmpty()) {
805                         ArtifactRepository repo = MavenRepositorySystem.buildArtifactRepository(
806                                 new org.apache.maven.model.DeploymentRepository(r));
807                         repositorySystem.injectProxy(request.getRepositorySession(), List.of(repo));
808                         repositorySystem.injectAuthentication(request.getRepositorySession(), List.of(repo));
809                         project.setReleaseArtifactRepository(repo);
810                     }
811                 } catch (InvalidRepositoryException e) {
812                     throw new IllegalStateException(
813                             "Failed to create release distribution repository for " + project.getId(), e);
814                 }
815             }
816 
817             // snapshot artifact repository
818             if (project.getDistributionManagement() != null
819                     && project.getDistributionManagement().getSnapshotRepository() != null) {
820                 try {
821                     DeploymentRepository r = project.getModel()
822                             .getDelegate()
823                             .getDistributionManagement()
824                             .getSnapshotRepository();
825                     if (r.getId() != null
826                             && !r.getId().isEmpty()
827                             && r.getUrl() != null
828                             && !r.getUrl().isEmpty()) {
829                         ArtifactRepository repo = MavenRepositorySystem.buildArtifactRepository(
830                                 new org.apache.maven.model.DeploymentRepository(r));
831                         repositorySystem.injectProxy(request.getRepositorySession(), List.of(repo));
832                         repositorySystem.injectAuthentication(request.getRepositorySession(), List.of(repo));
833                         project.setSnapshotArtifactRepository(repo);
834                     }
835                 } catch (InvalidRepositoryException e) {
836                     throw new IllegalStateException(
837                             "Failed to create snapshot distribution repository for " + project.getId(), e);
838                 }
839             }
840 
841             // remote repositories
842             List<ArtifactRepository> remoteRepositories = request.getRemoteRepositories();
843             try {
844                 remoteRepositories = projectBuildingHelper.createArtifactRepositories(
845                         project.getModel().getRepositories(), remoteRepositories, request);
846             } catch (Exception e) {
847                 result.getProblemCollector()
848                         .reportProblem(new org.apache.maven.impl.model.DefaultModelProblem(
849                                 "",
850                                 Severity.ERROR,
851                                 Version.BASE,
852                                 project.getModel().getDelegate(),
853                                 -1,
854                                 -1,
855                                 e));
856             }
857             project.setRemoteArtifactRepositories(remoteRepositories);
858         }
859 
860         private void initParent(MavenProject project, ModelBuilderResult result) {
861             Model parentModel = result.getParentModel();
862 
863             if (parentModel != null) {
864                 final String parentGroupId = getGroupId(parentModel);
865                 final String parentVersion = getVersion(parentModel);
866 
867                 project.setParentArtifact(repositorySystem.createProjectArtifact(
868                         parentGroupId, parentModel.getArtifactId(), parentVersion));
869 
870                 MavenProject parent = projectIndex.get(parentModel.getId());
871                 if (parent == null) {
872                     //
873                     // At this point the DefaultModelBuildingListener has fired, and it populates the
874                     // remote repositories with those found in the pom.xml, along with the existing externally
875                     // defined repositories.
876                     //
877                     // Compute merged repositories for this project and store in session
878                     // instead of mutating the shared request to avoid leakage between projects
879                     List<ArtifactRepository> mergedRepositories;
880                     switch (request.getRepositoryMerging()) {
881                         case POM_DOMINANT -> {
882                             LinkedHashSet<ArtifactRepository> reposes =
883                                     new LinkedHashSet<>(project.getRemoteArtifactRepositories());
884                             reposes.addAll(request.getRemoteRepositories());
885                             mergedRepositories = List.copyOf(reposes);
886                         }
887                         case REQUEST_DOMINANT -> {
888                             LinkedHashSet<ArtifactRepository> reposes =
889                                     new LinkedHashSet<>(request.getRemoteRepositories());
890                             reposes.addAll(project.getRemoteArtifactRepositories());
891                             mergedRepositories = List.copyOf(reposes);
892                         }
893                         default ->
894                             throw new IllegalArgumentException(
895                                     "Unsupported repository merging: " + request.getRepositoryMerging());
896                     }
897 
898                     // Store the computed repositories for this project in BuildSession storage
899                     // to avoid mutating the shared request and causing leakage between projects
900                     projectRepositories.put(project.getId(), mergedRepositories);
901 
902                     Path parentPomFile = parentModel.getPomFile();
903                     if (parentPomFile != null) {
904                         project.setParentFile(parentPomFile.toFile());
905                         try {
906                             parent = build(true, parentPomFile, Sources.buildSource(parentPomFile))
907                                     .getProject();
908                         } catch (ProjectBuildingException e) {
909                             // MNG-4488 where let invalid parents slide on by
910                             if (logger.isDebugEnabled()) {
911                                 // Message below is checked for in the MNG-2199 core IT.
912                                 logger.warn("Failed to build parent project for " + project.getId(), e);
913                             } else {
914                                 // Message below is checked for in the MNG-2199 core IT.
915                                 logger.warn("Failed to build parent project for " + project.getId());
916                             }
917                         }
918                     } else {
919                         Artifact parentArtifact = project.getParentArtifact();
920                         try {
921                             parent = build(true, parentArtifact, false, getEffectiveRepositories(project.getId()))
922                                     .getProject();
923                         } catch (ProjectBuildingException e) {
924                             // MNG-4488 where let invalid parents slide on by
925                             if (logger.isDebugEnabled()) {
926                                 // Message below is checked for in the MNG-2199 core IT.
927                                 logger.warn("Failed to build parent project for " + project.getId(), e);
928                             } else {
929                                 // Message below is checked for in the MNG-2199 core IT.
930                                 logger.warn("Failed to build parent project for " + project.getId());
931                             }
932                         }
933                     }
934                 }
935                 project.setParent(parent);
936                 if (project.getParentFile() == null && parent != null) {
937                     project.setParentFile(parent.getFile());
938                 }
939             }
940         }
941 
942         private ModelBuilderRequest.ModelBuilderRequestBuilder getModelBuildingRequest() {
943             ModelBuilderRequest.ModelBuilderRequestBuilder modelBuildingRequest = ModelBuilderRequest.builder();
944 
945             modelBuildingRequest.session(session);
946             modelBuildingRequest.requestType(ModelBuilderRequest.RequestType.BUILD_PROJECT);
947             modelBuildingRequest.profiles(
948                     request.getProfiles() != null
949                             ? request.getProfiles().stream()
950                                     .map(org.apache.maven.model.Profile::getDelegate)
951                                     .toList()
952                             : null);
953             modelBuildingRequest.activeProfileIds(request.getActiveProfileIds());
954             modelBuildingRequest.inactiveProfileIds(request.getInactiveProfileIds());
955             modelBuildingRequest.systemProperties(toMap(request.getSystemProperties()));
956             modelBuildingRequest.userProperties(toMap(request.getUserProperties()));
957             modelBuildingRequest.repositoryMerging(ModelBuilderRequest.RepositoryMerging.valueOf(
958                     request.getRepositoryMerging().name()));
959             modelBuildingRequest.repositories(request.getRemoteRepositories().stream()
960                     .map(r -> session.getRemoteRepository(RepositoryUtils.toRepo(r)))
961                     .toList());
962             return modelBuildingRequest;
963         }
964 
965         private DependencyResolutionResult resolveDependencies(MavenProject project) {
966             DependencyResolutionResult resolutionResult;
967 
968             RepositorySystemSession session = this.session.getSession();
969             try {
970                 DefaultDependencyResolutionRequest resolution =
971                         new DefaultDependencyResolutionRequest(project, session);
972                 resolutionResult = dependencyResolver.resolve(resolution);
973             } catch (DependencyResolutionException e) {
974                 resolutionResult = e.getResult();
975             }
976 
977             Set<Artifact> artifacts = new LinkedHashSet<>();
978             if (resolutionResult.getDependencyGraph() != null) {
979                 RepositoryUtils.toArtifacts(
980                         artifacts,
981                         resolutionResult.getDependencyGraph().getChildren(),
982                         Collections.singletonList(project.getArtifact().getId()),
983                         null);
984 
985                 // Maven 2.x quirk: an artifact always points at the local repo, regardless whether resolved or not
986                 LocalRepositoryManager lrm = session.getLocalRepositoryManager();
987                 for (Artifact artifact : artifacts) {
988                     if (!artifact.isResolved()) {
989                         Path path = lrm.getAbsolutePathForLocalArtifact(RepositoryUtils.toArtifact(artifact));
990                         artifact.setFile(path.toFile());
991                     }
992                 }
993             }
994             project.setResolvedArtifacts(artifacts);
995             project.setArtifacts(artifacts);
996 
997             return resolutionResult;
998         }
999     }
1000 
1001     private List<String> getProfileIds(List<Profile> profiles) {
1002         return profiles.stream().map(Profile::getId).collect(Collectors.toList());
1003     }
1004 
1005     private static ModelSource createStubModelSource(Artifact artifact) {
1006         String xml = "<?xml version='1.0'?>" + "<project>"
1007                 + "<modelVersion>4.0.0</modelVersion>"
1008                 + "<groupId>"
1009                 + artifact.getGroupId() + "</groupId>" + "<artifactId>"
1010                 + artifact.getArtifactId() + "</artifactId>" + "<version>"
1011                 + artifact.getBaseVersion() + "</version>" + "<packaging>"
1012                 + artifact.getType() + "</packaging>" + "</project>";
1013         return new StubModelSource(xml, artifact);
1014     }
1015 
1016     /**
1017      * Extracts project identification from ModelBuilderResult, falling back to rawModel or fileModel
1018      * when effectiveModel is null, similar to ModelBuilderException.getModelId().
1019      */
1020     private static String extractProjectId(ModelBuilderResult result) {
1021         Model model = null;
1022         if (result.getEffectiveModel() != null) {
1023             model = result.getEffectiveModel();
1024         } else if (result.getRawModel() != null) {
1025             model = result.getRawModel();
1026         } else if (result.getFileModel() != null) {
1027             model = result.getFileModel();
1028         }
1029 
1030         if (model != null) {
1031             return model.getId();
1032         }
1033 
1034         return "";
1035     }
1036 
1037     static String getGroupId(Model model) {
1038         String groupId = model.getGroupId();
1039         if (groupId == null && model.getParent() != null) {
1040             groupId = model.getParent().getGroupId();
1041         }
1042         return groupId;
1043     }
1044 
1045     static String getVersion(Model model) {
1046         String version = model.getVersion();
1047         if (version == null && model.getParent() != null) {
1048             version = model.getParent().getVersion();
1049         }
1050         return version;
1051     }
1052 
1053     private static Map<String, String> toMap(Properties properties) {
1054         if (properties != null && !properties.isEmpty()) {
1055             return properties.entrySet().stream()
1056                     .collect(Collectors.toMap(e -> String.valueOf(e.getKey()), e -> String.valueOf(e.getValue())));
1057         } else {
1058             return null;
1059         }
1060     }
1061 
1062     static class LazyMap<K, V> extends AbstractMap<K, V> {
1063         private final Supplier<Map<K, V>> supplier;
1064         private volatile Map<K, V> delegate;
1065 
1066         LazyMap(Supplier<Map<K, V>> supplier) {
1067             this.supplier = supplier;
1068         }
1069 
1070         @Override
1071         public Set<Entry<K, V>> entrySet() {
1072             if (delegate == null) {
1073                 synchronized (this) {
1074                     if (delegate == null) {
1075                         delegate = supplier.get();
1076                     }
1077                 }
1078             }
1079             return delegate.entrySet();
1080         }
1081     }
1082 
1083     private Model injectLifecycleBindings(
1084             Model model,
1085             ModelBuilderRequest request,
1086             ModelProblemCollector problems,
1087             MavenProject project,
1088             ProjectBuildingRequest projectBuildingRequest) {
1089         org.apache.maven.model.Model model3 = new org.apache.maven.model.Model(model);
1090         List<ArtifactRepository> remoteRepositories = projectBuildingRequest.getRemoteRepositories();
1091         List<ArtifactRepository> pluginRepositories = projectBuildingRequest.getPluginArtifactRepositories();
1092         try {
1093             pluginRepositories = projectBuildingHelper.createArtifactRepositories(
1094                     model3.getPluginRepositories(), pluginRepositories, projectBuildingRequest);
1095         } catch (Exception e) {
1096             problems.add(Severity.ERROR, Version.BASE, "Invalid plugin repository: " + e.getMessage(), e);
1097         }
1098         project.setPluginArtifactRepositories(pluginRepositories);
1099 
1100         if (request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_PROJECT) {
1101             try {
1102                 ProjectRealmCache.CacheRecord record =
1103                         projectBuildingHelper.createProjectRealm(project, model3, projectBuildingRequest);
1104 
1105                 project.setClassRealm(record.getRealm());
1106                 project.setExtensionDependencyFilter(record.getExtensionArtifactFilter());
1107             } catch (PluginResolutionException | PluginManagerException | PluginVersionResolutionException e) {
1108 
1109                 problems.add(Severity.ERROR, Version.BASE, "Unresolvable build extension: " + e.getMessage(), e);
1110             }
1111             projectBuildingHelper.selectProjectRealm(project);
1112         }
1113 
1114         // (re)build the regular repos after extensions are loaded to allow for custom layouts
1115         try {
1116             remoteRepositories = projectBuildingHelper.createArtifactRepositories(
1117                     model3.getRepositories(), remoteRepositories, projectBuildingRequest);
1118         } catch (Exception e) {
1119             problems.add(Severity.ERROR, Version.BASE, "Invalid artifact repository: " + e.getMessage(), e);
1120         }
1121         project.setRemoteArtifactRepositories(remoteRepositories);
1122 
1123         if (projectBuildingRequest.isProcessPlugins()) {
1124             return lifecycleBindingsInjector.injectLifecycleBindings(model3.getDelegate(), request, problems);
1125         } else {
1126             return model3.getDelegate();
1127         }
1128     }
1129 }