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  
40  /**
41   * @author Stephane Nicoll
42   * @version $Id: AbstractWarPackagingTask.java 985593 2010-08-14 22:12:09Z dennisl $
43   */
44  public abstract class AbstractWarPackagingTask
45      implements WarPackagingTask
46  {
47      public static final String[] DEFAULT_INCLUDES = {"**/**"};
48  
49      public static final String WEB_INF_PATH = "WEB-INF";
50  
51      public static final String META_INF_PATH = "META-INF";
52  
53      public static final String CLASSES_PATH = "WEB-INF/classes/";
54  
55      public static final String LIB_PATH = "WEB-INF/lib/";
56  
57      /**
58       * Copies the files if possible with an optional target prefix.
59       * <p/>
60       * Copy uses a first-win strategy: files that have already been copied by previous
61       * tasks are ignored. This method makes sure to update the list of protected files
62       * which gives the list of files that have already been copied.
63       * <p/>
64       * If the structure of the source directory is not the same as the root of the
65       * webapp, use the <tt>targetPrefix</tt> parameter to specify in which particular
66       * directory the files should be copied. Use <tt>null</tt> to copy the files with
67       * the same structure
68       *
69       * @param sourceId       the source id
70       * @param context        the context to use
71       * @param sourceBaseDir  the base directory from which the <tt>sourceFilesSet</tt> will be copied
72       * @param sourceFilesSet the files to be copied
73       * @param targetPrefix   the prefix to add to the target file name
74       * @throws IOException if an error occurred while copying the files
75       */
76      protected void copyFiles( String sourceId, WarPackagingContext context, File sourceBaseDir, PathSet sourceFilesSet,
77                                String targetPrefix, boolean filtered )
78          throws IOException, MojoExecutionException
79      {
80          for ( Iterator iter = sourceFilesSet.iterator(); iter.hasNext(); )
81          {
82              final String fileToCopyName = (String) iter.next();
83              final File sourceFile = new File( sourceBaseDir, fileToCopyName );
84  
85              String destinationFileName;
86              if ( targetPrefix == null )
87              {
88                  destinationFileName = fileToCopyName;
89              }
90              else
91              {
92                  destinationFileName = targetPrefix + fileToCopyName;
93              }
94              
95  
96              if ( filtered
97                  && !context.isNonFilteredExtension( sourceFile.getName() ) ) 
98              {
99                  copyFilteredFile( sourceId, context, sourceFile, destinationFileName );
100             }
101             else
102             {
103                 copyFile( sourceId, context, sourceFile, destinationFileName );
104             }
105         }
106     }
107 
108     /**
109      * Copies the files if possible as is.
110      * <p/>
111      * Copy uses a first-win strategy: files that have already been copied by previous
112      * tasks are ignored. This method makes sure to update the list of protected files
113      * which gives the list of files that have already been copied.
114      *
115      * @param sourceId       the source id
116      * @param context        the context to use
117      * @param sourceBaseDir  the base directory from which the <tt>sourceFilesSet</tt> will be copied
118      * @param sourceFilesSet the files to be copied
119      * @throws IOException if an error occurred while copying the files
120      */
121     protected void copyFiles( String sourceId, WarPackagingContext context, File sourceBaseDir, PathSet sourceFilesSet,
122                               boolean filtered )
123         throws IOException, MojoExecutionException
124     {
125         copyFiles( sourceId, context, sourceBaseDir, sourceFilesSet, null, filtered );
126     }
127 
128     /**
129      * Copy the specified file if the target location has not yet already been used.
130      * <p/>
131      * The <tt>targetFileName</tt> is the relative path according to the root of
132      * the generated web application.
133      *
134      * @param sourceId       the source id
135      * @param context        the context to use
136      * @param file           the file to copy
137      * @param targetFilename the relative path according to the root of the webapp
138      * @throws IOException if an error occurred while copying
139      */
140     protected void copyFile( String sourceId, final WarPackagingContext context, final File file,
141                              String targetFilename )
142         throws IOException
143     {
144         final File targetFile = new File( context.getWebappDirectory(), targetFilename );
145         context.getWebappStructure().registerFile( sourceId, targetFilename, new WebappStructure.RegistrationCallback()
146         {
147             public void registered( String ownerId, String targetFilename )
148                 throws IOException
149             {
150                 copyFile( context, file, targetFile, targetFilename, false );
151             }
152 
153             public void alreadyRegistered( String ownerId, String targetFilename )
154                 throws IOException
155             {
156                 copyFile( context, file, targetFile, targetFilename, true );
157             }
158 
159             public void refused( String ownerId, String targetFilename, String actualOwnerId )
160                 throws IOException
161             {
162                 context.getLog().debug( " - " + targetFilename + " wasn't copied because it has "
163                     + "already been packaged for overlay [" + actualOwnerId + "]." );
164             }
165 
166             public void superseded( String ownerId, String targetFilename, String deprecatedOwnerId )
167                 throws IOException
168             {
169                 context.getLog().info( "File [" + targetFilename + "] belonged to overlay [" + deprecatedOwnerId
170                     + "] so it will be overwritten." );
171                 copyFile( context, file, targetFile, targetFilename, false );
172             }
173 
174             public void supersededUnknownOwner( String ownerId, String targetFilename, String unknownOwnerId )
175                 throws IOException
176             {
177                 context.getLog()
178                     .warn( "File [" + targetFilename + "] belonged to overlay [" + unknownOwnerId
179                         + "] which does not exist anymore in the current project. It is recommended to invoke "
180                         + "clean if the dependencies of the project changed." );
181                 copyFile( context, file, targetFile, targetFilename, false );
182             }
183         } );
184     }
185 
186     /**
187      * Copy the specified file if the target location has not yet already been
188      * used and filter its content with the configured filter properties.
189      * <p/>
190      * The <tt>targetFileName</tt> is the relative path according to the root of
191      * the generated web application.
192      *
193      * @param sourceId       the source id
194      * @param context        the context to use
195      * @param file           the file to copy
196      * @param targetFilename the relative path according to the root of the webapp
197      * @return true if the file has been copied, false otherwise
198      * @throws IOException            if an error occurred while copying
199      * @throws MojoExecutionException if an error occurred while retrieving the filter properties
200      */
201     protected boolean copyFilteredFile( String sourceId, final WarPackagingContext context, File file,
202                                         String targetFilename )
203         throws IOException, MojoExecutionException
204     {
205 
206         if ( context.getWebappStructure().registerFile( sourceId, targetFilename ) )
207         {
208             final File targetFile = new File( context.getWebappDirectory(), targetFilename );
209             try
210             {
211                 // fix for MWAR-36, ensures that the parent dir are created first
212                 targetFile.getParentFile().mkdirs();
213                 // TODO: add encoding support (null mean platform encoding)
214                 context.getMavenFileFilter().copyFile( file, targetFile, true, context.getFilterWrappers(), null );
215             }
216             catch ( MavenFilteringException e )
217             {
218                 throw new MojoExecutionException( e.getMessage(), e );
219             }
220             // Add the file to the protected list
221             context.getLog().debug( " + " + targetFilename + " has been copied (filtered)." );
222             return true;
223         }
224         else
225         {
226             context.getLog().debug(
227                 " - " + targetFilename + " wasn't copied because it has already been packaged (filtered)." );
228             return false;
229         }
230     }
231 
232 
233     /**
234      * Unpacks the specified file to the specified directory.
235      *
236      * @param context         the packaging context
237      * @param file            the file to unpack
238      * @param unpackDirectory the directory to use for th unpacked file
239      * @throws MojoExecutionException if an error occurred while unpacking the file
240      */
241     protected void doUnpack( WarPackagingContext context, File file, File unpackDirectory )
242         throws MojoExecutionException
243     {
244         String archiveExt = FileUtils.getExtension( file.getAbsolutePath() ).toLowerCase();
245 
246         try
247         {
248             UnArchiver unArchiver = context.getArchiverManager().getUnArchiver( archiveExt );
249             unArchiver.setSourceFile( file );
250             unArchiver.setDestDirectory( unpackDirectory );
251             unArchiver.setOverwrite( true );
252             unArchiver.extract();
253         }
254         catch ( ArchiverException e )
255         {
256             throw new MojoExecutionException( "Error unpacking file [" + file.getAbsolutePath() + "]" + "to ["
257                 + unpackDirectory.getAbsolutePath() + "]", e );
258         }
259         catch ( NoSuchArchiverException e )
260         {
261             context.getLog().warn( "Skip unpacking dependency file [" + file.getAbsolutePath()
262                 + " with unknown extension [" + archiveExt + "]" );
263         }
264     }
265 
266     /**
267      * Copy file from source to destination. The directories up to <code>destination</code>
268      * will be created if they don't already exist. if the <code>onlyIfModified</code> flag
269      * is <tt>false</tt>, <code>destination</code> will be overwritten if it already exists. If the
270      * flag is <tt>true</tt> destination will be overwritten if it's not up to date.
271      * <p/>
272      *
273      * @param context        the packaging context
274      * @param source         an existing non-directory <code>File</code> to copy bytes from
275      * @param destination    a non-directory <code>File</code> to write bytes to (possibly overwriting).
276      * @param targetFilename the relative path of the file from the webapp root directory
277      * @param onlyIfModified if true, copy the file only if the source has changed, always copy otherwise
278      * @return true if the file has been copied/updated, false otherwise
279      * @throws IOException if <code>source</code> does not exist, <code>destination</code> cannot
280      *                     be written to, or an IO error occurs during copying
281      */
282     protected boolean copyFile( WarPackagingContext context, File source, File destination, String targetFilename,
283                                 boolean onlyIfModified )
284         throws IOException
285     {
286         if ( onlyIfModified && destination.lastModified() >= source.lastModified() )
287         {
288             context.getLog().debug( " * " + targetFilename + " is up to date." );
289             return false;
290         }
291         else
292         {
293             FileUtils.copyFile( source.getCanonicalFile(), destination );
294             // preserve timestamp
295             destination.setLastModified( source.lastModified() );
296             context.getLog().debug( " + " + targetFilename + " has been copied." );
297             return true;
298         }
299     }
300 
301     /**
302      * Returns the file to copy. If the includes are <tt>null</tt> or empty, the
303      * default includes are used.
304      *
305      * @param baseDir  the base directory to start from
306      * @param includes the includes
307      * @param excludes the excludes
308      * @return the files to copy
309      */
310     protected PathSet getFilesToIncludes( File baseDir, String[] includes, String[] excludes )
311     {
312         final DirectoryScanner scanner = new DirectoryScanner();
313         scanner.setBasedir( baseDir );
314 
315         if ( excludes != null )
316         {
317             scanner.setExcludes( excludes );
318         }
319         scanner.addDefaultExcludes();
320 
321         if ( includes != null && includes.length > 0 )
322         {
323             scanner.setIncludes( includes );
324         }
325         else
326         {
327             scanner.setIncludes( DEFAULT_INCLUDES );
328         }
329 
330         scanner.scan();
331 
332         return new PathSet( scanner.getIncludedFiles() );
333 
334     }
335 
336     /**
337      * Returns the final name of the specified artifact.
338      * <p/>
339      * If the <tt>outputFileNameMapping</tt> is set, it is used, otherwise
340      * the standard naming scheme is used.
341      *
342      * @param context  the packaging context
343      * @param artifact the artifact
344      * @return the converted filename of the artifact
345      */
346     protected String getArtifactFinalName( WarPackagingContext context, Artifact artifact )
347         throws InterpolationException
348     {
349         if ( context.getOutputFileNameMapping() != null )
350         {
351             return MappingUtils.evaluateFileNameMapping( context.getOutputFileNameMapping(), artifact );
352         }
353 
354         String classifier = artifact.getClassifier();
355         if ( ( classifier != null ) && !( "".equals( classifier.trim() ) ) )
356         {
357             return MappingUtils.evaluateFileNameMapping( AbstractWarMojo.DEFAULT_FILE_NAME_MAPPING_CLASSIFIER,
358                                                          artifact );
359         }
360         else
361         {
362             return MappingUtils.evaluateFileNameMapping( AbstractWarMojo.DEFAULT_FILE_NAME_MAPPING, artifact );
363         }
364 
365     }
366 }