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  import java.util.Iterator;
25  
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.manager.NoSuchArchiverException;
36  import org.codehaus.plexus.interpolation.InterpolationException;
37  import org.codehaus.plexus.util.DirectoryScanner;
38  import org.codehaus.plexus.util.FileUtils;
39  import org.codehaus.plexus.util.IOUtil;
40  import org.codehaus.plexus.util.xml.XmlStreamReader;
41  
42  /**
43   * @author Stephane Nicoll
44   * @version $Id: AbstractWarPackagingTask.java 1388368 2012-09-21 08:23:24Z dennisl $
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 ( Iterator iter = sourceFilesSet.iterator(); iter.hasNext(); )
83          {
84              final String fileToCopyName = (String) iter.next();
85              final File sourceFile = new File( sourceBaseDir, fileToCopyName );
86  
87              String destinationFileName;
88              if ( targetPrefix == null )
89              {
90                  destinationFileName = fileToCopyName;
91              }
92              else
93              {
94                  destinationFileName = targetPrefix + fileToCopyName;
95              }
96              
97  
98              if ( filtered
99                  && !context.isNonFilteredExtension( sourceFile.getName() ) ) 
100             {
101                 copyFilteredFile( sourceId, context, sourceFile, destinationFileName );
102             }
103             else
104             {
105                 copyFile( sourceId, context, sourceFile, destinationFileName );
106             }
107         }
108     }
109 
110     /**
111      * Copies the files if possible as is.
112      * <p/>
113      * Copy uses a first-win strategy: files that have already been copied by previous
114      * tasks are ignored. This method makes sure to update the list of protected files
115      * which gives the list of files that have already been copied.
116      *
117      * @param sourceId       the source id
118      * @param context        the context to use
119      * @param sourceBaseDir  the base directory from which the <tt>sourceFilesSet</tt> will be copied
120      * @param sourceFilesSet the files to be copied
121      * @throws IOException if an error occurred while copying the files
122      */
123     protected void copyFiles( String sourceId, WarPackagingContext context, File sourceBaseDir, PathSet sourceFilesSet,
124                               boolean filtered )
125         throws IOException, MojoExecutionException
126     {
127         copyFiles( sourceId, context, sourceBaseDir, sourceFilesSet, null, filtered );
128     }
129 
130     /**
131      * Copy the specified file if the target location has not yet already been used.
132      * <p/>
133      * The <tt>targetFileName</tt> is the relative path according to the root of
134      * the generated web application.
135      *
136      * @param sourceId       the source id
137      * @param context        the context to use
138      * @param file           the file to copy
139      * @param targetFilename the relative path according to the root of the webapp
140      * @throws IOException if an error occurred while copying
141      */
142     protected void copyFile( String sourceId, final WarPackagingContext context, final File file,
143                              String targetFilename )
144         throws IOException
145     {
146         final File targetFile = new File( context.getWebappDirectory(), targetFilename );
147         context.getWebappStructure().registerFile( sourceId, targetFilename, new WebappStructure.RegistrationCallback()
148         {
149             public void registered( String ownerId, String targetFilename )
150                 throws IOException
151             {
152                 copyFile( context, file, targetFile, targetFilename, false );
153             }
154 
155             public void alreadyRegistered( String ownerId, String targetFilename )
156                 throws IOException
157             {
158                 copyFile( context, file, targetFile, targetFilename, true );
159             }
160 
161             public void refused( String ownerId, String targetFilename, String actualOwnerId )
162                 throws IOException
163             {
164                 context.getLog().debug( " - " + targetFilename + " wasn't copied because it has "
165                     + "already been packaged for overlay [" + actualOwnerId + "]." );
166             }
167 
168             public void superseded( String ownerId, String targetFilename, String deprecatedOwnerId )
169                 throws IOException
170             {
171                 context.getLog().info( "File [" + targetFilename + "] belonged to overlay [" + deprecatedOwnerId
172                     + "] so it will be overwritten." );
173                 copyFile( context, file, targetFile, targetFilename, false );
174             }
175 
176             public void supersededUnknownOwner( String ownerId, String targetFilename, String unknownOwnerId )
177                 throws IOException
178             {
179                 context.getLog()
180                     .warn( "File [" + targetFilename + "] belonged to overlay [" + unknownOwnerId
181                         + "] which does not exist anymore in the current project. It is recommended to invoke "
182                         + "clean if the dependencies of the project changed." );
183                 copyFile( context, file, targetFile, targetFilename, false );
184             }
185         } );
186     }
187 
188     /**
189      * Copy the specified file if the target location has not yet already been
190      * used and filter its content with the configured filter properties.
191      * <p/>
192      * The <tt>targetFileName</tt> is the relative path according to the root of
193      * the generated web application.
194      *
195      * @param sourceId       the source id
196      * @param context        the context to use
197      * @param file           the file to copy
198      * @param targetFilename the relative path according to the root of the webapp
199      * @return true if the file has been copied, false otherwise
200      * @throws IOException            if an error occurred while copying
201      * @throws MojoExecutionException if an error occurred while retrieving the filter properties
202      */
203     protected boolean copyFilteredFile( String sourceId, final WarPackagingContext context, File file,
204                                         String targetFilename )
205         throws IOException, MojoExecutionException
206     {
207 
208         if ( context.getWebappStructure().registerFile( sourceId, targetFilename ) )
209         {
210             final File targetFile = new File( context.getWebappDirectory(), targetFilename );
211             final String encoding;
212             try
213             {
214                 if ( isXmlFile( file ) )
215                 {
216                     // For xml-files we extract the encoding from the files
217                     encoding = getEncoding( file );
218                 }
219                 else
220                 {
221                     // For all others we use the configured encoding
222                     encoding = context.getResourceEncoding();
223                 }
224                 // fix for MWAR-36, ensures that the parent dir are created first
225                 targetFile.getParentFile().mkdirs();
226                 context.getMavenFileFilter().copyFile( file, targetFile, true, context.getFilterWrappers(), encoding );
227             }
228             catch ( MavenFilteringException e )
229             {
230                 throw new MojoExecutionException( e.getMessage(), e );
231             }
232             // Add the file to the protected list
233             context.getLog().debug( " + " + targetFilename + " has been copied (filtered encoding='" + encoding + "')." );
234             return true;
235         }
236         else
237         {
238             context.getLog().debug(
239                 " - " + targetFilename + " wasn't copied because it has already been packaged (filtered)." );
240             return false;
241         }
242     }
243 
244     /**
245      * Unpacks the specified file to the specified directory.
246      *
247      * @param context         the packaging context
248      * @param file            the file to unpack
249      * @param unpackDirectory the directory to use for th unpacked file
250      * @throws MojoExecutionException if an error occurred while unpacking the file
251      */
252     protected void doUnpack( WarPackagingContext context, File file, File unpackDirectory )
253         throws MojoExecutionException
254     {
255         String archiveExt = FileUtils.getExtension( file.getAbsolutePath() ).toLowerCase();
256 
257         try
258         {
259             UnArchiver unArchiver = context.getArchiverManager().getUnArchiver( archiveExt );
260             unArchiver.setSourceFile( file );
261             unArchiver.setDestDirectory( unpackDirectory );
262             unArchiver.setOverwrite( true );
263             unArchiver.extract();
264         }
265         catch ( ArchiverException e )
266         {
267             throw new MojoExecutionException( "Error unpacking file [" + file.getAbsolutePath() + "]" + "to ["
268                 + unpackDirectory.getAbsolutePath() + "]", e );
269         }
270         catch ( NoSuchArchiverException e )
271         {
272             context.getLog().warn( "Skip unpacking dependency file [" + file.getAbsolutePath()
273                 + " with unknown extension [" + archiveExt + "]" );
274         }
275     }
276 
277     /**
278      * Copy file from source to destination. The directories up to <code>destination</code>
279      * will be created if they don't already exist. if the <code>onlyIfModified</code> flag
280      * is <tt>false</tt>, <code>destination</code> will be overwritten if it already exists. If the
281      * flag is <tt>true</tt> destination will be overwritten if it's not up to date.
282      * <p/>
283      *
284      * @param context        the packaging context
285      * @param source         an existing non-directory <code>File</code> to copy bytes from
286      * @param destination    a non-directory <code>File</code> to write bytes to (possibly overwriting).
287      * @param targetFilename the relative path of the file from the webapp root directory
288      * @param onlyIfModified if true, copy the file only if the source has changed, always copy otherwise
289      * @return true if the file has been copied/updated, false otherwise
290      * @throws IOException if <code>source</code> does not exist, <code>destination</code> cannot
291      *                     be written to, or an IO error occurs during copying
292      */
293     protected boolean copyFile( WarPackagingContext context, File source, File destination, String targetFilename,
294                                 boolean onlyIfModified )
295         throws IOException
296     {
297         if ( onlyIfModified && destination.lastModified() >= source.lastModified() )
298         {
299             context.getLog().debug( " * " + targetFilename + " is up to date." );
300             return false;
301         }
302         else
303         {
304             FileUtils.copyFile( source.getCanonicalFile(), destination );
305             // preserve timestamp
306             destination.setLastModified( source.lastModified() );
307             context.getLog().debug( " + " + targetFilename + " has been copied." );
308             return true;
309         }
310     }
311 
312     /**
313      * Get the encoding from an XML-file.
314      *
315      * @param webXml the XML-file
316      * @return The encoding of the XML-file, or UTF-8 if it's not specified in the file
317      * @throws java.io.IOException if an error occurred while reading the file
318      */
319     protected String getEncoding( File webXml )
320         throws IOException
321     {
322         XmlStreamReader xmlReader = new XmlStreamReader( webXml );
323         try
324         {
325             return xmlReader.getEncoding();
326         }
327         finally
328         {
329             IOUtil.close( xmlReader );
330         }
331     }
332 
333     /**
334      * Returns the file to copy. If the includes are <tt>null</tt> or empty, the
335      * default includes are used.
336      *
337      * @param baseDir  the base directory to start from
338      * @param includes the includes
339      * @param excludes the excludes
340      * @return the files to copy
341      */
342     protected PathSet getFilesToIncludes( File baseDir, String[] includes, String[] excludes )
343     {
344         final DirectoryScanner scanner = new DirectoryScanner();
345         scanner.setBasedir( baseDir );
346 
347         if ( excludes != null )
348         {
349             scanner.setExcludes( excludes );
350         }
351         scanner.addDefaultExcludes();
352 
353         if ( includes != null && includes.length > 0 )
354         {
355             scanner.setIncludes( includes );
356         }
357         else
358         {
359             scanner.setIncludes( DEFAULT_INCLUDES );
360         }
361 
362         scanner.scan();
363 
364         return new PathSet( scanner.getIncludedFiles() );
365 
366     }
367 
368     /**
369      * Returns the final name of the specified artifact.
370      * <p/>
371      * If the <tt>outputFileNameMapping</tt> is set, it is used, otherwise
372      * the standard naming scheme is used.
373      *
374      * @param context  the packaging context
375      * @param artifact the artifact
376      * @return the converted filename of the artifact
377      */
378     protected String getArtifactFinalName( WarPackagingContext context, Artifact artifact )
379         throws InterpolationException
380     {
381         if ( context.getOutputFileNameMapping() != null )
382         {
383             return MappingUtils.evaluateFileNameMapping( context.getOutputFileNameMapping(), artifact );
384         }
385 
386         String classifier = artifact.getClassifier();
387         if ( ( classifier != null ) && !( "".equals( classifier.trim() ) ) )
388         {
389             return MappingUtils.evaluateFileNameMapping( AbstractWarMojo.DEFAULT_FILE_NAME_MAPPING_CLASSIFIER,
390                                                          artifact );
391         }
392         else
393         {
394             return MappingUtils.evaluateFileNameMapping( AbstractWarMojo.DEFAULT_FILE_NAME_MAPPING, artifact );
395         }
396 
397     }
398 
399     /**
400      * Returns <code>true</code> if the <code>File</code>-object is a file (not
401      * a directory) that is not <code>null</code> and has a file name that ends
402      * in ".xml".
403      *
404      * @param file The file to check
405      * @return <code>true</code> if the file is an xml-file, otherwise <code>false</code>
406      * @since 2.3
407      */
408     private boolean isXmlFile( File file )
409     {
410         return file != null && file.isFile() && file.getName().endsWith( ".xml" );
411     }
412 }