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