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.util.LinkedHashSet;
25  import java.util.Set;
26  import java.util.concurrent.ConcurrentHashMap;
27  import java.util.concurrent.ConcurrentMap;
28  
29  import org.apache.maven.SessionScoped;
30  import org.apache.maven.buildcache.checksum.MavenProjectInput;
31  import org.apache.maven.buildcache.xml.CacheConfig;
32  import org.apache.maven.buildcache.xml.build.ProjectsInputInfo;
33  import org.apache.maven.execution.MavenSession;
34  import org.apache.maven.lifecycle.internal.builder.BuilderCommon;
35  import org.apache.maven.project.MavenProject;
36  import org.apache.maven.repository.RepositorySystem;
37  import org.slf4j.Logger;
38  import org.slf4j.LoggerFactory;
39  
40  @SessionScoped
41  @Named
42  public class DefaultProjectInputCalculator implements ProjectInputCalculator {
43  
44      private static final Logger LOGGER = LoggerFactory.getLogger(DefaultProjectInputCalculator.class);
45  
46      private final MavenSession mavenSession;
47      private final RemoteCacheRepository remoteCache;
48      private final CacheConfig cacheConfig;
49      private final RepositorySystem repoSystem;
50      private final NormalizedModelProvider normalizedModelProvider;
51      private final MultiModuleSupport multiModuleSupport;
52  
53      private final ConcurrentMap<String, ProjectsInputInfo> checkSumMap = new ConcurrentHashMap<>();
54  
55      private static final ThreadLocal<Set<String>> CURRENTLY_CALCULATING = ThreadLocal.withInitial(LinkedHashSet::new);
56  
57      @Inject
58      public DefaultProjectInputCalculator(
59              MavenSession mavenSession,
60              RemoteCacheRepository remoteCache,
61              CacheConfig cacheConfig,
62              RepositorySystem repoSystem,
63              NormalizedModelProvider rawModelProvider,
64              MultiModuleSupport multiModuleSupport) {
65          this.mavenSession = mavenSession;
66          this.remoteCache = remoteCache;
67          this.cacheConfig = cacheConfig;
68          this.repoSystem = repoSystem;
69          this.normalizedModelProvider = rawModelProvider;
70          this.multiModuleSupport = multiModuleSupport;
71      }
72  
73      @Override
74      public ProjectsInputInfo calculateInput(MavenProject project) {
75          LOGGER.info(
76                  "Going to calculate checksum for project [groupId={}, artifactId={}, version={}]",
77                  project.getGroupId(),
78                  project.getArtifactId(),
79                  project.getVersion());
80  
81          String key = BuilderCommon.getKey(project);
82          // NOTE: Do not use ConcurrentHashMap.computeIfAbsent() here because of recursive calls
83          // this could lead to runtime exception - IllegalStateException("Recursive update")
84          // in jdk 8 the result of attempt to modify items with the same hash code could lead to infinite loop
85          ProjectsInputInfo projectsInputInfo = checkSumMap.get(key);
86          if (projectsInputInfo != null) {
87              return projectsInputInfo;
88          }
89          projectsInputInfo = calculateInputInternal(key, project);
90          checkSumMap.put(key, projectsInputInfo);
91          return projectsInputInfo;
92      }
93  
94      private ProjectsInputInfo calculateInputInternal(String key, MavenProject project) {
95          Set<String> projectsSet = CURRENTLY_CALCULATING.get();
96  
97          if (!projectsSet.add(key)) {
98              throw new IllegalStateException("Checksum for project is already calculating. "
99                      + "Is there a cyclic dependencies? [project=" + key
100                     + ", setOfCalculatingProjects=" + projectsSet + "]");
101         }
102         try {
103             final MavenProjectInput input = new MavenProjectInput(
104                     project,
105                     normalizedModelProvider,
106                     multiModuleSupport,
107                     this,
108                     mavenSession,
109                     cacheConfig,
110                     repoSystem,
111                     remoteCache);
112             return input.calculateChecksum();
113         } catch (Exception e) {
114             throw new RuntimeException("Failed to calculate checksums for " + project.getArtifactId(), e);
115         } finally {
116             projectsSet.remove(key);
117         }
118     }
119 }