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