View Javadoc

1   package org.apache.maven.plugin.ear;
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.archiver.MavenArchiveConfiguration;
23  import org.apache.maven.archiver.MavenArchiver;
24  import org.apache.maven.execution.MavenSession;
25  import org.apache.maven.plugin.MojoExecutionException;
26  import org.apache.maven.plugin.MojoFailureException;
27  import org.apache.maven.plugin.ear.util.EarMavenArchiver;
28  import org.apache.maven.project.MavenProjectHelper;
29  import org.apache.maven.shared.filtering.MavenFileFilter;
30  import org.apache.maven.shared.filtering.MavenFilteringException;
31  import org.apache.maven.shared.filtering.MavenResourcesExecution;
32  import org.apache.maven.shared.filtering.MavenResourcesFiltering;
33  import org.codehaus.plexus.archiver.ArchiverException;
34  import org.codehaus.plexus.archiver.UnArchiver;
35  import org.codehaus.plexus.archiver.jar.JarArchiver;
36  import org.codehaus.plexus.archiver.manager.ArchiverManager;
37  import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
38  import org.codehaus.plexus.util.DirectoryScanner;
39  import org.codehaus.plexus.util.FileUtils;
40  import org.codehaus.plexus.util.StringUtils;
41  
42  import java.io.File;
43  import java.io.IOException;
44  import java.util.ArrayList;
45  import java.util.Arrays;
46  import java.util.Iterator;
47  import java.util.List;
48  
49  /**
50   * Builds J2EE Enteprise Archive (EAR) files.
51   *
52   * @author <a href="snicoll@apache.org">Stephane Nicoll</a>
53   * @version $Id: EarMojo.java 942855 2010-05-10 19:13:52Z krosenvold $
54   * @goal ear
55   * @phase package
56   * @threadSafe
57   * @requiresDependencyResolution test
58   */
59  public class EarMojo
60      extends AbstractEarMojo
61  {
62      private static final String[] EMPTY_STRING_ARRAY = {};
63  
64  
65      /**
66       * Single directory for extra files to include in the EAR.
67       *
68       * @parameter expression="${basedir}/src/main/application"
69       * @required
70       */
71      private File earSourceDirectory;
72  
73      /**
74       * The comma separated list of tokens to include in the EAR.
75       *
76       * @parameter alias="includes" default-value="**"
77       */
78      private String earSourceIncludes;
79  
80      /**
81       * The comma separated list of tokens to exclude from the EAR.
82       *
83       * @parameter alias="excludes"
84       */
85      private String earSourceExcludes;
86  
87      /**
88       * Specify that the ear sources should be filtered.
89       *
90       * @parameter default-value="false"
91       * @since 2.3.2
92       */
93      private boolean filtering;
94  
95      /**
96       * Filters (property files) to include during the interpolation of the pom.xml.
97       *
98       * @parameter
99       * @since 2.3.2
100      */
101     private List filters;
102 
103     /**
104      * A list of file extensions that should not be filtered if
105      * filtering is actived.
106      *
107      * @parameter
108      * @since 2.3.2
109      */
110     private List nonFilteredFileExtensions;
111 
112     /**
113      * To escape interpolated value with windows path
114      * c:\foo\bar will be replaced with c:\\foo\\bar
115      *
116      * @parameter expression="${maven.ear.escapedBackslashesInFilePath}" default-value="false"
117      * @since 2.3.2
118      */
119     private boolean escapedBackslashesInFilePath;
120 
121     /**
122      * Expression preceded with the String won't be interpolated
123      * \${foo} will be replaced with ${foo}
124      *
125      * @parameter expression="${maven.ear.escapeString}"
126      * @since 2.3.2
127      */
128     protected String escapeString;
129 
130     /**
131      * The location of the manifest file to be used within the ear file. If
132      * not value if specified, the default location in the workDirectory is
133      * taken. If the file does not exist, a manifest will be generated
134      * automatically.
135      *
136      * @parameter
137      */
138     private File manifestFile;
139 
140     /**
141      * The location of a custom application.xml file to be used
142      * within the ear file.
143      *
144      * @parameter
145      */
146     private String applicationXml;
147 
148     /**
149      * The directory for the generated EAR.
150      *
151      * @parameter expression="${project.build.directory}"
152      * @required
153      */
154     private String outputDirectory;
155 
156     /**
157      * The name of the EAR file to generate.
158      *
159      * @parameter alias="earName" expression="${project.build.finalName}"
160      * @required
161      */
162     private String finalName;
163 
164     /**
165      * The comma separated list of artifact's type(s) to unpack
166      * by default.
167      *
168      * @parameter
169      */
170     private String unpackTypes;
171 
172     /**
173      * Classifier to add to the artifact generated. If given, the artifact will
174      * be an attachment instead.
175      *
176      * @parameter
177      */
178     private String classifier;
179 
180     /**
181      * The directory to get the resources from.
182      *
183      * @parameter
184      * @deprecated please use earSourcesDirectory instead
185      */
186     private File resourcesDir;
187 
188     /**
189      * The Jar archiver.
190      *
191      * @component role="org.codehaus.plexus.archiver.Archiver" role-hint="jar"
192      */
193     private JarArchiver jarArchiver;
194 
195     /**
196      * The archive configuration to use.
197      * See <a href="http://maven.apache.org/shared/maven-archiver/index.html">Maven Archiver Reference</a>.
198      *
199      * @parameter
200      */
201     private MavenArchiveConfiguration archive = new MavenArchiveConfiguration();
202 
203     /**
204      * @component
205      */
206     private MavenProjectHelper projectHelper;
207 
208     /**
209      * The archive manager.
210      *
211      * @component
212      */
213     private ArchiverManager archiverManager;
214 
215     /**
216      * @component role="org.apache.maven.shared.filtering.MavenFileFilter" role-hint="default"
217      * @required
218      */
219     private MavenFileFilter mavenFileFilter;
220 
221     /**
222      * @component role="org.apache.maven.shared.filtering.MavenResourcesFiltering" role-hint="default"
223      * @required
224      */
225     private MavenResourcesFiltering mavenResourcesFiltering;
226 
227     /**
228      * @parameter expression="${session}"
229      * @readonly
230      * @required
231      * @since 2.3.2
232      */
233     private MavenSession session;
234 
235 
236     private List filterWrappers;
237 
238 
239     public void execute()
240         throws MojoExecutionException, MojoFailureException
241     {
242         // Initializes ear modules
243         super.execute();
244 
245         // Initializes unpack types
246         List unpackTypesList = new ArrayList();
247         if ( unpackTypes != null )
248         {
249             unpackTypesList = Arrays.asList( unpackTypes.split( "," ) );
250             final Iterator it = unpackTypesList.iterator();
251             while ( it.hasNext() )
252             {
253                 String type = (String) it.next();
254                 if ( !EarModuleFactory.standardArtifactTypes.contains( type ) )
255                 {
256                     throw new MojoExecutionException(
257                         "Invalid type[" + type + "] supported types are " + EarModuleFactory.standardArtifactTypes );
258                 }
259             }
260             getLog().debug( "Initialized unpack types " + unpackTypesList );
261         }
262 
263         // Copy modules
264         try
265         {
266             for ( Iterator iter = getModules().iterator(); iter.hasNext(); )
267             {
268                 EarModule module = (EarModule) iter.next();
269                 if ( module instanceof JavaModule )
270                 {
271                     getLog().warn( "JavaModule is deprecated (" + module + "), please use JarModule instead." );
272                 }
273                 if ( module instanceof Ejb3Module )
274                 {
275                     getLog().warn( "Ejb3Module is deprecated (" + module + "), please use EjbModule instead." );
276                 }
277                 final File sourceFile = module.getArtifact().getFile();
278                 final File destinationFile = buildDestinationFile( getWorkDirectory(), module.getUri() );
279                 if ( !sourceFile.isFile() )
280                 {
281                     throw new MojoExecutionException(
282                         "Cannot copy a directory: " + sourceFile.getAbsolutePath() + "; Did you package/install " +
283                             module.getArtifact() + "?" );
284                 }
285 
286                 if ( destinationFile.getCanonicalPath().equals( sourceFile.getCanonicalPath() ) )
287                 {
288                     getLog().info(
289                         "Skipping artifact[" + module + "], as it already exists at[" + module.getUri() + "]" );
290                     continue;
291                 }
292 
293                 // If the module is within the unpack list, make sure that no unpack wasn't forced (null or true)
294                 // If the module is not in the unpack list, it should be true
295                 if ( ( unpackTypesList.contains( module.getType() ) &&
296                     ( module.shouldUnpack() == null || module.shouldUnpack().booleanValue() ) ) ||
297                     ( module.shouldUnpack() != null && module.shouldUnpack().booleanValue() ) )
298                 {
299                     getLog().info( "Copying artifact[" + module + "] to[" + module.getUri() + "] (unpacked)" );
300                     // Make sure that the destination is a directory to avoid plexus nasty stuff :)
301                     destinationFile.mkdirs();
302                     unpack( sourceFile, destinationFile );
303                 }
304                 else
305                 {
306                     if ( sourceFile.lastModified() > destinationFile.lastModified() )
307                     {
308                         getLog().info( "Copying artifact[" + module + "] to[" + module.getUri() + "]" );
309                         FileUtils.copyFile( sourceFile, destinationFile );
310                     }
311                     else
312                     {
313                         getLog().debug(
314                             "Skipping artifact[" + module + "], as it is already up to date at[" + module.getUri() +
315                                 "]" );
316                     }
317                 }
318             }
319         }
320         catch ( IOException e )
321         {
322             throw new MojoExecutionException( "Error copying EAR modules", e );
323         }
324         catch ( ArchiverException e )
325         {
326             throw new MojoExecutionException( "Error unpacking EAR modules", e );
327         }
328         catch ( NoSuchArchiverException e )
329         {
330             throw new MojoExecutionException( "No Archiver found for EAR modules", e );
331         }
332 
333         // Copy source files
334         try
335         {
336             File earSourceDir = earSourceDirectory;
337             if ( earSourceDir.exists() )
338             {
339                 getLog().info( "Copy ear sources to " + getWorkDirectory().getAbsolutePath() );
340                 String[] fileNames = getEarFiles( earSourceDir );
341                 for ( int i = 0; i < fileNames.length; i++ )
342                 {
343                     copyFile( new File( earSourceDir, fileNames[i] ), new File( getWorkDirectory(), fileNames[i] ) );
344                 }
345             }
346 
347             if ( applicationXml != null && !"".equals( applicationXml ) )
348             {
349                 //rename to application.xml
350                 getLog().info( "Including custom application.xml[" + applicationXml + "]" );
351                 File metaInfDir = new File( getWorkDirectory(), META_INF );
352                 copyFile( new File( applicationXml ), new File( metaInfDir, "/application.xml" ) );
353             }
354 
355         }
356         catch ( IOException e )
357         {
358             throw new MojoExecutionException( "Error copying EAR sources", e );
359         }
360         catch ( MavenFilteringException e )
361         {
362             throw new MojoExecutionException( "Error filtering EAR sources", e );
363         }
364 
365         // Copy resources files
366         try
367         {
368             if ( resourcesDir != null && resourcesDir.exists() )
369             {
370                 getLog().warn( "resourcesDir is deprecated. Please use the earSourceDirectory property instead." );
371                 getLog().info( "Copy ear resources to " + getWorkDirectory().getAbsolutePath() );
372                 String[] fileNames = getEarFiles( resourcesDir );
373                 for ( int i = 0; i < fileNames.length; i++ )
374                 {
375                     FileUtils.copyFile( new File( resourcesDir, fileNames[i] ),
376                                         new File( getWorkDirectory(), fileNames[i] ) );
377                 }
378             }
379         }
380         catch ( IOException e )
381         {
382             throw new MojoExecutionException( "Error copying EAR resources", e );
383         }
384 
385         // Check if deployment descriptor is there
386         File ddFile = new File( getWorkDirectory(), APPLICATION_XML_URI );
387         if ( !ddFile.exists() && ( !( version.equals( VERSION_5 ) || version.equals( VERSION_6 ) ) ) )
388         {
389             throw new MojoExecutionException(
390                 "Deployment descriptor: " + ddFile.getAbsolutePath() + " does not exist." );
391         }
392 
393         try
394         {
395             File earFile = getEarFile( outputDirectory, finalName, classifier );
396             final MavenArchiver archiver = new EarMavenArchiver( getModules() );
397             final JarArchiver jarArchiver = getJarArchiver();
398             getLog().debug( "Jar archiver implementation[" + jarArchiver.getClass().getName() + "]" );
399             archiver.setArchiver( jarArchiver );
400             archiver.setOutputFile( earFile );
401 
402             // Include custom manifest if necessary
403             includeCustomManifestFile();
404 
405             archiver.getArchiver().addDirectory( getWorkDirectory() );
406             archiver.createArchive( getProject(), archive );
407 
408             if ( classifier != null )
409             {
410                 projectHelper.attachArtifact( getProject(), "ear", classifier, earFile );
411             }
412             else
413             {
414                 getProject().getArtifact().setFile( earFile );
415             }
416         }
417         catch ( Exception e )
418         {
419             throw new MojoExecutionException( "Error assembling EAR", e );
420         }
421     }
422 
423     public String getApplicationXml()
424     {
425         return applicationXml;
426     }
427 
428     public void setApplicationXml( String applicationXml )
429     {
430         this.applicationXml = applicationXml;
431     }
432 
433     /**
434      * Returns a string array of the excludes to be used
435      * when assembling/copying the ear.
436      *
437      * @return an array of tokens to exclude
438      */
439     protected String[] getExcludes()
440     {
441         List excludeList = new ArrayList( FileUtils.getDefaultExcludesAsList() );
442         if ( earSourceExcludes != null && !"".equals( earSourceExcludes ) )
443         {
444             excludeList.addAll( Arrays.asList( StringUtils.split( earSourceExcludes, "," ) ) );
445         }
446 
447         // if applicationXml is specified, omit the one in the source directory
448         if ( getApplicationXml() != null && !"".equals( getApplicationXml() ) )
449         {
450             excludeList.add( "**/" + META_INF + "/application.xml" );
451         }
452 
453         return (String[]) excludeList.toArray( EMPTY_STRING_ARRAY );
454     }
455 
456     /**
457      * Returns a string array of the includes to be used
458      * when assembling/copying the ear.
459      *
460      * @return an array of tokens to include
461      */
462     protected String[] getIncludes()
463     {
464         return StringUtils.split( StringUtils.defaultString( earSourceIncludes ), "," );
465     }
466 
467     private static File buildDestinationFile( File buildDir, String uri )
468     {
469         return new File( buildDir, uri );
470     }
471 
472     private void includeCustomManifestFile()
473     {
474         if ( manifestFile == null )
475         {
476             manifestFile = new File( getWorkDirectory(), "META-INF/MANIFEST.MF" );
477         }
478 
479         if ( !manifestFile.exists() )
480         {
481             getLog().info( "Could not find manifest file: " + manifestFile + " - Generating one" );
482         }
483         else
484         {
485             getLog().info( "Including custom manifest file[" + manifestFile + "]" );
486             archive.setManifestFile( manifestFile );
487         }
488     }
489 
490     /**
491      * Returns the EAR file to generate, based on an optional classifier.
492      *
493      * @param basedir    the output directory
494      * @param finalName  the name of the ear file
495      * @param classifier an optional classifier
496      * @return the EAR file to generate
497      */
498     private static File getEarFile( String basedir, String finalName, String classifier )
499     {
500         if ( classifier == null )
501         {
502             classifier = "";
503         }
504         else if ( classifier.trim().length() > 0 && !classifier.startsWith( "-" ) )
505         {
506             classifier = "-" + classifier;
507         }
508 
509         return new File( basedir, finalName + classifier + ".ear" );
510     }
511 
512     /**
513      * Returns a list of filenames that should be copied
514      * over to the destination directory.
515      *
516      * @param sourceDir the directory to be scanned
517      * @return the array of filenames, relative to the sourceDir
518      */
519     private String[] getEarFiles( File sourceDir )
520     {
521         DirectoryScanner scanner = new DirectoryScanner();
522         scanner.setBasedir( sourceDir );
523         scanner.setExcludes( getExcludes() );
524         scanner.addDefaultExcludes();
525 
526         scanner.setIncludes( getIncludes() );
527 
528         scanner.scan();
529 
530         return scanner.getIncludedFiles();
531     }
532 
533     /**
534      * Unpacks the module into the EAR structure.
535      *
536      * @param source  File to be unpacked.
537      * @param destDir Location where to put the unpacked files.
538      */
539     public void unpack( File source, File destDir )
540         throws NoSuchArchiverException, IOException, ArchiverException
541     {
542         UnArchiver unArchiver = archiverManager.getUnArchiver( "zip" );
543         unArchiver.setSourceFile( source );
544         unArchiver.setDestDirectory( destDir );
545 
546         // Extract the module
547         unArchiver.extract();
548     }
549 
550     /**
551      * Returns the {@link JarArchiver} implementation used
552      * to package the EAR file.
553      * <p/>
554      * By default the archiver is obtained from the Plexus container.
555      *
556      * @return the archiver
557      */
558     protected JarArchiver getJarArchiver()
559     {
560         return jarArchiver;
561     }
562 
563     private void copyFile( File source, File target )
564         throws MavenFilteringException, IOException, MojoExecutionException
565     {
566         if ( filtering && !isNonFilteredExtension( source.getName() ) )
567         {
568             // Silly that we have to do this ourselves
569             if ( target.getParentFile() != null && !target.getParentFile().exists() )
570             {
571                 target.getParentFile().mkdirs();
572             }
573             mavenFileFilter.copyFile( source, target, true, getFilterWrappers(), null );
574         }
575         else
576         {
577             FileUtils.copyFile( source, target );
578         }
579     }
580 
581     public boolean isNonFilteredExtension( String fileName )
582     {
583         return !mavenResourcesFiltering.filteredFileExtension( fileName, nonFilteredFileExtensions );
584     }
585 
586     private List getFilterWrappers()
587         throws MojoExecutionException
588     {
589         if ( filterWrappers == null )
590         {
591             try
592             {
593                 MavenResourcesExecution mavenResourcesExecution = new MavenResourcesExecution();
594                 mavenResourcesExecution.setEscapeString( escapeString );
595                 filterWrappers =
596                     mavenFileFilter.getDefaultFilterWrappers( project, filters, escapedBackslashesInFilePath,
597                                                               this.session, mavenResourcesExecution );
598             }
599             catch ( MavenFilteringException e )
600             {
601                 getLog().error( "fail to build filering wrappers " + e.getMessage() );
602                 throw new MojoExecutionException( e.getMessage(), e );
603             }
604         }
605         return filterWrappers;
606     }
607 }