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 }