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