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