1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.shared.filtering;
20
21 import javax.inject.Inject;
22 import javax.inject.Named;
23 import javax.inject.Singleton;
24
25 import java.io.File;
26 import java.io.IOException;
27 import java.io.Reader;
28 import java.io.StringReader;
29 import java.io.StringWriter;
30 import java.nio.file.Path;
31 import java.nio.file.Paths;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.List;
35 import java.util.Locale;
36
37 import org.apache.commons.io.FilenameUtils;
38 import org.apache.commons.io.IOUtils;
39 import org.apache.maven.model.Resource;
40 import org.codehaus.plexus.util.Scanner;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43 import org.sonatype.plexus.build.incremental.BuildContext;
44
45 import static java.util.Objects.requireNonNull;
46
47
48
49
50 @Singleton
51 @Named
52 public class DefaultMavenResourcesFiltering implements MavenResourcesFiltering {
53 private static final Logger LOGGER = LoggerFactory.getLogger(DefaultMavenResourcesFiltering.class);
54
55 private static final String[] EMPTY_STRING_ARRAY = {};
56
57 private static final String[] DEFAULT_INCLUDES = {"**/**"};
58
59 private final List<String> defaultNonFilteredFileExtensions;
60
61 private final MavenFileFilter mavenFileFilter;
62
63 private final BuildContext buildContext;
64
65 @Inject
66 public DefaultMavenResourcesFiltering(MavenFileFilter mavenFileFilter, BuildContext buildContext) {
67 this.mavenFileFilter = requireNonNull(mavenFileFilter);
68 this.buildContext = requireNonNull(buildContext);
69 this.defaultNonFilteredFileExtensions = new ArrayList<>(5);
70 this.defaultNonFilteredFileExtensions.add("jpg");
71 this.defaultNonFilteredFileExtensions.add("jpeg");
72 this.defaultNonFilteredFileExtensions.add("gif");
73 this.defaultNonFilteredFileExtensions.add("bmp");
74 this.defaultNonFilteredFileExtensions.add("png");
75 this.defaultNonFilteredFileExtensions.add("ico");
76 }
77
78 @Override
79 public boolean filteredFileExtension(String fileName, List<String> userNonFilteredFileExtensions) {
80 List<String> nonFilteredFileExtensions = new ArrayList<>(getDefaultNonFilteredFileExtensions());
81 if (userNonFilteredFileExtensions != null) {
82 nonFilteredFileExtensions.addAll(userNonFilteredFileExtensions);
83 }
84 String extension = getExtension(fileName);
85 boolean filteredFileExtension = !nonFilteredFileExtensions.contains(extension);
86 if (LOGGER.isDebugEnabled()) {
87 LOGGER.debug("file " + fileName + " has a" + (filteredFileExtension ? " " : " non ")
88 + "filtered file extension");
89 }
90 return filteredFileExtension;
91 }
92
93 private static String getExtension(String fileName) {
94 String rawExt = FilenameUtils.getExtension(fileName);
95 return rawExt == null ? null : rawExt.toLowerCase(Locale.ROOT);
96 }
97
98 @Override
99 public List<String> getDefaultNonFilteredFileExtensions() {
100 return this.defaultNonFilteredFileExtensions;
101 }
102
103 @Override
104 public void filterResources(MavenResourcesExecution mavenResourcesExecution) throws MavenFilteringException {
105 if (mavenResourcesExecution == null) {
106 throw new MavenFilteringException("mavenResourcesExecution cannot be null");
107 }
108
109 if (mavenResourcesExecution.getResources() == null) {
110 LOGGER.info("No resources configured skip copying/filtering");
111 return;
112 }
113
114 if (mavenResourcesExecution.getOutputDirectory() == null) {
115 throw new MavenFilteringException("outputDirectory cannot be null");
116 }
117
118 if (mavenResourcesExecution.isUseDefaultFilterWrappers()) {
119 handleDefaultFilterWrappers(mavenResourcesExecution);
120 }
121
122 if (mavenResourcesExecution.getEncoding() == null
123 || mavenResourcesExecution.getEncoding().length() < 1) {
124 LOGGER.warn("Using platform encoding (" + System.getProperty("file.encoding")
125 + " actually) to copy filtered resources, i.e. build is platform dependent!");
126 } else {
127 LOGGER.debug("Using '" + mavenResourcesExecution.getEncoding() + "' encoding to copy filtered resources.");
128 }
129
130 if (mavenResourcesExecution.getPropertiesEncoding() == null
131 || mavenResourcesExecution.getPropertiesEncoding().length() < 1) {
132 LOGGER.debug("Using '" + mavenResourcesExecution.getEncoding()
133 + "' encoding to copy filtered properties files.");
134 } else {
135 LOGGER.debug("Using '" + mavenResourcesExecution.getPropertiesEncoding()
136 + "' encoding to copy filtered properties files.");
137 }
138
139
140 boolean isFilteringUsed = false;
141 List<File> propertiesFiles = new ArrayList<>();
142
143 for (Resource resource : mavenResourcesExecution.getResources()) {
144
145 if (LOGGER.isDebugEnabled()) {
146 String ls = System.lineSeparator();
147 StringBuilder debugMessage = new StringBuilder("resource with targetPath ")
148 .append(resource.getTargetPath())
149 .append(ls);
150 debugMessage
151 .append("directory ")
152 .append(resource.getDirectory())
153 .append(ls);
154
155
156 debugMessage
157 .append("excludes ")
158 .append(
159 resource.getExcludes() == null
160 ? " empty "
161 : resource.getExcludes().toString())
162 .append(ls);
163 debugMessage
164 .append("includes ")
165 .append(
166 resource.getIncludes() == null
167 ? " empty "
168 : resource.getIncludes().toString());
169
170
171 LOGGER.debug(debugMessage.toString());
172 }
173
174 String targetPath = resource.getTargetPath();
175
176 File resourceDirectory = (resource.getDirectory() == null) ? null : new File(resource.getDirectory());
177
178 if (resourceDirectory != null && !resourceDirectory.isAbsolute()) {
179 resourceDirectory =
180 new File(mavenResourcesExecution.getResourcesBaseDirectory(), resourceDirectory.getPath());
181 }
182
183 if (resourceDirectory == null || !resourceDirectory.exists()) {
184 LOGGER.info("skip non existing resourceDirectory " + resourceDirectory);
185 continue;
186 }
187
188
189
190
191 File outputDirectory = mavenResourcesExecution.getOutputDirectory();
192 boolean outputExists = outputDirectory.exists();
193 if (!outputExists && !outputDirectory.mkdirs()) {
194 throw new MavenFilteringException("Cannot create resource output directory: " + outputDirectory);
195 }
196
197 if (resource.isFiltering()) {
198 isFilteringUsed = true;
199 }
200
201 boolean ignoreDelta = !outputExists
202 || buildContext.hasDelta(mavenResourcesExecution.getFileFilters())
203 || buildContext.hasDelta(getRelativeOutputDirectory(mavenResourcesExecution));
204 LOGGER.debug("ignoreDelta " + ignoreDelta);
205 Scanner scanner = buildContext.newScanner(resourceDirectory, ignoreDelta);
206
207 setupScanner(resource, scanner, mavenResourcesExecution.isAddDefaultExcludes());
208
209 scanner.scan();
210
211 if (mavenResourcesExecution.isIncludeEmptyDirs()) {
212 try {
213 File targetDirectory = targetPath == null ? outputDirectory : new File(outputDirectory, targetPath);
214 copyDirectoryLayout(resourceDirectory, targetDirectory, scanner);
215 } catch (IOException e) {
216 throw new MavenFilteringException("Cannot copy directory structure from "
217 + resourceDirectory.getPath() + " to " + outputDirectory.getPath());
218 }
219 }
220
221 List<String> includedFiles = Arrays.asList(scanner.getIncludedFiles());
222
223 try {
224 Path basedir = mavenResourcesExecution
225 .getMavenProject()
226 .getBasedir()
227 .getAbsoluteFile()
228 .toPath();
229 Path destination = getDestinationFile(outputDirectory, targetPath, "", mavenResourcesExecution)
230 .getAbsoluteFile()
231 .toPath();
232 LOGGER.info("Copying " + includedFiles.size() + " resource" + (includedFiles.size() > 1 ? "s" : "")
233 + " from "
234 + basedir.relativize(resourceDirectory.getAbsoluteFile().toPath())
235 + " to "
236 + basedir.relativize(destination));
237 } catch (Exception e) {
238
239 LOGGER.info("Copying " + includedFiles.size() + " resource" + (includedFiles.size() > 1 ? "s" : "")
240 + (targetPath == null ? "" : " to " + targetPath));
241 }
242
243 for (String name : includedFiles) {
244
245 LOGGER.debug("Copying file " + name);
246 File source = new File(resourceDirectory, name);
247
248 File destinationFile = getDestinationFile(outputDirectory, targetPath, name, mavenResourcesExecution);
249
250 if (mavenResourcesExecution.isFlatten() && destinationFile.exists()) {
251 if (mavenResourcesExecution.isOverwrite()) {
252 LOGGER.warn("existing file " + destinationFile.getName() + " will be overwritten by " + name);
253 } else {
254 throw new MavenFilteringException("existing file " + destinationFile.getName()
255 + " will be overwritten by " + name + " and overwrite was not set to true");
256 }
257 }
258 boolean filteredExt =
259 filteredFileExtension(source.getName(), mavenResourcesExecution.getNonFilteredFileExtensions());
260 if (resource.isFiltering() && isPropertiesFile(source)) {
261 propertiesFiles.add(source);
262 }
263
264
265 String encoding = getEncoding(
266 source, mavenResourcesExecution.getEncoding(), mavenResourcesExecution.getPropertiesEncoding());
267 LOGGER.debug("Using '" + encoding + "' encoding to copy filtered resource '" + source.getName() + "'.");
268 mavenFileFilter.copyFile(
269 source,
270 destinationFile,
271 resource.isFiltering() && filteredExt,
272 mavenResourcesExecution.getFilterWrappers(),
273 encoding,
274 mavenResourcesExecution.isOverwrite());
275 }
276
277
278
279 scanner = buildContext.newDeleteScanner(resourceDirectory);
280
281 setupScanner(resource, scanner, mavenResourcesExecution.isAddDefaultExcludes());
282
283 scanner.scan();
284
285 for (String name : scanner.getIncludedFiles()) {
286 File destinationFile = getDestinationFile(outputDirectory, targetPath, name, mavenResourcesExecution);
287
288 destinationFile.delete();
289
290 buildContext.refresh(destinationFile);
291 }
292 }
293
294
295
296
297
298
299 if ((mavenResourcesExecution.getPropertiesEncoding() == null
300 || mavenResourcesExecution.getPropertiesEncoding().length() < 1)
301 && !mavenResourcesExecution.getNonFilteredFileExtensions().contains("properties")
302 && isFilteringUsed
303 && propertiesFiles.size() > 0) {
304
305 LOGGER.info("The encoding used to copy filtered properties files have not been set."
306 + " This means that the same encoding will be used to copy filtered properties files"
307 + " as when copying other filtered resources. This might not be what you want!"
308 + " Run your build with --debug to see which files might be affected."
309 + " Read more at "
310 + "https://maven.apache.org/plugins/maven-resources-plugin/"
311 + "examples/filtering-properties-files.html");
312
313 StringBuilder affectedFiles = new StringBuilder();
314 affectedFiles.append("Here is a list of the filtered properties files in you project that might be"
315 + " affected by encoding problems: ");
316 for (File propertiesFile : propertiesFiles) {
317 affectedFiles.append(System.lineSeparator()).append(" - ").append(propertiesFile.getPath());
318 }
319 LOGGER.debug(affectedFiles.toString());
320 }
321 }
322
323
324
325
326
327
328
329
330
331
332
333 static String getEncoding(File file, String encoding, String propertiesEncoding) {
334 if (isPropertiesFile(file)) {
335 if (propertiesEncoding == null) {
336
337
338 return encoding;
339 } else {
340 return propertiesEncoding;
341 }
342 } else {
343 return encoding;
344 }
345 }
346
347
348
349
350
351
352
353
354 static boolean isPropertiesFile(File file) {
355 return "properties".equals(getExtension(file.getName()));
356 }
357
358 private void handleDefaultFilterWrappers(MavenResourcesExecution mavenResourcesExecution)
359 throws MavenFilteringException {
360 List<FilterWrapper> filterWrappers = new ArrayList<>();
361 if (mavenResourcesExecution.getFilterWrappers() != null) {
362 filterWrappers.addAll(mavenResourcesExecution.getFilterWrappers());
363 }
364 filterWrappers.addAll(mavenFileFilter.getDefaultFilterWrappers(mavenResourcesExecution));
365 mavenResourcesExecution.setFilterWrappers(filterWrappers);
366 }
367
368 private File getDestinationFile(
369 File outputDirectory, String targetPath, String name, MavenResourcesExecution mavenResourcesExecution)
370 throws MavenFilteringException {
371 String destination;
372 if (!mavenResourcesExecution.isFlatten()) {
373 destination = name;
374 } else {
375 Path path = Paths.get(name);
376 Path filePath = path.getFileName();
377 destination = filePath.toString();
378 }
379
380 if (mavenResourcesExecution.isFilterFilenames()
381 && mavenResourcesExecution.getFilterWrappers().size() > 0) {
382 destination = filterFileName(destination, mavenResourcesExecution.getFilterWrappers());
383 }
384
385 if (targetPath != null) {
386 destination = targetPath + "/" + destination;
387 }
388
389 File destinationFile = new File(destination);
390 if (!destinationFile.isAbsolute()) {
391 destinationFile = new File(outputDirectory, destination);
392 }
393
394 if (!destinationFile.getParentFile().exists()) {
395 destinationFile.getParentFile().mkdirs();
396 }
397 return destinationFile;
398 }
399
400 private String[] setupScanner(Resource resource, Scanner scanner, boolean addDefaultExcludes) {
401 String[] includes;
402 if (resource.getIncludes() != null && !resource.getIncludes().isEmpty()) {
403 includes = resource.getIncludes().toArray(EMPTY_STRING_ARRAY);
404 } else {
405 includes = DEFAULT_INCLUDES;
406 }
407 scanner.setIncludes(includes);
408
409 String[] excludes = null;
410 if (resource.getExcludes() != null && !resource.getExcludes().isEmpty()) {
411 excludes = resource.getExcludes().toArray(EMPTY_STRING_ARRAY);
412 scanner.setExcludes(excludes);
413 }
414
415 if (addDefaultExcludes) {
416 scanner.addDefaultExcludes();
417 }
418 return includes;
419 }
420
421 private void copyDirectoryLayout(File sourceDirectory, File destinationDirectory, Scanner scanner)
422 throws IOException {
423 if (sourceDirectory == null) {
424 throw new IOException("source directory can't be null.");
425 }
426
427 if (destinationDirectory == null) {
428 throw new IOException("destination directory can't be null.");
429 }
430
431 if (sourceDirectory.equals(destinationDirectory)) {
432 throw new IOException("source and destination are the same directory.");
433 }
434
435 if (!sourceDirectory.exists()) {
436 throw new IOException("Source directory doesn't exists (" + sourceDirectory.getAbsolutePath() + ").");
437 }
438
439 for (String name : scanner.getIncludedDirectories()) {
440 File source = new File(sourceDirectory, name);
441
442 if (source.equals(sourceDirectory)) {
443 continue;
444 }
445
446 File destination = new File(destinationDirectory, name);
447 destination.mkdirs();
448 }
449 }
450
451 private String getRelativeOutputDirectory(MavenResourcesExecution execution) {
452 String relOutDir = execution.getOutputDirectory().getAbsolutePath();
453
454 if (execution.getMavenProject() != null && execution.getMavenProject().getBasedir() != null) {
455 String basedir = execution.getMavenProject().getBasedir().getAbsolutePath();
456 relOutDir = FilteringUtils.getRelativeFilePath(basedir, relOutDir);
457 if (relOutDir == null) {
458 relOutDir = execution.getOutputDirectory().getPath();
459 } else {
460 relOutDir = relOutDir.replace('\\', '/');
461 }
462 }
463
464 return relOutDir;
465 }
466
467
468
469
470 private String filterFileName(String name, List<FilterWrapper> wrappers) throws MavenFilteringException {
471
472 Reader reader = new StringReader(name);
473 for (FilterWrapper wrapper : wrappers) {
474 reader = wrapper.getReader(reader);
475 }
476
477 try (StringWriter writer = new StringWriter()) {
478 IOUtils.copy(reader, writer);
479 String filteredFilename = writer.toString();
480
481 if (LOGGER.isDebugEnabled()) {
482 LOGGER.debug("renaming filename " + name + " to " + filteredFilename);
483 }
484 return filteredFilename;
485 } catch (IOException e) {
486 throw new MavenFilteringException("Failed filtering filename" + name, e);
487 }
488 }
489 }