View Javadoc

1   package org.apache.maven.plugin.war.packaging;
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  
25  import org.apache.commons.io.input.XmlStreamReader;
26  import org.apache.maven.artifact.Artifact;
27  import org.apache.maven.plugin.MojoExecutionException;
28  import org.apache.maven.plugin.war.AbstractWarMojo;
29  import org.apache.maven.plugin.war.util.MappingUtils;
30  import org.apache.maven.plugin.war.util.PathSet;
31  import org.apache.maven.plugin.war.util.WebappStructure;
32  import org.apache.maven.shared.filtering.MavenFilteringException;
33  import org.codehaus.plexus.archiver.ArchiverException;
34  import org.codehaus.plexus.archiver.UnArchiver;
35  import org.codehaus.plexus.archiver.jar.JarArchiver;
36  import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
37  import org.codehaus.plexus.interpolation.InterpolationException;
38  import org.codehaus.plexus.util.DirectoryScanner;
39  import org.codehaus.plexus.util.FileUtils;
40  import org.codehaus.plexus.util.IOUtil;
41  
42  /**
43   * @author Stephane Nicoll
44   * @version $Id: AbstractWarPackagingTask.html 868453 2013-07-05 11:25:41Z olamy $
45   */
46  public abstract class AbstractWarPackagingTask
47      implements WarPackagingTask
48  {
49      public static final String[] DEFAULT_INCLUDES = {"**/**"};
50  
51      public static final String WEB_INF_PATH = "WEB-INF";
52  
53      public static final String META_INF_PATH = "META-INF";
54  
55      public static final String CLASSES_PATH = "WEB-INF/classes/";
56  
57      public static final String LIB_PATH = "WEB-INF/lib/";
58  
59      /**
60       * Copies the files if possible with an optional target prefix.
61       * <p/>
62       * Copy uses a first-win strategy: files that have already been copied by previous
63       * tasks are ignored. This method makes sure to update the list of protected files
64       * which gives the list of files that have already been copied.
65       * <p/>
66       * If the structure of the source directory is not the same as the root of the
67       * webapp, use the <tt>targetPrefix</tt> parameter to specify in which particular
68       * directory the files should be copied. Use <tt>null</tt> to copy the files with
69       * the same structure
70       *
71       * @param sourceId       the source id
72       * @param context        the context to use
73       * @param sourceBaseDir  the base directory from which the <tt>sourceFilesSet</tt> will be copied
74       * @param sourceFilesSet the files to be copied
75       * @param targetPrefix   the prefix to add to the target file name
76       * @throws IOException if an error occurred while copying the files
77       */
78      protected void copyFiles( String sourceId, WarPackagingContext context, File sourceBaseDir, PathSet sourceFilesSet,
79                                String targetPrefix, boolean filtered )
80          throws IOException, MojoExecutionException
81      {
82          for ( String fileToCopyName : sourceFilesSet.paths() )
83          {
84              final File sourceFile = new File( sourceBaseDir, fileToCopyName );
85  
86              String destinationFileName;
87              if ( targetPrefix == null )
88              {
89                  destinationFileName = fileToCopyName;
90              }
91              else
92              {
93                  destinationFileName = targetPrefix + fileToCopyName;
94              }
95  
96  
97              if ( filtered
98                  && !context.isNonFilteredExtension( sourceFile.getName() ) )
99              {
100                 copyFilteredFile( sourceId, context, sourceFile, destinationFileName );
101             }
102             else
103             {
104                 copyFile( sourceId, context, sourceFile, destinationFileName );
105             }
106         }
107     }
108 
109     /**
110      * Copies the files if possible as is.
111      * <p/>
112      * Copy uses a first-win strategy: files that have already been copied by previous
113      * tasks are ignored. This method makes sure to update the list of protected files
114      * which gives the list of files that have already been copied.
115      *
116      * @param sourceId       the source id
117      * @param context        the context to use
118      * @param sourceBaseDir  the base directory from which the <tt>sourceFilesSet</tt> will be copied
119      * @param sourceFilesSet the files to be copied
120      * @throws IOException if an error occurred while copying the files
121      */
122     protected void copyFiles( String sourceId, WarPackagingContext context, File sourceBaseDir, PathSet sourceFilesSet,
123                               boolean filtered )
124         throws IOException, MojoExecutionException
125     {
126         copyFiles( sourceId, context, sourceBaseDir, sourceFilesSet, null, filtered );
127     }
128 
129     /**
130      * Copy the specified file if the target location has not yet already been used.
131      * <p/>
132      * The <tt>targetFileName</tt> is the relative path according to the root of
133      * the generated web application.
134      *
135      * @param sourceId       the source id
136      * @param context        the context to use
137      * @param file           the file to copy
138      * @param targetFilename the relative path according to the root of the webapp
139      * @throws IOException if an error occurred while copying
140      */
141     protected void copyFile( String sourceId, final WarPackagingContext context, final File file,
142                              String targetFilename )
143         throws IOException
144     {
145         final File targetFile = new File( context.getWebappDirectory(), targetFilename );
146         
147         if( file.isFile() )
148         {
149             context.getWebappStructure().registerFile( sourceId, targetFilename, new WebappStructure.RegistrationCallback()
150             {
151                 public void registered( String ownerId, String targetFilename )
152                     throws IOException
153                 {
154                     copyFile( context, file, targetFile, targetFilename, false );
155                 }
156 
157                 public void alreadyRegistered( String ownerId, String targetFilename )
158                     throws IOException
159                 {
160                     copyFile( context, file, targetFile, targetFilename, true );
161                 }
162 
163                 public void refused( String ownerId, String targetFilename, String actualOwnerId )
164                     throws IOException
165                 {
166                     context.getLog().debug( " - " + targetFilename + " wasn't copied because it has "
167                         + "already been packaged for overlay [" + actualOwnerId + "]." );
168                 }
169 
170                 public void superseded( String ownerId, String targetFilename, String deprecatedOwnerId )
171                     throws IOException
172                 {
173                     context.getLog().info( "File [" + targetFilename + "] belonged to overlay [" + deprecatedOwnerId
174                         + "] so it will be overwritten." );
175                     copyFile( context, file, targetFile, targetFilename, false );
176                 }
177 
178                 public void supersededUnknownOwner( String ownerId, String targetFilename, String unknownOwnerId )
179                     throws IOException
180                 {
181                     context.getLog()
182                         .warn( "File [" + targetFilename + "] belonged to overlay [" + unknownOwnerId
183                             + "] which does not exist anymore in the current project. It is recommended to invoke "
184                             + "clean if the dependencies of the project changed." );
185                     copyFile( context, file, targetFile, targetFilename, false );
186                 }
187             } );
188         }
189         else if ( !targetFile.exists() && !targetFile.mkdirs() )
190         {
191             context.getLog().info( "Failed to create directory " + targetFile.getAbsolutePath() );
192         }
193     }
194 
195     /**
196      * Copy the specified file if the target location has not yet already been
197      * used and filter its content with the configured filter properties.
198      * <p/>
199      * The <tt>targetFileName</tt> is the relative path according to the root of
200      * the generated web application.
201      *
202      * @param sourceId       the source id
203      * @param context        the context to use
204      * @param file           the file to copy
205      * @param targetFilename the relative path according to the root of the webapp
206      * @return true if the file has been copied, false otherwise
207      * @throws IOException            if an error occurred while copying
208      * @throws MojoExecutionException if an error occurred while retrieving the filter properties
209      */
210     protected boolean copyFilteredFile( String sourceId, final WarPackagingContext context, File file,
211                                         String targetFilename )
212         throws IOException, MojoExecutionException
213     {
214 
215         if ( context.getWebappStructure().registerFile( sourceId, targetFilename ) )
216         {
217             final File targetFile = new File( context.getWebappDirectory(), targetFilename );
218             final String encoding;
219             try
220             {
221                 if ( isXmlFile( file ) )
222                 {
223                     // For xml-files we extract the encoding from the files
224                     encoding = getEncoding( file );
225                 }
226                 else
227                 {
228                     // For all others we use the configured encoding
229                     encoding = context.getResourceEncoding();
230                 }
231                 // fix for MWAR-36, ensures that the parent dir are created first
232                 targetFile.getParentFile().mkdirs();
233                 context.getMavenFileFilter().copyFile( file, targetFile, true, context.getFilterWrappers(), encoding );
234             }
235             catch ( MavenFilteringException e )
236             {
237                 throw new MojoExecutionException( e.getMessage(), e );
238             }
239             // Add the file to the protected list
240             context.getLog().debug( " + " + targetFilename + " has been copied (filtered encoding='" + encoding + "')." );
241             return true;
242         }
243         else
244         {
245             context.getLog().debug(
246                 " - " + targetFilename + " wasn't copied because it has already been packaged (filtered)." );
247             return false;
248         }
249     }
250 
251     /**
252      * Unpacks the specified file to the specified directory.
253      *
254      * @param context         the packaging context
255      * @param file            the file to unpack
256      * @param unpackDirectory the directory to use for th unpacked file
257      * @throws MojoExecutionException if an error occurred while unpacking the file
258      */
259     protected void doUnpack( WarPackagingContext context, File file, File unpackDirectory )
260         throws MojoExecutionException
261     {
262         String archiveExt = FileUtils.getExtension( file.getAbsolutePath() ).toLowerCase();
263 
264         try
265         {
266             UnArchiver unArchiver = context.getArchiverManager().getUnArchiver( archiveExt );
267             unArchiver.setSourceFile( file );
268             unArchiver.setUseJvmChmod( context.isUseJvmChmod() );
269             unArchiver.setDestDirectory( unpackDirectory );
270             unArchiver.setOverwrite( true );
271             unArchiver.extract();
272         }
273         catch ( ArchiverException e )
274         {
275             throw new MojoExecutionException( "Error unpacking file [" + file.getAbsolutePath() + "]" + "to ["
276                 + unpackDirectory.getAbsolutePath() + "]", e );
277         }
278         catch ( NoSuchArchiverException e )
279         {
280             context.getLog().warn( "Skip unpacking dependency file [" + file.getAbsolutePath()
281                 + " with unknown extension [" + archiveExt + "]" );
282         }
283     }
284 
285     /**
286      * Copy file from source to destination. The directories up to <code>destination</code>
287      * will be created if they don't already exist. if the <code>onlyIfModified</code> flag
288      * is <tt>false</tt>, <code>destination</code> will be overwritten if it already exists. If the
289      * flag is <tt>true</tt> destination will be overwritten if it's not up to date.
290      * <p/>
291      *
292      * @param context        the packaging context
293      * @param source         an existing non-directory <code>File</code> to copy bytes from
294      * @param destination    a non-directory <code>File</code> to write bytes to (possibly overwriting).
295      * @param targetFilename the relative path of the file from the webapp root directory
296      * @param onlyIfModified if true, copy the file only if the source has changed, always copy otherwise
297      * @return true if the file has been copied/updated, false otherwise
298      * @throws IOException if <code>source</code> does not exist, <code>destination</code> cannot
299      *                     be written to, or an IO error occurs during copying
300      */
301     protected boolean copyFile( WarPackagingContext context, File source, File destination, String targetFilename,
302                                 boolean onlyIfModified )
303         throws IOException
304     {
305         if ( onlyIfModified && destination.lastModified() >= source.lastModified() )
306         {
307             context.getLog().debug( " * " + targetFilename + " is up to date." );
308             return false;
309         }
310         else
311         {
312             if ( source.isDirectory() )
313             {
314                 context.getLog().warn( " + " + targetFilename + " is packaged from the source folder" );
315 
316                 try
317                 {
318                     JarArchiver archiver = context.getJarArchiver();
319                     archiver.addDirectory( source );
320                     archiver.setDestFile( destination );
321                     archiver.createArchive();
322                 }
323                 catch ( ArchiverException e )
324                 {
325                     String msg = "Failed to create " + targetFilename;
326                     context.getLog().error( msg, e );
327                     IOException ioe = new IOException( msg );
328                     ioe.initCause( e );
329                     throw ioe;
330                 }
331             }
332             else
333             {
334                 FileUtils.copyFile( source.getCanonicalFile(), destination );
335                 // preserve timestamp
336                 destination.setLastModified( source.lastModified() );
337                 context.getLog().debug( " + " + targetFilename + " has been copied." );
338             }
339             return true;
340         }
341     }
342 
343     /**
344      * Get the encoding from an XML-file.
345      *
346      * @param webXml the XML-file
347      * @return The encoding of the XML-file, or UTF-8 if it's not specified in the file
348      * @throws java.io.IOException if an error occurred while reading the file
349      */
350     protected String getEncoding( File webXml )
351         throws IOException
352     {
353         XmlStreamReader xmlReader = new XmlStreamReader( webXml );
354         try
355         {
356             return xmlReader.getEncoding();
357         }
358         finally
359         {
360             IOUtil.close( xmlReader );
361         }
362     }
363     
364     /**
365      * Returns the file to copy. If the includes are <tt>null</tt> or empty, the
366      * default includes are used.
367      *
368      * @param baseDir  the base directory to start from
369      * @param includes the includes
370      * @param excludes the excludes
371      * @return the files to copy
372      */
373     protected PathSet getFilesToIncludes( File baseDir, String[] includes, String[] excludes )
374     {
375         return getFilesToIncludes( baseDir, includes, excludes, false );
376     }
377 
378     /**
379      * Returns the file to copy. If the includes are <tt>null</tt> or empty, the
380      * default includes are used.
381      *
382      * @param baseDir  the base directory to start from
383      * @param includes the includes
384      * @param excludes the excludes
385      * @return the files to copy
386      */
387     protected PathSet getFilesToIncludes( File baseDir, String[] includes, String[] excludes, boolean includeDirectories )
388     {
389         final DirectoryScanner scanner = new DirectoryScanner();
390         scanner.setBasedir( baseDir );
391 
392         if ( excludes != null )
393         {
394             scanner.setExcludes( excludes );
395         }
396         scanner.addDefaultExcludes();
397 
398         if ( includes != null && includes.length > 0 )
399         {
400             scanner.setIncludes( includes );
401         }
402         else
403         {
404             scanner.setIncludes( DEFAULT_INCLUDES );
405         }
406 
407         scanner.scan();
408 
409         PathSet pathSet = new PathSet( scanner.getIncludedFiles() );
410         
411         if ( includeDirectories )
412         {
413             pathSet.addAll( scanner.getIncludedDirectories() );
414         }
415         
416         return pathSet;
417     }
418 
419     /**
420      * Returns the final name of the specified artifact.
421      * <p/>
422      * If the <tt>outputFileNameMapping</tt> is set, it is used, otherwise
423      * the standard naming scheme is used.
424      *
425      * @param context  the packaging context
426      * @param artifact the artifact
427      * @return the converted filename of the artifact
428      */
429     protected String getArtifactFinalName( WarPackagingContext context, Artifact artifact )
430         throws InterpolationException
431     {
432         if ( context.getOutputFileNameMapping() != null )
433         {
434             return MappingUtils.evaluateFileNameMapping( context.getOutputFileNameMapping(), artifact );
435         }
436 
437         String classifier = artifact.getClassifier();
438         if ( ( classifier != null ) && !( "".equals( classifier.trim() ) ) )
439         {
440             return MappingUtils.evaluateFileNameMapping( AbstractWarMojo.DEFAULT_FILE_NAME_MAPPING_CLASSIFIER,
441                                                          artifact );
442         }
443         else
444         {
445             return MappingUtils.evaluateFileNameMapping( AbstractWarMojo.DEFAULT_FILE_NAME_MAPPING, artifact );
446         }
447 
448     }
449 
450     /**
451      * Returns <code>true</code> if the <code>File</code>-object is a file (not
452      * a directory) that is not <code>null</code> and has a file name that ends
453      * in ".xml".
454      *
455      * @param file The file to check
456      * @return <code>true</code> if the file is an xml-file, otherwise <code>false</code>
457      * @since 2.3
458      */
459     private boolean isXmlFile( File file )
460     {
461         return file != null && file.isFile() && file.getName().endsWith( ".xml" );
462     }
463 }