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.buildcache;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Provider;
24  
25  import java.io.File;
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.HashSet;
29  import java.util.LinkedHashSet;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Optional;
33  import java.util.Properties;
34  import java.util.Set;
35  import java.util.TreeSet;
36  import java.util.function.Function;
37  import java.util.stream.Collectors;
38  
39  import org.apache.maven.SessionScoped;
40  import org.apache.maven.buildcache.checksum.KeyUtils;
41  import org.apache.maven.buildcache.xml.CacheConfig;
42  import org.apache.maven.buildcache.xml.config.Discovery;
43  import org.apache.maven.buildcache.xml.config.MultiModule;
44  import org.apache.maven.execution.MavenSession;
45  import org.apache.maven.project.DefaultProjectBuildingRequest;
46  import org.apache.maven.project.MavenProject;
47  import org.apache.maven.project.ProjectBuilder;
48  import org.apache.maven.project.ProjectBuildingException;
49  import org.apache.maven.project.ProjectBuildingRequest;
50  import org.apache.maven.project.ProjectBuildingResult;
51  import org.slf4j.Logger;
52  import org.slf4j.LoggerFactory;
53  
54  @SessionScoped
55  @Named
56  public class DefaultMultiModuleSupport implements MultiModuleSupport {
57  
58      private static final Logger LOGGER = LoggerFactory.getLogger(DefaultMultiModuleSupport.class);
59  
60      private final ProjectBuilder projectBuilder;
61      private final CacheConfig cacheConfig;
62      private final Provider<MavenSession> providerSession;
63  
64      private volatile boolean built;
65      private volatile Map<String, MavenProject> projectMap;
66      private volatile Map<String, MavenProject> sessionProjectMap;
67  
68      @Inject
69      public DefaultMultiModuleSupport(
70              ProjectBuilder projectBuilder, CacheConfig cacheConfig, Provider<MavenSession> providerSession) {
71          this.projectBuilder = projectBuilder;
72          this.cacheConfig = cacheConfig;
73          this.providerSession = providerSession;
74      }
75  
76      @Override
77      public boolean isPartOfSession(String groupId, String artifactId, String version) {
78          MavenSession session = providerSession.get();
79          return getProjectMap(session).containsKey(KeyUtils.getProjectKey(groupId, artifactId, version));
80      }
81  
82      @Override
83      public Optional<MavenProject> tryToResolveProject(String groupId, String artifactId, String version) {
84          return Optional.ofNullable(
85                  getMultiModuleProjectsMap().get(KeyUtils.getProjectKey(groupId, artifactId, version)));
86      }
87  
88      @Override
89      public boolean isPartOfMultiModule(String groupId, String artifactId, String version) {
90          String projectKey = KeyUtils.getProjectKey(groupId, artifactId, version);
91          MavenSession session = providerSession.get();
92          return getProjectMap(session).containsKey(projectKey)
93                  || getMultiModuleProjectsMap().containsKey(projectKey);
94      }
95  
96      private Map<String, MavenProject> getProjectMap(MavenSession session) {
97          if (sessionProjectMap != null) {
98              return sessionProjectMap;
99          }
100         sessionProjectMap =
101                 session.getProjects().stream().collect(Collectors.toMap(KeyUtils::getProjectKey, Function.identity()));
102         return sessionProjectMap;
103     }
104 
105     private Map<String, MavenProject> getMultiModuleProjectsMap() {
106         if (projectMap != null) {
107             return projectMap;
108         }
109         MavenSession session = providerSession.get();
110         return getMultiModuleProjectsMapInner(session);
111     }
112 
113     private synchronized Map<String, MavenProject> getMultiModuleProjectsMapInner(MavenSession session) {
114         if (projectMap != null) {
115             return projectMap;
116         }
117         buildModel(session);
118         return projectMap;
119     }
120 
121     private synchronized void buildModel(MavenSession session) {
122         if (built) {
123             return;
124         }
125 
126         Optional<Discovery> multiModuleDiscovery =
127                 Optional.ofNullable(cacheConfig.getMultiModule()).map(MultiModule::getDiscovery);
128 
129         // no discovery configuration, use only projects in session
130         if (!multiModuleDiscovery.isPresent()) {
131             projectMap = buildProjectMap(session.getProjects());
132             return;
133         }
134 
135         Set<String> scanProfiles = new TreeSet<>(
136                 multiModuleDiscovery.map(Discovery::getScanProfiles).orElse(Collections.emptyList()));
137         MavenProject currentProject = session.getCurrentProject();
138         File multiModulePomFile = getMultiModulePomFile(session);
139 
140         ProjectBuildingRequest projectBuildingRequest = currentProject.getProjectBuildingRequest();
141         boolean profilesMatched = new HashSet<>(projectBuildingRequest.getActiveProfileIds()).containsAll(scanProfiles);
142 
143         // we are building from root with the same profiles, no need to re-scan the whole multi-module project
144         if (currentProject.getFile().getAbsolutePath().equals(multiModulePomFile.getAbsolutePath())
145                 && profilesMatched) {
146             projectMap = buildProjectMap(session.getProjects());
147             return;
148         }
149 
150         long t0 = System.currentTimeMillis();
151 
152         ProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest(projectBuildingRequest);
153         // clear properties because after first build request some properties could be set to profiles
154         // these properties could change effective pom when we try to rebuild whole multi module project again
155         // for example the first model build process do not resolve ${os.detected.classifier}
156         // but once build completed this property is set to profile
157         // if we try to rebuild model for the whole project here string interpolator replaces this value
158         // and effective pom could be different (depending on OS) if this property is used in pom.xml
159         buildingRequest.setProfiles(buildingRequest.getProfiles().stream()
160                 .peek(it -> it.setProperties(new Properties()))
161                 .collect(Collectors.toList()));
162         if (!profilesMatched) {
163             Set<String> profiles = new LinkedHashSet<>(buildingRequest.getActiveProfileIds());
164             // remove duplicates
165             profiles.addAll(scanProfiles);
166             buildingRequest.setActiveProfileIds(new ArrayList<>(profiles));
167         }
168         try {
169             List<ProjectBuildingResult> buildingResults =
170                     projectBuilder.build(Collections.singletonList(multiModulePomFile), true, buildingRequest);
171             LOGGER.info(
172                     "Multi module project model calculated [activeProfiles={}, time={} ms ",
173                     buildingRequest.getActiveProfileIds(),
174                     System.currentTimeMillis() - t0);
175 
176             List<MavenProject> projectList = buildingResults.stream()
177                     .map(ProjectBuildingResult::getProject)
178                     .collect(Collectors.toList());
179             projectMap = buildProjectMap(projectList);
180 
181         } catch (ProjectBuildingException e) {
182             LOGGER.error("Unable to build model", e);
183         } finally {
184             built = true;
185         }
186     }
187 
188     private Map<String, MavenProject> buildProjectMap(List<MavenProject> projectList) {
189         return projectList.stream().collect(Collectors.toMap(KeyUtils::getProjectKey, Function.identity()));
190     }
191 
192     private static File getMultiModulePomFile(MavenSession session) {
193         return CacheUtils.getMultimoduleRoot(session).resolve("pom.xml").toFile();
194     }
195 }