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