View Javadoc
1   package org.apache.maven.shared.filtering;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *    http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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.maven.execution.MavenSession;
32  import org.apache.maven.model.Resource;
33  import org.apache.maven.project.MavenProject;
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;
38  import org.apache.maven.shared.utils.io.FileUtils.FilterWrapper;
39  import org.apache.maven.shared.utils.io.IOUtil;
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   * @author Olivier Lamy
48   * @plexus.component role="org.apache.maven.shared.filtering.MavenResourcesFiltering"
49   * role-hint="default"
50   */
51  public class DefaultMavenResourcesFiltering
52      extends AbstractLogEnabled
53      implements MavenResourcesFiltering, Initializable
54  {
55  
56      private static final String[] EMPTY_STRING_ARRAY = { };
57  
58      private static final String[] DEFAULT_INCLUDES = { "**/**" };
59  
60      private List<String> defaultNonFilteredFileExtensions;
61  
62      /**
63       * @plexus.requirement
64       */
65      private BuildContext buildContext;
66  
67      // ------------------------------------------------
68      //  Plexus lifecycle
69      // ------------------------------------------------
70      public void initialize()
71          throws InitializationException
72      {
73          // jpg,jpeg,gif,bmp,png
74          this.defaultNonFilteredFileExtensions = new ArrayList<String>( 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      }
81  
82  
83      /**
84       * @plexus.requirement role-hint="default"
85       */
86      private MavenFileFilter mavenFileFilter;
87  
88      public void filterResources( List<Resource> resources, File outputDirectory, MavenProject mavenProject,
89                                   String encoding, List<String> fileFilters, List<String> nonFilteredFileExtensions,
90                                   MavenSession mavenSession )
91          throws MavenFilteringException
92      {
93          MavenResourcesExecution mavenResourcesExecution =
94              new MavenResourcesExecution( resources, outputDirectory, mavenProject, encoding, fileFilters,
95                                           nonFilteredFileExtensions, mavenSession );
96          mavenResourcesExecution.setUseDefaultFilterWrappers( true );
97  //        mavenResourcesExecution.setEscapeWindowsPaths( false );
98  
99          filterResources( mavenResourcesExecution );
100     }
101 
102     public void filterResources( List<Resource> resources, File outputDirectory, String encoding,
103                                  List<FileUtils.FilterWrapper> filterWrappers, File resourcesBaseDirectory,
104                                  List<String> nonFilteredFileExtensions )
105         throws MavenFilteringException
106     {
107         MavenResourcesExecution mavenResourcesExecution =
108             new MavenResourcesExecution( resources, outputDirectory, encoding, filterWrappers, resourcesBaseDirectory,
109                                          nonFilteredFileExtensions );
110         filterResources( mavenResourcesExecution );
111     }
112 
113 
114     public boolean filteredFileExtension( String fileName, List<String> userNonFilteredFileExtensions )
115     {
116         List<String> nonFilteredFileExtensions = new ArrayList<String>( getDefaultNonFilteredFileExtensions() );
117         if ( userNonFilteredFileExtensions != null )
118         {
119             nonFilteredFileExtensions.addAll( userNonFilteredFileExtensions );
120         }
121         boolean filteredFileExtension =
122             !nonFilteredFileExtensions.contains( StringUtils.lowerCase( FileUtils.extension( fileName ) ) );
123         if ( getLogger().isDebugEnabled() )
124         {
125             getLogger().debug(
126                 "file " + fileName + " has a" + ( filteredFileExtension ? " " : " non " ) + "filtered file extension" );
127         }
128         return filteredFileExtension;
129     }
130 
131     public List<String> getDefaultNonFilteredFileExtensions()
132     {
133         if ( this.defaultNonFilteredFileExtensions == null )
134         {
135             this.defaultNonFilteredFileExtensions = new ArrayList<String>();
136         }
137         return this.defaultNonFilteredFileExtensions;
138     }
139 
140     public void filterResources( MavenResourcesExecution mavenResourcesExecution )
141         throws MavenFilteringException
142     {
143         if ( mavenResourcesExecution == null )
144         {
145             throw new MavenFilteringException( "mavenResourcesExecution cannot be null" );
146         }
147 
148         if ( mavenResourcesExecution.getResources() == null )
149         {
150             getLogger().info( "No resources configured skip copying/filtering" );
151             return;
152         }
153 
154         if ( mavenResourcesExecution.getOutputDirectory() == null )
155         {
156             throw new MavenFilteringException( "outputDirectory cannot be null" );
157         }
158 
159         if ( mavenResourcesExecution.isUseDefaultFilterWrappers() )
160         {
161             List<FileUtils.FilterWrapper> filterWrappers = new ArrayList<FileUtils.FilterWrapper>();
162             if ( mavenResourcesExecution.getFilterWrappers() != null )
163             {
164                 filterWrappers.addAll( mavenResourcesExecution.getFilterWrappers() );
165             }
166             filterWrappers.addAll( mavenFileFilter.getDefaultFilterWrappers( mavenResourcesExecution ) );
167             mavenResourcesExecution.setFilterWrappers( filterWrappers );
168         }
169 
170         if ( mavenResourcesExecution.getEncoding() == null || mavenResourcesExecution.getEncoding().length() < 1 )
171         {
172             getLogger().warn( "Using platform encoding (" + ReaderFactory.FILE_ENCODING
173                                   + " actually) to copy filtered resources, i.e. build is platform dependent!" );
174         }
175         else
176         {
177             getLogger().info(
178                 "Using '" + mavenResourcesExecution.getEncoding() + "' encoding to copy filtered resources." );
179         }
180 
181         for ( Resource resource : mavenResourcesExecution.getResources() )
182         {
183 
184             if ( getLogger().isDebugEnabled() )
185             {
186                 String ls = System.getProperty( "line.separator" );
187                 StringBuffer debugMessage =
188                     new StringBuffer( "resource with targetPath " ).append( resource.getTargetPath() ).append( ls );
189                 debugMessage.append( "directory " ).append( resource.getDirectory() ).append( ls );
190                 debugMessage.append( "excludes " ).append( resource.getExcludes() == null ? " empty "
191                                                                            : resource.getExcludes().toString() ).append( ls );
192                 debugMessage.append( "includes " ).append( resource.getIncludes() == null ? " empty "
193                                                                            : resource.getIncludes().toString() );
194                 getLogger().debug( debugMessage.toString() );
195             }
196 
197             String targetPath = resource.getTargetPath();
198 
199             File resourceDirectory = new File( resource.getDirectory() );
200 
201             if ( !resourceDirectory.isAbsolute() )
202             {
203                 resourceDirectory =
204                     new File( mavenResourcesExecution.getResourcesBaseDirectory(), resourceDirectory.getPath() );
205             }
206 
207             if ( !resourceDirectory.exists() )
208             {
209                 getLogger().info( "skip non existing resourceDirectory " + resourceDirectory.getPath() );
210                 continue;
211             }
212 
213             // this part is required in case the user specified "../something" as destination
214             // see MNG-1345
215             File outputDirectory = mavenResourcesExecution.getOutputDirectory();
216             boolean outputExists = outputDirectory.exists();
217             if ( !outputExists && !outputDirectory.mkdirs() )
218             {
219                 throw new MavenFilteringException( "Cannot create resource output directory: " + outputDirectory );
220             }
221 
222             boolean ignoreDelta = !outputExists || buildContext.hasDelta( mavenResourcesExecution.getFileFilters() )
223                 || buildContext.hasDelta( getRelativeOutputDirectory( mavenResourcesExecution ) );
224             getLogger().debug( "ignoreDelta " + ignoreDelta );
225             Scanner scanner = buildContext.newScanner( resourceDirectory, ignoreDelta );
226 
227             setupScanner( resource, scanner );
228 
229             scanner.scan();
230 
231             if ( mavenResourcesExecution.isIncludeEmptyDirs() )
232             {
233                 try
234                 {
235                     File targetDirectory =
236                         targetPath == null ? outputDirectory : new File( outputDirectory, targetPath );
237                     copyDirectoryLayout( resourceDirectory, targetDirectory, scanner );
238                 }
239                 catch ( IOException e )
240                 {
241                     throw new MavenFilteringException(
242                         "Cannot copy directory structure from " + resourceDirectory.getPath() + " to "
243                             + outputDirectory.getPath() );
244                 }
245             }
246 
247             List<String> includedFiles = Arrays.asList( scanner.getIncludedFiles() );
248 
249             getLogger().info(
250                 "Copying " + includedFiles.size() + " resource" + ( includedFiles.size() > 1 ? "s" : "" ) + (
251                     targetPath == null ? "" : " to " + targetPath ) );
252 
253             for ( String name : includedFiles )
254             {
255 
256                 File source = new File( resourceDirectory, name );
257 
258                 File destinationFile =
259                     getDestinationFile( outputDirectory, targetPath, name, mavenResourcesExecution );
260 
261                 boolean filteredExt =
262                     filteredFileExtension( source.getName(), mavenResourcesExecution.getNonFilteredFileExtensions() );
263 
264                 mavenFileFilter.copyFile( source, destinationFile, resource.isFiltering() && filteredExt,
265                                           mavenResourcesExecution.getFilterWrappers(),
266                                           mavenResourcesExecution.getEncoding(),
267                                           mavenResourcesExecution.isOverwrite() );
268             }
269 
270             // deal with deleted source files
271 
272             scanner = buildContext.newDeleteScanner( resourceDirectory );
273 
274             setupScanner( resource, scanner );
275 
276             scanner.scan();
277 
278             List<String> deletedFiles = Arrays.asList( scanner.getIncludedFiles() );
279 
280             for ( String name : deletedFiles )
281             {
282                 File destinationFile =
283                     getDestinationFile( outputDirectory, targetPath, name, mavenResourcesExecution );
284 
285                 destinationFile.delete();
286 
287                 buildContext.refresh( destinationFile );
288             }
289 
290         }
291 
292     }
293 
294     private File getDestinationFile( File outputDirectory, String targetPath, String name, MavenResourcesExecution mavenResourcesExecution )
295         throws MavenFilteringException
296     {
297         String destination = name;
298         
299         if ( mavenResourcesExecution.isFilterFilenames() && mavenResourcesExecution.getFilterWrappers().size() > 0 )
300         {
301             destination = filterFileName( destination, mavenResourcesExecution.getFilterWrappers() );
302         }
303 
304         if ( targetPath != null )
305         {
306             destination = targetPath + "/" + destination;
307         }
308 
309         File destinationFile = new File( destination );
310         if ( !destinationFile.isAbsolute() )
311         {
312             destinationFile = new File( outputDirectory, destination );
313         }
314 
315         if ( !destinationFile.getParentFile().exists() )
316         {
317             destinationFile.getParentFile().mkdirs();
318         }
319         return destinationFile;
320     }
321 
322     private String[] setupScanner( Resource resource, Scanner scanner )
323     {
324         String[] includes = null;
325         if ( resource.getIncludes() != null && !resource.getIncludes().isEmpty() )
326         {
327             includes = (String[]) resource.getIncludes().toArray( EMPTY_STRING_ARRAY );
328         }
329         else
330         {
331             includes = DEFAULT_INCLUDES;
332         }
333         scanner.setIncludes( includes );
334 
335         String[] excludes = null;
336         if ( resource.getExcludes() != null && !resource.getExcludes().isEmpty() )
337         {
338             excludes = (String[]) resource.getExcludes().toArray( EMPTY_STRING_ARRAY );
339             scanner.setExcludes( excludes );
340         }
341 
342         scanner.addDefaultExcludes();
343         return includes;
344     }
345 
346     private void copyDirectoryLayout( File sourceDirectory, File destinationDirectory, Scanner scanner )
347         throws IOException
348     {
349         if ( sourceDirectory == null )
350         {
351             throw new IOException( "source directory can't be null." );
352         }
353 
354         if ( destinationDirectory == null )
355         {
356             throw new IOException( "destination directory can't be null." );
357         }
358 
359         if ( sourceDirectory.equals( destinationDirectory ) )
360         {
361             throw new IOException( "source and destination are the same directory." );
362         }
363 
364         if ( !sourceDirectory.exists() )
365         {
366             throw new IOException( "Source directory doesn't exists (" + sourceDirectory.getAbsolutePath() + ")." );
367         }
368 
369         List<String> includedDirectories = Arrays.asList( scanner.getIncludedDirectories() );
370 
371         for ( String name : includedDirectories )
372         {
373             File source = new File( sourceDirectory, name );
374 
375             if ( source.equals( sourceDirectory ) )
376             {
377                 continue;
378             }
379 
380             File destination = new File( destinationDirectory, name );
381             destination.mkdirs();
382         }
383     }
384 
385     private String getRelativeOutputDirectory( MavenResourcesExecution execution )
386     {
387         String relOutDir = execution.getOutputDirectory().getAbsolutePath();
388 
389         if ( execution.getMavenProject() != null && execution.getMavenProject().getBasedir() != null )
390         {
391             String basedir = execution.getMavenProject().getBasedir().getAbsolutePath();
392             relOutDir = PathTool.getRelativeFilePath( basedir, relOutDir );
393             if ( relOutDir == null )
394             {
395                 relOutDir = execution.getOutputDirectory().getPath();
396             }
397             else
398             {
399                 relOutDir = relOutDir.replace( '\\', '/' );
400             }
401         }
402 
403         return relOutDir;
404     }
405     
406     /*
407      * Filter the name of a file using the same mechanism for filtering the content of the file.
408      */
409     private String filterFileName( String name, List<FilterWrapper> wrappers )
410         throws MavenFilteringException
411     {
412 
413         Reader reader = new StringReader( name );
414         for ( FilterWrapper wrapper : wrappers )
415         {
416             reader = wrapper.getReader( reader );
417         }
418 
419         StringWriter writer = new StringWriter();
420 
421         try
422         {
423             IOUtil.copy( reader, writer );
424         }
425         catch ( IOException e )
426         {
427             throw new MavenFilteringException( "Failed filtering filename" + name, e );
428         }
429 
430         String filteredFilename = writer.toString();
431 
432         if ( getLogger().isDebugEnabled() )
433         {
434             getLogger().debug( "renaming filename " + name + " to " + filteredFilename );
435         }
436         return filteredFilename;
437     }
438 
439 }