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.checksum.exclude;
20  
21  import java.nio.file.Files;
22  import java.nio.file.Path;
23  import java.nio.file.Paths;
24  import java.util.ArrayList;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Properties;
30  import java.util.Set;
31  
32  import org.apache.maven.buildcache.xml.CacheConfig;
33  import org.apache.maven.buildcache.xml.config.Exclude;
34  import org.apache.maven.model.Build;
35  import org.apache.maven.project.MavenProject;
36  
37  public class ExclusionResolver {
38  
39      /**
40       * property name prefix to exclude files from input. smth like maven.build.cache.exclude.value.1 should be set in project
41       * props
42       */
43      private static final String PROJECT_PROPERTY_EXCLUDE_PREFIX = "maven.build.cache.exclude";
44  
45      public static final String PROJECT_PROPERTY_EXCLUDE_VALUE = PROJECT_PROPERTY_EXCLUDE_PREFIX + ".value";
46      public static final String PROJECT_PROPERTY_EXCLUDE_GLOB = PROJECT_PROPERTY_EXCLUDE_PREFIX + ".glob";
47      public static final String PROJECT_PROPERTY_EXCLUDE_ENTRY_TYPE = PROJECT_PROPERTY_EXCLUDE_PREFIX + ".entryType";
48      public static final String PROJECT_PROPERTY_EXCLUDE_MATCHER_TYPE = PROJECT_PROPERTY_EXCLUDE_PREFIX + ".matcherType";
49  
50      /**
51       * Directories exclusions based on a glob
52       */
53      private final List<Exclusion> directoryExclusions = new ArrayList<>();
54      /**
55       * Files exclusions based on a glob
56       */
57      private final List<Exclusion> filesExclusions = new ArrayList<>();
58      /**
59       * Direct files exclusions (based on a path targeting a file)
60       */
61      private final Set<Path> directFileExclusions = new HashSet<>();
62  
63      private final Path projectBaseDirectory;
64  
65      public ExclusionResolver(MavenProject project, CacheConfig config) {
66          addDefaultExcludes(project);
67          Path baseDirectory = project.getBasedir().toPath().toAbsolutePath();
68          projectBaseDirectory = baseDirectory;
69  
70          // Global exclusions
71          List<Exclude> excludes = config.getGlobalExcludePaths();
72          for (Exclude exclude : excludes) {
73              addExclusion(baseDirectory, exclude);
74          }
75  
76          // Project specific exclusions
77          Properties properties = project.getProperties();
78          Map<String, Exclude> propertyMap = new HashMap<>();
79          for (String propertyName : properties.stringPropertyNames()) {
80              if (propertyName.startsWith(PROJECT_PROPERTY_EXCLUDE_VALUE)) {
81                  String propertyKey = propertyName.substring(PROJECT_PROPERTY_EXCLUDE_VALUE.length());
82                  Exclude exclude = propertyMap.computeIfAbsent(propertyKey, key -> new Exclude());
83                  exclude.setValue(properties.getProperty(propertyName));
84              } else if (propertyName.startsWith(PROJECT_PROPERTY_EXCLUDE_GLOB)) {
85                  String propertyKey = propertyName.substring(PROJECT_PROPERTY_EXCLUDE_GLOB.length());
86                  Exclude exclude = propertyMap.computeIfAbsent(propertyKey, key -> new Exclude());
87                  exclude.setGlob(properties.getProperty(propertyName));
88              } else if (propertyName.startsWith(PROJECT_PROPERTY_EXCLUDE_ENTRY_TYPE)) {
89                  String propertyKey = propertyName.substring(PROJECT_PROPERTY_EXCLUDE_ENTRY_TYPE.length());
90                  Exclude exclude = propertyMap.computeIfAbsent(propertyKey, key -> new Exclude());
91                  exclude.setEntryType(properties.getProperty(propertyName));
92              } else if (propertyName.startsWith(PROJECT_PROPERTY_EXCLUDE_MATCHER_TYPE)) {
93                  String propertyKey = propertyName.substring(PROJECT_PROPERTY_EXCLUDE_MATCHER_TYPE.length());
94                  Exclude exclude = propertyMap.computeIfAbsent(propertyKey, key -> new Exclude());
95                  exclude.setMatcherType(properties.getProperty(propertyName));
96              }
97          }
98          for (Exclude propertyExclude : propertyMap.values()) {
99              addExclusion(baseDirectory, propertyExclude);
100         }
101     }
102 
103     private void addExclusion(Path baseDirectory, Exclude exclude) {
104         Exclusion exclusion = new Exclusion(baseDirectory, exclude);
105 
106         if (!Files.exists(exclusion.getAbsolutePath())) {
107             // The file does not exist in this module, no time to waste by checking the exclusion while scanning the
108             // filesystem.
109             return;
110         }
111 
112         if (Files.isDirectory(exclusion.getAbsolutePath())) {
113             switch (exclusion.getEntryType()) {
114                 case ALL:
115                     directoryExclusions.add(exclusion);
116                     filesExclusions.add(exclusion);
117                     break;
118                 case FILE:
119                     filesExclusions.add(exclusion);
120                     break;
121                 case DIRECTORY:
122                     directoryExclusions.add(exclusion);
123                     break;
124                 default:
125                     throw new RuntimeException("Exclusion range not handled.");
126             }
127         } else {
128             directFileExclusions.add(exclusion.getAbsolutePath());
129         }
130     }
131 
132     private void addDefaultExcludes(MavenProject project) {
133         Build build = project.getBuild();
134         // target by default
135         Path buildDirectoryPath = absoluteNormalizedPath(build.getDirectory());
136         // target/classes by default
137         Path outputDirectoryPath = absoluteNormalizedPath(build.getOutputDirectory());
138         // target/test-classes by default
139         Path testOutputDirectoryPath = absoluteNormalizedPath(build.getTestOutputDirectory());
140 
141         addFileAndDirectoryExclusion(
142                 new Exclusion(buildDirectoryPath, Exclusion.MatcherType.FILENAME, Exclusion.EntryType.ALL));
143 
144         if (!outputDirectoryPath.startsWith(buildDirectoryPath)) {
145             addFileAndDirectoryExclusion(
146                     new Exclusion(outputDirectoryPath, Exclusion.MatcherType.FILENAME, Exclusion.EntryType.ALL));
147         }
148         if (!testOutputDirectoryPath.startsWith(buildDirectoryPath)) {
149             addFileAndDirectoryExclusion(
150                     new Exclusion(testOutputDirectoryPath, Exclusion.MatcherType.FILENAME, Exclusion.EntryType.ALL));
151         }
152     }
153 
154     private void addFileAndDirectoryExclusion(final Exclusion exclusion) {
155         directoryExclusions.add(exclusion);
156         filesExclusions.add(exclusion);
157     }
158 
159     private Path absoluteNormalizedPath(String directory) {
160         return Paths.get(directory).toAbsolutePath().normalize();
161     }
162 
163     public boolean excludesPath(Path entryAbsolutePath) {
164         boolean isDirectory = Files.isDirectory(entryAbsolutePath);
165         // Check direct files exclusions
166         if (!isDirectory && directFileExclusions.contains(entryAbsolutePath)) {
167             return true;
168         }
169         List<Exclusion> exclusionList = isDirectory ? directoryExclusions : filesExclusions;
170         for (Exclusion exclusion : exclusionList) {
171             if (exclusion.excludesPath(projectBaseDirectory, entryAbsolutePath)) {
172                 return true;
173             }
174         }
175         return false;
176     }
177 }