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.Collection;
25  import java.util.List;
26  import java.util.Objects;
27  import java.util.concurrent.ConcurrentHashMap;
28  import java.util.concurrent.ConcurrentMap;
29  import java.util.stream.Collectors;
30  
31  import org.apache.maven.SessionScoped;
32  import org.apache.maven.buildcache.xml.CacheConfig;
33  import org.apache.maven.lifecycle.internal.builder.BuilderCommon;
34  import org.apache.maven.model.Build;
35  import org.apache.maven.model.Dependency;
36  import org.apache.maven.model.Model;
37  import org.apache.maven.model.Plugin;
38  import org.apache.maven.model.PluginExecution;
39  import org.apache.maven.project.MavenProject;
40  import org.slf4j.Logger;
41  import org.slf4j.LoggerFactory;
42  
43  @SessionScoped
44  @Named
45  public class DefaultNormalizedModelProvider implements NormalizedModelProvider {
46  
47      private static final String NORMALIZED_VERSION = "cache-extension-version";
48  
49      private static final Logger LOGGER = LoggerFactory.getLogger(DefaultNormalizedModelProvider.class);
50  
51      private final CacheConfig cacheConfig;
52      private final MultiModuleSupport multiModuleSupport;
53      private final ConcurrentMap<String, Model> modelCache = new ConcurrentHashMap<>();
54  
55      @Inject
56      public DefaultNormalizedModelProvider(MultiModuleSupport multiModuleSupport, CacheConfig cacheConfig) {
57          this.multiModuleSupport = multiModuleSupport;
58          this.cacheConfig = cacheConfig;
59      }
60  
61      @Override
62      public Model normalizedModel(MavenProject project) {
63          MavenProject validatedProject = Objects.requireNonNull(project, "project");
64          return modelCache.computeIfAbsent(
65                  BuilderCommon.getKey(validatedProject), k -> normalizedModelInner(validatedProject));
66      }
67  
68      private Model normalizedModelInner(MavenProject project) {
69          // prefer project from multimodule than reactor because effective pom of reactor project
70          // could be built with maven local/remote dependencies but not with artifacts from cache
71          MavenProject projectToNormalize = multiModuleSupport
72                  .tryToResolveProject(project.getGroupId(), project.getArtifactId(), project.getVersion())
73                  .orElse(project);
74          Model prototype = projectToNormalize.getModel();
75  
76          // TODO validate status of the model - it should be in resolved state
77          Model resultModel = new Model();
78  
79          resultModel.setGroupId(prototype.getGroupId());
80          resultModel.setArtifactId(prototype.getArtifactId());
81          // does not make sense to add project version to calculate hash
82          resultModel.setVersion(NORMALIZED_VERSION);
83          resultModel.setModules(prototype.getModules());
84  
85          resultModel.setDependencies(normalizeDependencies(prototype.getDependencies()));
86  
87          org.apache.maven.model.Build protoBuild = prototype.getBuild();
88          if (protoBuild == null) {
89              return resultModel;
90          }
91  
92          Build build = new Build();
93          List<Plugin> plugins = prototype.getBuild().getPlugins();
94          build.setPlugins(normalizePlugins(plugins));
95          resultModel.setBuild(build);
96          return resultModel;
97      }
98  
99      private List<Dependency> normalizeDependencies(Collection<Dependency> source) {
100         return source.stream()
101                 .map(it -> {
102                     if (!multiModuleSupport.isPartOfMultiModule(it.getGroupId(), it.getArtifactId(), it.getVersion())) {
103                         return it;
104                     }
105                     Dependency cloned = it.clone();
106                     cloned.setVersion(NORMALIZED_VERSION);
107                     return cloned;
108                 })
109                 .sorted(DefaultNormalizedModelProvider::compareDependencies)
110                 .collect(Collectors.toList());
111     }
112 
113     private List<Plugin> normalizePlugins(List<Plugin> plugins) {
114         if (plugins.isEmpty()) {
115             return plugins;
116         }
117 
118         return plugins.stream()
119                 .map(plugin -> {
120                     Plugin copy = plugin.clone();
121                     List<String> excludeProperties = cacheConfig.getEffectivePomExcludeProperties(copy);
122                     if (!excludeProperties.isEmpty()) {
123                         CacheUtils.debugPrintCollection(
124                                 LOGGER,
125                                 excludeProperties,
126                                 String.format("List of excluded properties for %s", copy.getArtifactId()),
127                                 "Excluded property");
128                         removeBlacklistedAttributes(copy.getConfiguration(), excludeProperties);
129                         for (PluginExecution execution : copy.getExecutions()) {
130                             removeBlacklistedAttributes(execution.getConfiguration(), excludeProperties);
131                         }
132                     }
133 
134                     copy.setDependencies(normalizeDependencies(copy.getDependencies().stream()
135                             .sorted(DefaultNormalizedModelProvider::compareDependencies)
136                             .collect(Collectors.toList())));
137                     if (multiModuleSupport.isPartOfMultiModule(
138                             copy.getGroupId(), copy.getArtifactId(), copy.getVersion())) {
139                         copy.setVersion(NORMALIZED_VERSION);
140                     }
141                     return copy;
142                 })
143                 .collect(Collectors.toList());
144     }
145 
146     private void removeBlacklistedAttributes(Object node, List<String> excludeProperties) {
147         if (node == null) {
148             return;
149         }
150 
151         Object[] children = Xpp3DomUtils.getChildren(node);
152         int indexToRemove = 0;
153         for (Object child : children) {
154             if (excludeProperties.contains(Xpp3DomUtils.getName(child))) {
155                 Xpp3DomUtils.removeChild(node, indexToRemove);
156                 continue;
157             }
158             indexToRemove++;
159             removeBlacklistedAttributes(child, excludeProperties);
160         }
161     }
162 
163     private static int compareDependencies(Dependency d1, Dependency d2) {
164         return d1.getArtifactId().compareTo(d2.getArtifactId());
165     }
166 }