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