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;
20  
21  import static java.util.stream.Collectors.toSet;
22  
23  import java.io.File;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.Collection;
27  import java.util.Collections;
28  import java.util.Date;
29  import java.util.HashSet;
30  import java.util.LinkedHashMap;
31  import java.util.LinkedHashSet;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Set;
35  import java.util.function.Function;
36  import java.util.stream.Stream;
37  import javax.inject.Inject;
38  import javax.inject.Named;
39  import javax.inject.Singleton;
40  import org.apache.maven.api.Session;
41  import org.apache.maven.api.model.Model;
42  import org.apache.maven.api.model.Prerequisites;
43  import org.apache.maven.api.model.Profile;
44  import org.apache.maven.artifact.ArtifactUtils;
45  import org.apache.maven.execution.BuildResumptionAnalyzer;
46  import org.apache.maven.execution.BuildResumptionDataRepository;
47  import org.apache.maven.execution.BuildResumptionPersistenceException;
48  import org.apache.maven.execution.DefaultMavenExecutionResult;
49  import org.apache.maven.execution.ExecutionEvent;
50  import org.apache.maven.execution.MavenExecutionRequest;
51  import org.apache.maven.execution.MavenExecutionResult;
52  import org.apache.maven.execution.MavenSession;
53  import org.apache.maven.execution.ProfileActivation;
54  import org.apache.maven.execution.ProjectActivation;
55  import org.apache.maven.execution.ProjectDependencyGraph;
56  import org.apache.maven.graph.GraphBuilder;
57  import org.apache.maven.graph.ProjectSelector;
58  import org.apache.maven.internal.aether.DefaultRepositorySystemSessionFactory;
59  import org.apache.maven.internal.aether.MavenChainedWorkspaceReader;
60  import org.apache.maven.internal.impl.DefaultSession;
61  import org.apache.maven.internal.impl.DefaultSessionFactory;
62  import org.apache.maven.lifecycle.LifecycleExecutionException;
63  import org.apache.maven.lifecycle.internal.ExecutionEventCatapult;
64  import org.apache.maven.lifecycle.internal.LifecycleStarter;
65  import org.apache.maven.model.building.ModelProblem;
66  import org.apache.maven.model.building.Result;
67  import org.apache.maven.model.superpom.SuperPomProvider;
68  import org.apache.maven.plugin.LegacySupport;
69  import org.apache.maven.project.MavenProject;
70  import org.apache.maven.project.ProjectBuilder;
71  import org.apache.maven.repository.LocalRepositoryNotAccessibleException;
72  import org.apache.maven.session.scope.internal.SessionScope;
73  import org.codehaus.plexus.PlexusContainer;
74  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
75  import org.eclipse.aether.DefaultRepositorySystemSession;
76  import org.eclipse.aether.RepositorySystemSession;
77  import org.eclipse.aether.repository.WorkspaceReader;
78  import org.slf4j.Logger;
79  import org.slf4j.LoggerFactory;
80  import org.slf4j.helpers.MessageFormatter;
81  
82  /**
83   * @author Jason van Zyl
84   */
85  @Named
86  @Singleton
87  public class DefaultMaven implements Maven {
88      private final Logger logger = LoggerFactory.getLogger(getClass());
89  
90      protected ProjectBuilder projectBuilder;
91  
92      private LifecycleStarter lifecycleStarter;
93  
94      protected PlexusContainer container;
95  
96      private ExecutionEventCatapult eventCatapult;
97  
98      private LegacySupport legacySupport;
99  
100     private SessionScope sessionScope;
101 
102     private DefaultRepositorySystemSessionFactory repositorySessionFactory;
103 
104     private final GraphBuilder graphBuilder;
105 
106     private final BuildResumptionAnalyzer buildResumptionAnalyzer;
107 
108     private final BuildResumptionDataRepository buildResumptionDataRepository;
109 
110     private final SuperPomProvider superPomProvider;
111 
112     private final DefaultSessionFactory defaultSessionFactory;
113 
114     private final ProjectSelector projectSelector;
115 
116     @Inject
117     @SuppressWarnings("checkstyle:ParameterNumber")
118     public DefaultMaven(
119             ProjectBuilder projectBuilder,
120             LifecycleStarter lifecycleStarter,
121             PlexusContainer container,
122             ExecutionEventCatapult eventCatapult,
123             LegacySupport legacySupport,
124             SessionScope sessionScope,
125             DefaultRepositorySystemSessionFactory repositorySessionFactory,
126             @Named(GraphBuilder.HINT) GraphBuilder graphBuilder,
127             BuildResumptionAnalyzer buildResumptionAnalyzer,
128             BuildResumptionDataRepository buildResumptionDataRepository,
129             SuperPomProvider superPomProvider,
130             DefaultSessionFactory defaultSessionFactory) {
131         this.projectBuilder = projectBuilder;
132         this.lifecycleStarter = lifecycleStarter;
133         this.container = container;
134         this.eventCatapult = eventCatapult;
135         this.legacySupport = legacySupport;
136         this.sessionScope = sessionScope;
137         this.repositorySessionFactory = repositorySessionFactory;
138         this.graphBuilder = graphBuilder;
139         this.buildResumptionAnalyzer = buildResumptionAnalyzer;
140         this.buildResumptionDataRepository = buildResumptionDataRepository;
141         this.superPomProvider = superPomProvider;
142         this.defaultSessionFactory = defaultSessionFactory;
143         this.projectSelector = new ProjectSelector(); // if necessary switch to DI
144     }
145 
146     @Override
147     public MavenExecutionResult execute(MavenExecutionRequest request) {
148         MavenExecutionResult result;
149 
150         try {
151             result = doExecute(request);
152         } catch (OutOfMemoryError e) {
153             result = addExceptionToResult(new DefaultMavenExecutionResult(), e);
154         } catch (RuntimeException e) {
155             // TODO Hack to make the cycle detection the same for the new graph builder
156             if (e.getCause() instanceof ProjectCycleException) {
157                 result = addExceptionToResult(new DefaultMavenExecutionResult(), e.getCause());
158             } else {
159                 result = addExceptionToResult(
160                         new DefaultMavenExecutionResult(), new InternalErrorException("Internal error: " + e, e));
161             }
162         } finally {
163             legacySupport.setSession(null);
164         }
165 
166         return result;
167     }
168 
169     //
170     // 1) Setup initial properties.
171     //
172     // 2) Validate local repository directory is accessible.
173     //
174     // 3) Create RepositorySystemSession.
175     //
176     // 4) Create MavenSession.
177     //
178     // 5) Execute AbstractLifecycleParticipant.afterSessionStart(session)
179     //
180     // 6) Get reactor projects looking for general POM errors
181     //
182     // 7) Create ProjectDependencyGraph using trimming which takes into account --projects and reactor mode.
183     // This ensures that the projects passed into the ReactorReader are only those specified.
184     //
185     // 8) Create ReactorReader with the getProjectMap( projects ). NOTE that getProjectMap(projects) is the code that
186     // checks for duplicate projects definitions in the build. Ideally this type of duplicate checking should be
187     // part of getting the reactor projects in 6). The duplicate checking is conflated with getProjectMap(projects).
188     //
189     // 9) Execute AbstractLifecycleParticipant.afterProjectsRead(session)
190     //
191     // 10) Create ProjectDependencyGraph without trimming (as trimming was done in 7). A new topological sort is
192     // required after the execution of 9) as the AbstractLifecycleParticipants are free to mutate the MavenProject
193     // instances, which may change dependencies which can, in turn, affect the build order.
194     //
195     // 11) Execute LifecycleStarter.start()
196     //
197     @SuppressWarnings("checkstyle:methodlength")
198     private MavenExecutionResult doExecute(MavenExecutionRequest request) {
199         request.setStartTime(new Date());
200 
201         MavenExecutionResult result = new DefaultMavenExecutionResult();
202 
203         try {
204             validateLocalRepository(request);
205         } catch (LocalRepositoryNotAccessibleException e) {
206             return addExceptionToResult(result, e);
207         }
208 
209         //
210         // We enter the session scope right after the MavenSession creation and before any of the
211         // AbstractLifecycleParticipant lookups
212         // so that @SessionScoped components can be @Injected into AbstractLifecycleParticipants.
213         //
214         sessionScope.enter();
215         try {
216             DefaultRepositorySystemSession repoSession = (DefaultRepositorySystemSession) newRepositorySession(request);
217             MavenSession session = new MavenSession(container, repoSession, request, result);
218             session.setSession(defaultSessionFactory.getSession(session));
219 
220             sessionScope.seed(MavenSession.class, session);
221             sessionScope.seed(Session.class, session.getSession());
222             sessionScope.seed(DefaultSession.class, (DefaultSession) session.getSession());
223 
224             legacySupport.setSession(session);
225 
226             return doExecute(request, session, result, repoSession);
227         } finally {
228             sessionScope.exit();
229         }
230     }
231 
232     private MavenExecutionResult doExecute(
233             MavenExecutionRequest request,
234             MavenSession session,
235             MavenExecutionResult result,
236             DefaultRepositorySystemSession repoSession) {
237         try {
238             afterSessionStart(session);
239         } catch (MavenExecutionException e) {
240             return addExceptionToResult(result, e);
241         }
242 
243         eventCatapult.fire(ExecutionEvent.Type.ProjectDiscoveryStarted, session, null);
244 
245         Result<? extends ProjectDependencyGraph> graphResult = buildGraph(session);
246 
247         if (graphResult.hasErrors()) {
248             return addExceptionToResult(
249                     result, graphResult.getProblems().iterator().next().getException());
250         }
251 
252         try {
253             session.setProjectMap(getProjectMap(session.getProjects()));
254         } catch (DuplicateProjectException e) {
255             return addExceptionToResult(result, e);
256         }
257 
258         try {
259             setupWorkspaceReader(session, repoSession);
260         } catch (ComponentLookupException e) {
261             return addExceptionToResult(result, e);
262         }
263         repoSession.setReadOnly();
264         try {
265             afterProjectsRead(session);
266         } catch (MavenExecutionException e) {
267             return addExceptionToResult(result, e);
268         }
269 
270         //
271         // The projects need to be topologically after the participants have run their afterProjectsRead(session)
272         // because the participant is free to change the dependencies of a project which can potentially change the
273         // topological order of the projects, and therefore can potentially change the build order.
274         //
275         // Note that participants may affect the topological order of the projects but it is
276         // not expected that a participant will add or remove projects from the session.
277         //
278 
279         graphResult = buildGraph(session);
280 
281         if (graphResult.hasErrors()) {
282             return addExceptionToResult(
283                     result, graphResult.getProblems().iterator().next().getException());
284         }
285 
286         try {
287             if (result.hasExceptions()) {
288                 return result;
289             }
290 
291             result.setTopologicallySortedProjects(session.getProjects());
292 
293             result.setProject(session.getTopLevelProject());
294 
295             validatePrerequisitesForNonMavenPluginProjects(session.getProjects());
296 
297             validateRequiredProfiles(session, request.getProfileActivation());
298             if (session.getResult().hasExceptions()) {
299                 return result;
300             }
301 
302             validateOptionalProfiles(session, request.getProfileActivation());
303 
304             lifecycleStarter.execute(session);
305 
306             validateOptionalProjects(request, session);
307             validateOptionalProfiles(session, request.getProfileActivation());
308 
309             if (session.getResult().hasExceptions()) {
310                 addExceptionToResult(result, session.getResult().getExceptions().get(0));
311                 persistResumptionData(result, session);
312                 return result;
313             } else {
314                 session.getAllProjects().stream()
315                         .filter(MavenProject::isExecutionRoot)
316                         .findFirst()
317                         .ifPresent(buildResumptionDataRepository::removeResumptionData);
318             }
319         } finally {
320             try {
321                 afterSessionEnd(session.getProjects(), session);
322             } catch (MavenExecutionException e) {
323                 return addExceptionToResult(result, e);
324             }
325         }
326 
327         return result;
328     }
329 
330     private void setupWorkspaceReader(MavenSession session, DefaultRepositorySystemSession repoSession)
331             throws ComponentLookupException {
332         // Desired order of precedence for workspace readers before querying the local artifact repositories
333         List<WorkspaceReader> workspaceReaders = new ArrayList<WorkspaceReader>();
334         // 1) Reactor workspace reader
335         workspaceReaders.add(container.lookup(WorkspaceReader.class, ReactorReader.HINT));
336         // 2) Repository system session-scoped workspace reader
337         WorkspaceReader repoWorkspaceReader = repoSession.getWorkspaceReader();
338         if (repoWorkspaceReader != null) {
339             workspaceReaders.add(repoWorkspaceReader);
340         }
341         // 3) .. n) Project-scoped workspace readers
342         for (WorkspaceReader workspaceReader :
343                 getProjectScopedExtensionComponents(session.getProjects(), WorkspaceReader.class)) {
344             if (workspaceReaders.contains(workspaceReader)) {
345                 continue;
346             }
347             workspaceReaders.add(workspaceReader);
348         }
349         repoSession.setWorkspaceReader(MavenChainedWorkspaceReader.of(workspaceReaders));
350     }
351 
352     private void afterSessionStart(MavenSession session) throws MavenExecutionException {
353         // CHECKSTYLE_OFF: LineLength
354         for (AbstractMavenLifecycleParticipant listener :
355                 getExtensionComponents(Collections.emptyList(), AbstractMavenLifecycleParticipant.class))
356         // CHECKSTYLE_ON: LineLength
357         {
358             listener.afterSessionStart(session);
359         }
360     }
361 
362     private void afterProjectsRead(MavenSession session) throws MavenExecutionException {
363         ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
364         try {
365             // CHECKSTYLE_OFF: LineLength
366             for (AbstractMavenLifecycleParticipant listener :
367                     getExtensionComponents(session.getProjects(), AbstractMavenLifecycleParticipant.class))
368             // CHECKSTYLE_ON: LineLength
369             {
370                 Thread.currentThread().setContextClassLoader(listener.getClass().getClassLoader());
371 
372                 listener.afterProjectsRead(session);
373             }
374         } finally {
375             Thread.currentThread().setContextClassLoader(originalClassLoader);
376         }
377     }
378 
379     private void afterSessionEnd(Collection<MavenProject> projects, MavenSession session)
380             throws MavenExecutionException {
381         ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
382         try {
383             // CHECKSTYLE_OFF: LineLength
384             for (AbstractMavenLifecycleParticipant listener :
385                     getExtensionComponents(projects, AbstractMavenLifecycleParticipant.class))
386             // CHECKSTYLE_ON: LineLength
387             {
388                 Thread.currentThread().setContextClassLoader(listener.getClass().getClassLoader());
389 
390                 listener.afterSessionEnd(session);
391             }
392         } finally {
393             Thread.currentThread().setContextClassLoader(originalClassLoader);
394         }
395     }
396 
397     private void persistResumptionData(MavenExecutionResult result, MavenSession session) {
398         boolean hasLifecycleExecutionExceptions =
399                 result.getExceptions().stream().anyMatch(LifecycleExecutionException.class::isInstance);
400 
401         if (hasLifecycleExecutionExceptions) {
402             MavenProject rootProject = session.getAllProjects().stream()
403                     .filter(MavenProject::isExecutionRoot)
404                     .findFirst()
405                     .orElseThrow(() -> new IllegalStateException("No project in the session is execution root"));
406 
407             buildResumptionAnalyzer.determineBuildResumptionData(result).ifPresent(resumption -> {
408                 try {
409                     buildResumptionDataRepository.persistResumptionData(rootProject, resumption);
410                     result.setCanResume(true);
411                 } catch (BuildResumptionPersistenceException e) {
412                     logger.warn("Could not persist build resumption data", e);
413                 }
414             });
415         }
416     }
417 
418     public RepositorySystemSession newRepositorySession(MavenExecutionRequest request) {
419         return repositorySessionFactory.newRepositorySession(request);
420     }
421 
422     private void validateLocalRepository(MavenExecutionRequest request) throws LocalRepositoryNotAccessibleException {
423         File localRepoDir = request.getLocalRepositoryPath();
424 
425         logger.debug("Using local repository at " + localRepoDir);
426 
427         localRepoDir.mkdirs();
428 
429         if (!localRepoDir.isDirectory()) {
430             throw new LocalRepositoryNotAccessibleException("Could not create local repository at " + localRepoDir);
431         }
432     }
433 
434     private <T> Collection<T> getExtensionComponents(Collection<MavenProject> projects, Class<T> role) {
435         Collection<T> foundComponents = new LinkedHashSet<>();
436 
437         try {
438             foundComponents.addAll(container.lookupList(role));
439         } catch (ComponentLookupException e) {
440             // this is just silly, lookupList should return an empty list!
441             logger.warn("Failed to lookup " + role + ": " + e.getMessage());
442         }
443 
444         foundComponents.addAll(getProjectScopedExtensionComponents(projects, role));
445 
446         return foundComponents;
447     }
448 
449     protected <T> Collection<T> getProjectScopedExtensionComponents(Collection<MavenProject> projects, Class<T> role) {
450 
451         Collection<T> foundComponents = new LinkedHashSet<>();
452         Collection<ClassLoader> scannedRealms = new HashSet<>();
453 
454         Thread currentThread = Thread.currentThread();
455         ClassLoader originalContextClassLoader = currentThread.getContextClassLoader();
456         try {
457             for (MavenProject project : projects) {
458                 ClassLoader projectRealm = project.getClassRealm();
459 
460                 if (projectRealm != null && scannedRealms.add(projectRealm)) {
461                     currentThread.setContextClassLoader(projectRealm);
462 
463                     try {
464                         foundComponents.addAll(container.lookupList(role));
465                     } catch (ComponentLookupException e) {
466                         // this is just silly, lookupList should return an empty list!
467                         logger.warn("Failed to lookup " + role + ": " + e.getMessage());
468                     }
469                 }
470             }
471             return foundComponents;
472         } finally {
473             currentThread.setContextClassLoader(originalContextClassLoader);
474         }
475     }
476 
477     private MavenExecutionResult addExceptionToResult(MavenExecutionResult result, Throwable e) {
478         if (!result.getExceptions().contains(e)) {
479             result.addException(e);
480         }
481 
482         return result;
483     }
484 
485     private void validatePrerequisitesForNonMavenPluginProjects(List<MavenProject> projects) {
486         for (MavenProject mavenProject : projects) {
487             if (!"maven-plugin".equals(mavenProject.getPackaging())) {
488                 Prerequisites prerequisites =
489                         mavenProject.getModel().getDelegate().getPrerequisites();
490                 if (prerequisites != null && prerequisites.getMaven() != null) {
491                     logger.warn("The project " + mavenProject.getId() + " uses prerequisites"
492                             + " which is only intended for maven-plugin projects "
493                             + "but not for non maven-plugin projects. "
494                             + "For such purposes you should use the maven-enforcer-plugin. "
495                             + "See https://maven.apache.org/enforcer/enforcer-rules/requireMavenVersion.html");
496                 }
497             }
498         }
499     }
500 
501     /**
502      * Get all profiles that are detected in the projects, any parent of the projects, or the settings.
503      * @param session The Maven session
504      * @return A {@link Set} of profile identifiers, never {@code null}.
505      */
506     private Set<String> getAllProfiles(MavenSession session) {
507         final Model superPomModel = superPomProvider.getSuperModel("4.0.0");
508         final Set<MavenProject> projectsIncludingParents = new HashSet<>();
509         for (MavenProject project : session.getProjects()) {
510             boolean isAdded = projectsIncludingParents.add(project);
511             MavenProject parent = project.getParent();
512             while (isAdded && parent != null) {
513                 isAdded = projectsIncludingParents.add(parent);
514                 parent = parent.getParent();
515             }
516         }
517 
518         final Stream<String> projectProfiles = projectsIncludingParents.stream()
519                 .flatMap(p -> p.getModel().getDelegate().getProfiles().stream())
520                 .map(Profile::getId);
521         final Stream<String> settingsProfiles =
522                 session.getSettings().getProfiles().stream().map(org.apache.maven.settings.Profile::getId);
523         final Stream<String> superPomProfiles =
524                 superPomModel.getProfiles().stream().map(Profile::getId);
525 
526         return Stream.of(projectProfiles, settingsProfiles, superPomProfiles)
527                 .flatMap(Function.identity())
528                 .collect(toSet());
529     }
530 
531     /**
532      * Check whether the required profiles were found in any of the projects we're building or the settings.
533      * @param session the Maven session.
534      * @param profileActivation the requested optional and required profiles.
535      */
536     private void validateRequiredProfiles(MavenSession session, ProfileActivation profileActivation) {
537         final Set<String> allAvailableProfiles = getAllProfiles(session);
538 
539         final Set<String> requiredProfiles = new HashSet<>();
540         requiredProfiles.addAll(profileActivation.getRequiredActiveProfileIds());
541         requiredProfiles.addAll(profileActivation.getRequiredInactiveProfileIds());
542 
543         // Check whether the required profiles were found in any of the projects we're building.
544         final Set<String> notFoundRequiredProfiles = requiredProfiles.stream()
545                 .filter(rap -> !allAvailableProfiles.contains(rap))
546                 .collect(toSet());
547 
548         if (!notFoundRequiredProfiles.isEmpty()) {
549             // Use SLF4J formatter for consistency with warnings reported by logger
550             final String message = MessageFormatter.format(
551                             "The requested profiles {} could not be activated or deactivated because they do not"
552                                     + " exist.",
553                             notFoundRequiredProfiles)
554                     .getMessage();
555             addExceptionToResult(session.getResult(), new MissingProfilesException(message));
556         }
557     }
558 
559     /**
560      * Check whether any of the requested optional projects were not activated or deactivated.
561      * @param request the {@link MavenExecutionRequest}.
562      * @param session the {@link MavenSession}.
563      */
564     private void validateOptionalProjects(MavenExecutionRequest request, MavenSession session) {
565         final ProjectActivation projectActivation = request.getProjectActivation();
566         final Set<String> allOptionalSelectors = new HashSet<>();
567         allOptionalSelectors.addAll(projectActivation.getOptionalActiveProjectSelectors());
568         allOptionalSelectors.addAll(projectActivation.getRequiredActiveProjectSelectors());
569         // We intentionally ignore the results of this method.
570         // As a side effect it will log the optional projects that could not be resolved.
571         projectSelector.getOptionalProjectsBySelectors(request, session.getAllProjects(), allOptionalSelectors);
572     }
573 
574     /**
575      * Check whether any of the requested optional profiles were not activated or deactivated.
576      * @param session the Maven session.
577      * @param profileActivation the requested optional and required profiles.
578      */
579     private void validateOptionalProfiles(MavenSession session, ProfileActivation profileActivation) {
580         final Set<String> allAvailableProfiles = getAllProfiles(session);
581 
582         final Set<String> optionalProfiles = new HashSet<>();
583         optionalProfiles.addAll(profileActivation.getOptionalActiveProfileIds());
584         optionalProfiles.addAll(profileActivation.getOptionalInactiveProfileIds());
585 
586         final Set<String> notFoundOptionalProfiles = optionalProfiles.stream()
587                 .filter(rap -> !allAvailableProfiles.contains(rap))
588                 .collect(toSet());
589 
590         if (!notFoundOptionalProfiles.isEmpty()) {
591             logger.info(
592                     "The requested optional profiles {} could not be activated or deactivated because they do not"
593                             + " exist.",
594                     notFoundOptionalProfiles);
595         }
596     }
597 
598     private Map<String, MavenProject> getProjectMap(Collection<MavenProject> projects)
599             throws DuplicateProjectException {
600         Map<String, MavenProject> index = new LinkedHashMap<>();
601         Map<String, List<File>> collisions = new LinkedHashMap<>();
602 
603         for (MavenProject project : projects) {
604             String projectId = ArtifactUtils.key(project.getGroupId(), project.getArtifactId(), project.getVersion());
605 
606             MavenProject collision = index.get(projectId);
607 
608             if (collision == null) {
609                 index.put(projectId, project);
610             } else {
611                 List<File> pomFiles = collisions.get(projectId);
612 
613                 if (pomFiles == null) {
614                     pomFiles = new ArrayList<>(Arrays.asList(collision.getFile(), project.getFile()));
615                     collisions.put(projectId, pomFiles);
616                 } else {
617                     pomFiles.add(project.getFile());
618                 }
619             }
620         }
621 
622         if (!collisions.isEmpty()) {
623             throw new DuplicateProjectException(
624                     "Two or more projects in the reactor"
625                             + " have the same identifier, please make sure that <groupId>:<artifactId>:<version>"
626                             + " is unique for each project: " + collisions,
627                     collisions);
628         }
629 
630         return index;
631     }
632 
633     private Result<? extends ProjectDependencyGraph> buildGraph(MavenSession session) {
634         Result<? extends ProjectDependencyGraph> graphResult = graphBuilder.build(session);
635         for (ModelProblem problem : graphResult.getProblems()) {
636             if (problem.getSeverity() == ModelProblem.Severity.WARNING) {
637                 logger.warn(problem.getMessage());
638             } else {
639                 logger.error(problem.getMessage());
640             }
641         }
642 
643         if (!graphResult.hasErrors()) {
644             ProjectDependencyGraph projectDependencyGraph = graphResult.get();
645             session.setProjects(projectDependencyGraph.getSortedProjects());
646             session.setAllProjects(projectDependencyGraph.getAllProjects());
647             session.setProjectDependencyGraph(projectDependencyGraph);
648         }
649 
650         return graphResult;
651     }
652 
653     @Deprecated
654     // 5 January 2014
655     protected Logger getLogger() {
656         return logger;
657     }
658 }