1 package org.alfresco.maven.plugin.amp.packaging;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.maven.artifact.Artifact;
23 import org.apache.maven.plugin.MojoExecutionException;
24 import org.alfresco.maven.plugin.amp.AbstractAmpMojo;
25
26 import org.alfresco.maven.plugin.amp.util.MappingUtils;
27 import org.alfresco.maven.plugin.amp.util.PathSet;
28 import org.alfresco.maven.plugin.amp.util.AmpStructure;
29
30
31 import org.codehaus.plexus.archiver.ArchiverException;
32 import org.codehaus.plexus.archiver.UnArchiver;
33 import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
34 import org.codehaus.plexus.util.DirectoryScanner;
35 import org.codehaus.plexus.util.FileUtils;
36 import org.codehaus.plexus.util.IOUtil;
37 import org.codehaus.plexus.util.InterpolationFilterReader;
38
39 import java.io.BufferedReader;
40 import java.io.File;
41 import java.io.FileReader;
42 import java.io.FileWriter;
43 import java.io.IOException;
44 import java.io.Reader;
45 import java.io.Writer;
46 import java.util.Iterator;
47 import java.util.Map;
48
49 /***
50 * @author Stephane Nicoll
51 */
52 public abstract class AbstractAmpPackagingTask
53 implements AmpPackagingTask
54 {
55 public static final String[] DEFAULT_INCLUDES = {"**/**"};
56
57
58 public static final String META_INF_PATH = "META-INF";
59
60
61 /***
62 * Copies the files if possible with an optional target prefix.
63 * <p/>
64 * Copy uses a first-win strategy: files that have already been copied by previous
65 * tasks are ignored. This method makes sure to update the list of protected files
66 * which gives the list of files that have already been copied.
67 * <p/>
68 * If the structure of the source directory is not the same as the root of the
69 * webapp, use the <tt>targetPrefix</tt> parameter to specify in which particular
70 * directory the files should be copied. Use <tt>null</tt> to copy the files with
71 * the same structure
72 *
73 * @param sourceId the source id
74 * @param context the context to use
75 * @param sourceBaseDir the base directory from which the <tt>sourceFilesSet</tt> will be copied
76 * @param sourceFilesSet the files to be copied
77 * @param targetPrefix the prefix to add to the target file name
78 * @throws IOException if an error occured while copying the files
79 */
80 protected void copyFiles( String sourceId, AmpPackagingContext context, File sourceBaseDir, PathSet sourceFilesSet,
81 String targetPrefix )
82 throws IOException
83 {
84 for ( Iterator iter = sourceFilesSet.iterator(); iter.hasNext(); )
85 {
86 final String fileToCopyName = (String) iter.next();
87 final File sourceFile = new File( sourceBaseDir, fileToCopyName );
88
89 String destinationFileName;
90 if ( targetPrefix == null )
91 {
92 destinationFileName = fileToCopyName;
93 }
94 else
95 {
96 destinationFileName = targetPrefix + fileToCopyName;
97 }
98
99 copyFile( sourceId, context, sourceFile, destinationFileName );
100 }
101 }
102
103 /***
104 * Copies the files if possible as is.
105 * <p/>
106 * Copy uses a first-win strategy: files that have already been copied by previous
107 * tasks are ignored. This method makes sure to update the list of protected files
108 * which gives the list of files that have already been copied.
109 *
110 * @param sourceId the source id
111 * @param context the context to use
112 * @param sourceBaseDir the base directory from which the <tt>sourceFilesSet</tt> will be copied
113 * @param sourceFilesSet the files to be copied
114 * @throws IOException if an error occured while copying the files
115 */
116 protected void copyFiles( String sourceId, AmpPackagingContext context, File sourceBaseDir, PathSet sourceFilesSet )
117 throws IOException
118 {
119 copyFiles( sourceId, context, sourceBaseDir, sourceFilesSet, null );
120 }
121
122 /***
123 * Copy the specified file if the target location has not yet already been used.
124 * <p/>
125 * The <tt>targetFileName</tt> is the relative path according to the root of
126 * the generated web application.
127 *
128 * @param sourceId the source id
129 * @param context the context to use
130 * @param file the file to copy
131 * @param targetFilename the relative path according to the root of the webapp
132 * @throws IOException if an error occured while copying
133 */
134 protected void copyFile( String sourceId, final AmpPackagingContext context, final File file,
135 String targetFilename )
136 throws IOException
137 {
138 final File targetFile = new File( context.getAmpDirectory(), targetFilename );
139 context.getAmpStructure().registerFile( sourceId, targetFilename, new AmpStructure.RegistrationCallback()
140 {
141 public void registered( String ownerId, String targetFilename )
142 throws IOException
143 {
144 copyFile( context, file, targetFile, targetFilename, false );
145 }
146
147 public void alreadyRegistered( String ownerId, String targetFilename )
148 throws IOException
149 {
150 copyFile( context, file, targetFile, targetFilename, true );
151 }
152
153 public void refused( String ownerId, String targetFilename, String actualOwnerId )
154 throws IOException
155 {
156 context.getLog().debug( " - " + targetFilename + " wasn't copied because it has " +
157 "already been packaged for overlay[" + actualOwnerId + "]." );
158 }
159
160 public void superseded( String ownerId, String targetFilename, String deprecatedOwnerId )
161 throws IOException
162 {
163 context.getLog().info( "File[" + targetFilename + "] belonged to overlay[" + deprecatedOwnerId +
164 "] so it will be overwritten." );
165 copyFile( context, file, targetFile, targetFilename, false );
166 }
167
168 public void supersededUnknownOwner( String ownerId, String targetFilename, String unknownOwnerId )
169 throws IOException
170 {
171 context.getLog().warn( "File[" + targetFilename + "] belonged to overlay[" + unknownOwnerId +
172 "] which does not exist anymore in the current project. It is recommended to invoke " +
173 "clean if the dependencies of the project changed." );
174 copyFile( context, file, targetFile, targetFilename, false );
175 }
176 } );
177 }
178
179 /***
180 * Copy the specified file if the target location has not yet already been
181 * used and filter its content with the configureed filter properties.
182 * <p/>
183 * The <tt>targetFileName</tt> is the relative path according to the root of
184 * the generated web application.
185 *
186 * @param sourceId the source id
187 * @param context the context to use
188 * @param file the file to copy
189 * @param targetFilename the relative path according to the root of the webapp
190 * @return true if the file has been copied, false otherwise
191 * @throws IOException if an error occured while copying
192 * @throws MojoExecutionException if an error occured while retrieving the filter properties
193 */
194 protected boolean copyFilteredFile( String sourceId, AmpPackagingContext context, File file, String targetFilename )
195 throws IOException, MojoExecutionException
196 {
197
198 if ( context.getAmpStructure().registerFile( sourceId, targetFilename ) )
199 {
200 final File targetFile = new File( context.getAmpDirectory(), targetFilename );
201
202 Reader fileReader = null;
203 Writer fileWriter = null;
204 try
205 {
206
207 targetFile.getParentFile().mkdirs();
208
209 fileReader = new BufferedReader( new FileReader( file ) );
210 fileWriter = new FileWriter( targetFile );
211
212 Reader reader = fileReader;
213 for ( int i = 0; i < getFilterWrappers().length; i++ )
214 {
215 FilterWrapper wrapper = getFilterWrappers()[i];
216 reader = wrapper.getReader( reader, context.getFilterProperties() );
217 }
218
219 IOUtil.copy( reader, fileWriter );
220 }
221 finally
222 {
223 IOUtil.close( fileReader );
224 IOUtil.close( fileWriter );
225 }
226
227 context.getLog().debug( " + " + targetFilename + " has been copied." );
228 return true;
229 }
230 else
231 {
232 context.getLog().debug( " - " + targetFilename + " wasn't copied because it has already been packaged." );
233 return false;
234 }
235 }
236
237
238 /***
239 * Unpacks the specified file to the specified directory.
240 *
241 * @param context the packaging context
242 * @param file the file to unpack
243 * @param unpackDirectory the directory to use for th unpacked file
244 * @throws MojoExecutionException if an error occured while unpacking the file
245 */
246 protected void doUnpack( AmpPackagingContext context, File file, File unpackDirectory )
247 throws MojoExecutionException
248 {
249 String archiveExt = FileUtils.getExtension( file.getAbsolutePath() ).toLowerCase();
250
251
252
253 if ("amp".equals(archiveExt))
254 {
255 archiveExt = "zip";
256 }
257
258 try
259 {
260 UnArchiver unArchiver = context.getArchiverManager().getUnArchiver( archiveExt );
261 unArchiver.setSourceFile( file );
262 unArchiver.setDestDirectory( unpackDirectory );
263 unArchiver.setOverwrite( true );
264 unArchiver.extract();
265 }
266 catch ( IOException e )
267 {
268 throw new MojoExecutionException( "Error unpacking file[" + file.getAbsolutePath() + "]" + "to[" +
269 unpackDirectory.getAbsolutePath() + "]", e );
270 }
271 catch ( ArchiverException e )
272 {
273 throw new MojoExecutionException( "Error unpacking file[" + file.getAbsolutePath() + "]" + "to[" +
274 unpackDirectory.getAbsolutePath() + "]", e );
275 }
276 catch ( NoSuchArchiverException e )
277 {
278 context.getLog().warn( "Skip unpacking dependency file[" + file.getAbsolutePath() +
279 " with unknown extension[" + archiveExt + "]" );
280 }
281 }
282
283 /***
284 * Copy file from source to destination. The directories up to <code>destination</code>
285 * will be created if they don't already exist. if the <code>onlyIfModified</code> flag
286 * is <tt>false</tt>, <code>destination</code> will be overwritten if it already exists. If the
287 * flag is <tt>true</tt> destination will be overwritten if it's not up to date.
288 * <p/>
289 *
290 * @param context the packaging context
291 * @param source an existing non-directory <code>File</code> to copy bytes from
292 * @param destination a non-directory <code>File</code> to write bytes to (possibly overwriting).
293 * @param targetFilename the relative path of the file from the webapp root directory
294 * @param onlyIfModified if true, copy the file only if the source has changed, always copy otherwise
295 * @return true if the file has been copied/updated, false otherwise
296 * @throws IOException if <code>source</code> does not exist, <code>destination</code> cannot
297 * be written to, or an IO error occurs during copying
298 */
299 protected boolean copyFile( AmpPackagingContext context, File source, File destination, String targetFilename,
300 boolean onlyIfModified )
301 throws IOException
302 {
303 if ( onlyIfModified && destination.lastModified() >= source.lastModified() )
304 {
305 context.getLog().debug( " * " + targetFilename + " is up to date." );
306 return false;
307 }
308 else
309 {
310 FileUtils.copyFile( source.getCanonicalFile(), destination );
311
312 destination.setLastModified( source.lastModified() );
313 context.getLog().debug( " + " + targetFilename + " has been copied." );
314 return true;
315 }
316 }
317
318 /***
319 * Returns the file to copy. If the includes are <tt>null</tt> or empty, the
320 * default includes are used.
321 *
322 * @param baseDir the base directory to start from
323 * @param includes the includes
324 * @param excludes the excludes
325 * @return the files to copy
326 */
327 protected PathSet getFilesToIncludes( File baseDir, String[] includes, String[] excludes )
328 {
329 final DirectoryScanner scanner = new DirectoryScanner();
330 scanner.setBasedir( baseDir );
331
332 if ( excludes != null )
333 {
334 scanner.setExcludes( excludes );
335 }
336 scanner.addDefaultExcludes();
337
338 if ( includes != null && includes.length > 0 )
339 {
340 scanner.setIncludes( includes );
341 }
342 else
343 {
344 scanner.setIncludes( DEFAULT_INCLUDES );
345 }
346
347 scanner.scan();
348
349 return new PathSet( scanner.getIncludedFiles() );
350
351 }
352
353 /***
354 * Returns the final name of the specified artifact.
355 * <p/>
356 * If the <tt>outputFileNameMapping</tt> is set, it is used, otherwise
357 * the standard naming scheme is used.
358 *
359 * @param context the packaging context
360 * @param artifact the artifact
361 * @return the converted filename of the artifact
362 */
363 protected String getArtifactFinalName( AmpPackagingContext context, Artifact artifact )
364 {
365 if ( context.getOutputFileNameMapping() != null )
366 {
367 return MappingUtils.evaluateFileNameMapping( context.getOutputFileNameMapping(), artifact );
368 }
369
370 String classifier = artifact.getClassifier();
371 if ( ( classifier != null ) && !( "".equals( classifier.trim() ) ) )
372 {
373 return MappingUtils.evaluateFileNameMapping( AbstractAmpMojo.DEFAULT_FILE_NAME_MAPPING_CLASSIFIER,
374 artifact );
375 }
376 else
377 {
378 return MappingUtils.evaluateFileNameMapping( AbstractAmpMojo.DEFAULT_FILE_NAME_MAPPING, artifact );
379 }
380
381 }
382
383 private FilterWrapper[] getFilterWrappers()
384 {
385 return new FilterWrapper[]{
386
387 new FilterWrapper()
388 {
389 public Reader getReader( Reader fileReader, Map filterProperties )
390 {
391 return new InterpolationFilterReader( fileReader, filterProperties, "${", "}" );
392 }
393 },
394
395 new FilterWrapper()
396 {
397 public Reader getReader( Reader fileReader, Map filterProperties )
398 {
399 return new InterpolationFilterReader( fileReader, filterProperties, "@", "@" );
400 }
401 }};
402 }
403
404 private interface FilterWrapper
405 {
406 Reader getReader( Reader fileReader, Map filterProperties );
407 }
408
409 }