View Javadoc

1   package org.alfresco.maven.plugin.amp.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 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             // buffer so it isn't reading a byte at a time!
202             Reader fileReader = null;
203             Writer fileWriter = null;
204             try
205             {
206                 // fix for MWAR-36, ensures that the parent dir are created first
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             // Add the file to the protected list
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         // Uncompressing an AMP into another AMP does not require any 
252         // special treatment so we just use a zip unarchiver 
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             // preserve timestamp
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             // support ${token}
387             new FilterWrapper()
388             {
389                 public Reader getReader( Reader fileReader, Map filterProperties )
390                 {
391                     return new InterpolationFilterReader( fileReader, filterProperties, "${", "}" );
392                 }
393             },
394             // support @token@
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 }