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