View Javadoc
1   package org.apache.maven.plugins.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 java.io.File;
23  import java.io.FileInputStream;
24  import java.io.FileOutputStream;
25  import java.io.IOException;
26  import java.io.OutputStreamWriter;
27  import java.nio.charset.StandardCharsets;
28  import java.nio.file.FileVisitResult;
29  import java.nio.file.Files;
30  import java.nio.file.Path;
31  import java.nio.file.Paths;
32  import java.nio.file.SimpleFileVisitor;
33  import java.nio.file.attribute.BasicFileAttributes;
34  import java.util.ArrayList;
35  import java.util.Arrays;
36  import java.util.Collection;
37  import java.util.Date;
38  import java.util.List;
39  import java.util.Objects;
40  
41  import org.apache.maven.archiver.MavenArchiveConfiguration;
42  import org.apache.maven.archiver.MavenArchiver;
43  import org.apache.maven.artifact.DependencyResolutionRequiredException;
44  import org.apache.maven.execution.MavenSession;
45  import org.apache.maven.plugin.MojoExecutionException;
46  import org.apache.maven.plugin.MojoFailureException;
47  import org.apache.maven.plugins.annotations.Component;
48  import org.apache.maven.plugins.annotations.LifecyclePhase;
49  import org.apache.maven.plugins.annotations.Mojo;
50  import org.apache.maven.plugins.annotations.Parameter;
51  import org.apache.maven.plugins.annotations.ResolutionScope;
52  import org.apache.maven.plugins.ear.util.EarMavenArchiver;
53  import org.apache.maven.plugins.ear.util.JavaEEVersion;
54  import org.apache.maven.project.MavenProjectHelper;
55  import org.apache.maven.shared.filtering.MavenFileFilter;
56  import org.apache.maven.shared.filtering.MavenFilteringException;
57  import org.apache.maven.shared.filtering.MavenResourcesExecution;
58  import org.apache.maven.shared.filtering.MavenResourcesFiltering;
59  import org.apache.maven.shared.utils.io.FileUtils;
60  import org.codehaus.plexus.archiver.Archiver;
61  import org.codehaus.plexus.archiver.ArchiverException;
62  import org.codehaus.plexus.archiver.UnArchiver;
63  import org.codehaus.plexus.archiver.ear.EarArchiver;
64  import org.codehaus.plexus.archiver.jar.JarArchiver;
65  import org.codehaus.plexus.archiver.jar.Manifest;
66  import org.codehaus.plexus.archiver.jar.Manifest.Attribute;
67  import org.codehaus.plexus.archiver.jar.ManifestException;
68  import org.codehaus.plexus.archiver.manager.ArchiverManager;
69  import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
70  import org.codehaus.plexus.archiver.zip.ZipArchiver;
71  import org.codehaus.plexus.archiver.zip.ZipUnArchiver;
72  import org.codehaus.plexus.components.io.filemappers.FileMapper;
73  import org.codehaus.plexus.util.DirectoryScanner;
74  import org.codehaus.plexus.util.StringUtils;
75  
76  /**
77   * Builds J2EE Enterprise Archive (EAR) files.
78   * 
79   * @author <a href="snicoll@apache.org">Stephane Nicoll</a>
80   */
81  // CHECKSTYLE_OFF: LineLength
82  @Mojo( name = "ear", defaultPhase = LifecyclePhase.PACKAGE, threadSafe = true, requiresDependencyResolution = ResolutionScope.TEST )
83  // CHECKSTYLE_ON: LineLength
84  public class EarMojo
85      extends AbstractEarMojo
86  {
87      /**
88       * Single directory for extra files to include in the EAR.
89       */
90      @Parameter( defaultValue = "${basedir}/src/main/application", required = true )
91      private File earSourceDirectory;
92  
93      /**
94       * The comma separated list of tokens to include in the EAR.
95       */
96      @Parameter( alias = "includes", defaultValue = "**" )
97      private String earSourceIncludes;
98  
99      /**
100      * The comma separated list of tokens to exclude from the EAR.
101      */
102     @Parameter( alias = "excludes" )
103     private String earSourceExcludes;
104 
105     /**
106      * Specify that the EAR sources should be filtered.
107      * 
108      * @since 2.3.2
109      */
110     @Parameter( defaultValue = "false" )
111     private boolean filtering;
112 
113     /**
114      * Filters (property files) to include during the interpolation of the pom.xml.
115      * 
116      * @since 2.3.2
117      */
118     @Parameter
119     private List<String> filters;
120 
121     /**
122      * A list of file extensions that should not be filtered if filtering is enabled.
123      * 
124      * @since 2.3.2
125      */
126     @Parameter
127     private List<String> nonFilteredFileExtensions;
128 
129     /**
130      * To escape interpolated value with Windows path c:\foo\bar will be replaced with c:\\foo\\bar.
131      * 
132      * @since 2.3.2
133      */
134     @Parameter( defaultValue = "false" )
135     private boolean escapedBackslashesInFilePath;
136 
137     /**
138      * Expression preceded with this String won't be interpolated \${foo} will be replaced with ${foo}.
139      * 
140      * @since 2.3.2
141      */
142     @Parameter
143     protected String escapeString;
144 
145     /**
146      * In case of using the {@link #skinnyWars} and {@link #defaultLibBundleDir} usually the classpath will be modified.
147      * By settings this option {@code true} you can change this and keep the classpath untouched. This option has been
148      * introduced to keep the backward compatibility with earlier versions of the plugin.
149      * 
150      * @since 2.10
151      */
152     @Parameter( defaultValue = "false" )
153     private boolean skipClassPathModification;
154 
155     /**
156      * The location of a custom application.xml file to be used within the EAR file.
157      */
158     @Parameter
159     private String applicationXml;
160 
161     /**
162      * The directory for the generated EAR.
163      */
164     @Parameter( defaultValue = "${project.build.directory}", required = true )
165     private String outputDirectory;
166 
167     /**
168      * The name of the EAR file to generate.
169      */
170     @Parameter( defaultValue = "${project.build.finalName}", required = true, readonly = true )
171     private String finalName;
172 
173     /**
174      * The comma separated list of artifact's type(s) to unpack by default.
175      */
176     @Parameter
177     private String unpackTypes;
178 
179     /**
180      * Classifier to add to the artifact generated. If given, the artifact will be an attachment instead.
181      */
182     @Parameter
183     private String classifier;
184 
185     /**
186      * A comma separated list of tokens to exclude when packaging the EAR. By default nothing is excluded. Note that you
187      * can use the Java Regular Expressions engine to include and exclude specific pattern using the expression
188      * %regex[]. Hint: read the about (?!Pattern).
189      * 
190      * @since 2.7
191      */
192     @Parameter
193     private String packagingExcludes;
194 
195     /**
196      * A comma separated list of tokens to include when packaging the EAR. By default everything is included. Note that
197      * you can use the Java Regular Expressions engine to include and exclude specific pattern using the expression
198      * %regex[].
199      * 
200      * @since 2.7
201      */
202     @Parameter
203     private String packagingIncludes;
204 
205     /**
206      * Whether to create skinny WARs or not. A skinny WAR is a WAR that does not have all of its dependencies in
207      * WEB-INF/lib. Instead those dependencies are shared between the WARs through the EAR.
208      * 
209      * @since 2.7
210      */
211     @Parameter( defaultValue = "false" )
212     private boolean skinnyWars;
213 
214     /**
215      * The Plexus EAR archiver to create the output archive.
216      */
217     @Component( role = Archiver.class, hint = "ear" )
218     private EarArchiver earArchiver;
219 
220     /**
221      * The Plexus JAR archiver to create the output archive if not EAR application descriptor is provided (JavaEE 5+).
222      */
223     @Component( role = Archiver.class, hint = "jar" )
224     private JarArchiver jarArchiver;
225 
226     /**
227      * The Plexus Zip archiver for Skinny WAR repackaging.
228      */
229     @Component( role = Archiver.class, hint = "zip" )
230     private ZipArchiver zipArchiver;
231 
232     /**
233      * The Plexus Zip Un archiver for Skinny WAR repackaging.
234      */
235     @Component( role = UnArchiver.class, hint = "zip" )
236     private ZipUnArchiver zipUnArchiver;
237 
238     /**
239      * The archive configuration to use. See <a href="https://maven.apache.org/shared/maven-archiver/">Maven Archiver
240      * Reference</a>.
241      */
242     @Parameter
243     private MavenArchiveConfiguration archive = new MavenArchiveConfiguration();
244 
245     /**
246      * Timestamp for reproducible output archive entries, either formatted as ISO 8601
247      * <code>yyyy-MM-dd'T'HH:mm:ssXXX</code> or as an int representing seconds since the epoch (like
248      * <a href="https://reproducible-builds.org/docs/source-date-epoch/">SOURCE_DATE_EPOCH</a>).
249      *
250      * @since 3.1.0
251      */
252     @Parameter( defaultValue = "${project.build.outputTimestamp}" )
253     private String outputTimestamp;
254 
255     /**
256      */
257     @Component
258     private MavenProjectHelper projectHelper;
259 
260     /**
261      * The archive manager.
262      */
263     @Component
264     private ArchiverManager archiverManager;
265 
266     /**
267      */
268     @Component( role = MavenFileFilter.class, hint = "default" )
269     private MavenFileFilter mavenFileFilter;
270 
271     /**
272      */
273     @Component( role = MavenResourcesFiltering.class, hint = "default" )
274     private MavenResourcesFiltering mavenResourcesFiltering;
275 
276     /**
277      * @since 2.3.2
278      */
279     @Parameter( defaultValue = "${session}", readonly = true, required = true )
280     private MavenSession session;
281 
282     private List<FileUtils.FilterWrapper> filterWrappers;
283 
284     /**
285      * @since 2.9
286      */
287     @Parameter( defaultValue = "true" )
288     private boolean useJvmChmod = true;
289 
290     /** {@inheritDoc} */
291     public void execute()
292         throws MojoExecutionException, MojoFailureException
293     {
294         // Initializes ear modules
295         super.execute();
296 
297         File earFile = getEarFile( outputDirectory, finalName, classifier );
298         MavenArchiver archiver = new EarMavenArchiver( getModules() );
299         File ddFile = new File( getWorkDirectory(), APPLICATION_XML_URI );
300 
301         JarArchiver theArchiver;
302         if ( ddFile.exists() )
303         {
304             earArchiver.setAppxml( ddFile );
305             theArchiver = earArchiver;
306         }
307         else
308         {
309             // current Plexus EarArchiver does not support application.xml-less JavaEE 5+ case
310             // => fallback to Plexus Jar archiver 
311             theArchiver = jarArchiver;
312         }
313         getLog().debug( "Ear archiver implementation [" + theArchiver.getClass().getName() + "]" );
314         archiver.setArchiver( theArchiver );
315         archiver.setOutputFile( earFile );
316         archiver.setCreatedBy( "Maven EAR Plugin", "org.apache.maven.plugins", "maven-ear-plugin" );
317 
318         // configure for Reproducible Builds based on outputTimestamp value
319         Date reproducibleLastModifiedDate = archiver.configureReproducible( outputTimestamp );
320 
321         zipArchiver.setUseJvmChmod( useJvmChmod );
322         if ( reproducibleLastModifiedDate != null )
323         {
324             zipArchiver.configureReproducible( reproducibleLastModifiedDate );
325         }
326         zipUnArchiver.setUseJvmChmod( useJvmChmod );
327 
328         final JavaEEVersion javaEEVersion = JavaEEVersion.getJavaEEVersion( version );
329 
330         final Collection<String> outdatedResources = initOutdatedResources();
331 
332         // Initializes unpack types
333         List<String> unpackTypesList = createUnpackList();
334 
335         // Copy modules
336         copyModules( javaEEVersion, unpackTypesList, outdatedResources );
337 
338         // Copy source files
339         try
340         {
341             File earSourceDir = earSourceDirectory;
342 
343             if ( earSourceDir.exists() )
344             {
345                 getLog().info( "Copy ear sources to " + getWorkDirectory().getAbsolutePath() );
346                 String[] fileNames = getEarFiles( earSourceDir );
347                 for ( String fileName : fileNames )
348                 {
349                     copyFile( new File( earSourceDir, fileName ), new File( getWorkDirectory(), fileName ) );
350                     outdatedResources.remove( Paths.get( fileName ).toString() );
351                 }
352             }
353 
354             if ( applicationXml != null && !"".equals( applicationXml ) )
355             {
356                 // rename to application.xml
357                 getLog().info( "Including custom application.xml[" + applicationXml + "]" );
358                 File metaInfDir = new File( getWorkDirectory(), META_INF );
359                 copyFile( new File( applicationXml ), new File( metaInfDir, "/application.xml" ) );
360                 outdatedResources.remove( Paths.get( "META-INF/application.xml" ).toString() );
361             }
362         }
363         catch ( IOException e )
364         {
365             throw new MojoExecutionException( "Error copying EAR sources", e );
366         }
367         catch ( MavenFilteringException e )
368         {
369             throw new MojoExecutionException( "Error filtering EAR sources", e );
370         }
371 
372         // Check if deployment descriptor is there
373         if ( !ddFile.exists() && ( javaEEVersion.lt( JavaEEVersion.FIVE ) ) )
374         {
375             throw new MojoExecutionException( "Deployment descriptor: " + ddFile.getAbsolutePath()
376                 + " does not exist." );
377         }
378         // no need to check timestamp for descriptors: removing if outdated does not really make sense
379         outdatedResources.remove( Paths.get( APPLICATION_XML_URI ).toString() );
380         if ( getJbossConfiguration() != null )
381         {
382             outdatedResources.remove( Paths.get( "META-INF/jboss-app.xml" ).toString() );
383         }
384 
385         deleteOutdatedResources( outdatedResources );
386 
387         try
388         {
389             getLog().debug( "Excluding " + Arrays.asList( getPackagingExcludes() ) + " from the generated EAR." );
390             getLog().debug( "Including " + Arrays.asList( getPackagingIncludes() ) + " in the generated EAR." );
391 
392             archiver.getArchiver().addDirectory( getWorkDirectory(), getPackagingIncludes(), getPackagingExcludes() );
393 
394             archiver.createArchive( session, getProject(), archive );
395         }
396         catch ( ManifestException | IOException | DependencyResolutionRequiredException e )
397         {
398             throw new MojoExecutionException( "Error assembling EAR", e );
399         }
400 
401         if ( classifier != null )
402         {
403             projectHelper.attachArtifact( getProject(), "ear", classifier, earFile );
404         }
405         else
406         {
407             getProject().getArtifact().setFile( earFile );
408         }
409     }
410 
411     private void copyModules( final JavaEEVersion javaEEVersion, 
412                               List<String> unpackTypesList, 
413                               Collection<String> outdatedResources )
414         throws MojoExecutionException, MojoFailureException
415     {
416         final Path workingDir = getWorkDirectory().toPath();
417 
418         try
419         {
420             for ( EarModule module : getModules() )
421             {
422                 final File sourceFile = module.getArtifact().getFile();
423                 final File destinationFile = buildDestinationFile( getWorkDirectory(), module.getUri() );
424                 if ( !sourceFile.isFile() )
425                 {
426                     throw new MojoExecutionException( "Cannot copy a directory: " + sourceFile.getAbsolutePath()
427                         + "; Did you package/install " + module.getArtifact() + "?" );
428                 }
429 
430                 if ( destinationFile.getCanonicalPath().equals( sourceFile.getCanonicalPath() ) )
431                 {
432                     getLog().info( "Skipping artifact [" + module + "], as it already exists at [" + module.getUri()
433                         + "]" );
434                     // FIXME: Shouldn't that result in a build failure!?
435                     continue;
436                 }
437 
438                 // If the module is within the unpack list, make sure that no unpack wasn't forced (null or true)
439                 // If the module is not in the unpack list, it should be true
440                 if ( ( unpackTypesList.contains( module.getType() )
441                     && ( module.shouldUnpack() == null || module.shouldUnpack() ) )
442                     || ( module.shouldUnpack() != null && module.shouldUnpack() ) )
443                 {
444                     getLog().info( "Copying artifact [" + module + "] to [" + module.getUri() + "] (unpacked)" );
445                     // Make sure that the destination is a directory to avoid plexus nasty stuff :)
446                     if ( !destinationFile.mkdirs() )
447                     {
448                         throw new MojoExecutionException( "Error creating " + destinationFile );
449                     }
450                     unpack( sourceFile, destinationFile, outdatedResources );
451 
452                     if ( skinnyWars && module.changeManifestClasspath() )
453                     {
454                         changeManifestClasspath( module, destinationFile, javaEEVersion );
455                     }
456                 }
457                 else
458                 {
459                     if ( sourceFile.lastModified() > destinationFile.lastModified() )
460                     {
461                         getLog().info( "Copying artifact [" + module + "] to [" + module.getUri() + "]" );
462                         FileUtils.copyFile( sourceFile, destinationFile );
463 
464                         if ( skinnyWars && module.changeManifestClasspath() )
465                         {
466                             changeManifestClasspath( module, destinationFile, javaEEVersion );
467                         }
468                     }
469                     else
470                     {
471                         getLog().debug( "Skipping artifact [" + module + "], as it is already up to date at ["
472                             + module.getUri() + "]" );
473                     }
474                     outdatedResources.remove( workingDir.relativize( destinationFile.toPath() ).toString() );
475                 }
476             }
477         }
478         catch ( IOException e )
479         {
480             throw new MojoExecutionException( "Error copying EAR modules", e );
481         }
482         catch ( ArchiverException e )
483         {
484             throw new MojoExecutionException( "Error unpacking EAR modules", e );
485         }
486         catch ( NoSuchArchiverException e )
487         {
488             throw new MojoExecutionException( "No Archiver found for EAR modules", e );
489         }
490     }
491 
492     private List<String> createUnpackList()
493         throws MojoExecutionException
494     {
495         List<String> unpackTypesList = new ArrayList<String>();
496         if ( unpackTypes != null )
497         {
498             unpackTypesList = Arrays.asList( unpackTypes.split( "," ) );
499             for ( String type : unpackTypesList )
500             {
501                 if ( !EarModuleFactory.STANDARD_ARTIFACT_TYPE.contains( type ) )
502                 {
503                     throw new MojoExecutionException( "Invalid type [" + type + "] supported types are "
504                         + EarModuleFactory.STANDARD_ARTIFACT_TYPE );
505                 }
506             }
507             getLog().debug( "Initialized unpack types " + unpackTypesList );
508         }
509         return unpackTypesList;
510     }
511 
512     /**
513      * @return {@link #applicationXml}
514      */
515     public String getApplicationXml()
516     {
517         return applicationXml;
518     }
519 
520     /**
521      * @param applicationXml {@link #applicationXml}
522      */
523     public void setApplicationXml( String applicationXml )
524     {
525         this.applicationXml = applicationXml;
526     }
527 
528     /**
529      * Returns a string array of the excludes to be used when assembling/copying the ear.
530      * 
531      * @return an array of tokens to exclude
532      */
533     protected String[] getExcludes()
534     {
535         List<String> excludeList = new ArrayList<String>( FileUtils.getDefaultExcludesAsList() );
536         if ( earSourceExcludes != null && !"".equals( earSourceExcludes ) )
537         {
538             excludeList.addAll( Arrays.asList( StringUtils.split( earSourceExcludes, "," ) ) );
539         }
540 
541         // if applicationXml is specified, omit the one in the source directory
542         if ( getApplicationXml() != null && !"".equals( getApplicationXml() ) )
543         {
544             excludeList.add( "**/" + META_INF + "/application.xml" );
545         }
546 
547         return excludeList.toArray( new String[excludeList.size()] );
548     }
549 
550     /**
551      * Returns a string array of the includes to be used when assembling/copying the ear.
552      * 
553      * @return an array of tokens to include
554      */
555     protected String[] getIncludes()
556     {
557         return StringUtils.split( Objects.toString( earSourceIncludes, "" ), "," );
558     }
559 
560     /**
561      * @return The array with the packaging excludes.
562      */
563     public String[] getPackagingExcludes()
564     {
565         if ( StringUtils.isEmpty( packagingExcludes ) )
566         {
567             return new String[0];
568         }
569         else
570         {
571             return StringUtils.split( packagingExcludes, "," );
572         }
573     }
574 
575     /**
576      * @param packagingExcludes {@link #packagingExcludes}
577      */
578     public void setPackagingExcludes( String packagingExcludes )
579     {
580         this.packagingExcludes = packagingExcludes;
581     }
582 
583     /**
584      * @return the arrays with the includes
585      */
586     public String[] getPackagingIncludes()
587     {
588         if ( StringUtils.isEmpty( packagingIncludes ) )
589         {
590             return new String[] { "**" };
591         }
592         else
593         {
594             return StringUtils.split( packagingIncludes, "," );
595         }
596     }
597 
598     /**
599      * @param packagingIncludes {@link #packagingIncludes}
600      */
601     public void setPackagingIncludes( String packagingIncludes )
602     {
603         this.packagingIncludes = packagingIncludes;
604     }
605 
606     private static File buildDestinationFile( File buildDir, String uri )
607     {
608         return new File( buildDir, uri );
609     }
610 
611     /**
612      * Returns the EAR file to generate, based on an optional classifier.
613      * 
614      * @param basedir the output directory
615      * @param finalName the name of the ear file
616      * @param classifier an optional classifier
617      * @return the EAR file to generate
618      */
619     private static File getEarFile( String basedir, String finalName, String classifier )
620     {
621         if ( classifier == null )
622         {
623             classifier = "";
624         }
625         else if ( classifier.trim().length() > 0 && !classifier.startsWith( "-" ) )
626         {
627             classifier = "-" + classifier;
628         }
629 
630         return new File( basedir, finalName + classifier + ".ear" );
631     }
632 
633     /**
634      * Returns a list of filenames that should be copied over to the destination directory.
635      * 
636      * @param sourceDir the directory to be scanned
637      * @return the array of filenames, relative to the sourceDir
638      */
639     private String[] getEarFiles( File sourceDir )
640     {
641         DirectoryScanner scanner = new DirectoryScanner();
642         scanner.setBasedir( sourceDir );
643         scanner.setExcludes( getExcludes() );
644         scanner.addDefaultExcludes();
645 
646         scanner.setIncludes( getIncludes() );
647 
648         scanner.scan();
649 
650         return scanner.getIncludedFiles();
651     }
652 
653     /**
654      * Unpacks the module into the EAR structure.
655      * 
656      * @param source file to be unpacked
657      * @param destDir where to put the unpacked files
658      * @param outdatedResources currently outdated resources
659      * @throws ArchiverException a corrupt archive
660      * @throws NoSuchArchiverException if we don't have an appropriate archiver
661      * @throws IOException in case of a general IOException
662      */
663     public void unpack( File source, final File destDir, final Collection<String> outdatedResources )
664         throws ArchiverException, NoSuchArchiverException, IOException
665     {
666         UnArchiver unArchiver = archiverManager.getUnArchiver( "zip" );
667         unArchiver.setSourceFile( source );
668         unArchiver.setDestDirectory( destDir );
669         unArchiver.setFileMappers( new FileMapper[] {
670             new FileMapper()
671             {
672                 @Override
673                 public String getMappedFileName( String pName )
674                 {
675                     Path destFile = destDir.toPath().resolve( pName );
676                     outdatedResources.remove( getWorkDirectory().toPath().relativize( destFile ).toString() );
677                     return pName;
678                 }
679             }
680         } );
681 
682         // Extract the module
683         unArchiver.extract();
684     }
685 
686     private void copyFile( File source, File target )
687         throws MavenFilteringException, IOException, MojoExecutionException
688     {
689         if ( filtering && !isNonFilteredExtension( source.getName() ) )
690         {
691             // Silly that we have to do this ourselves
692             File parentDirectory = target.getParentFile();
693             if ( parentDirectory != null && !parentDirectory.exists() )
694             {
695                 Files.createDirectories( parentDirectory.toPath() );
696             }
697 
698             mavenFileFilter.copyFile( source, target, true, getFilterWrappers(), encoding );
699         }
700         else
701         {
702             FileUtils.copyFile( source, target );
703         }
704     }
705 
706     /**
707      * @param fileName the name of the file which should be checked
708      * @return {@code true} if the name is part of the non filtered extensions; {@code false} otherwise
709      */
710     public boolean isNonFilteredExtension( String fileName )
711     {
712         return !mavenResourcesFiltering.filteredFileExtension( fileName, nonFilteredFileExtensions );
713     }
714 
715     private List<FileUtils.FilterWrapper> getFilterWrappers()
716         throws MojoExecutionException
717     {
718         if ( filterWrappers == null )
719         {
720             try
721             {
722                 MavenResourcesExecution mavenResourcesExecution = new MavenResourcesExecution();
723                 mavenResourcesExecution.setMavenProject( getProject() );
724                 mavenResourcesExecution.setEscapedBackslashesInFilePath( escapedBackslashesInFilePath );
725                 mavenResourcesExecution.setFilters( filters );
726                 mavenResourcesExecution.setEscapeString( escapeString );
727 
728                 filterWrappers = mavenFileFilter.getDefaultFilterWrappers( mavenResourcesExecution );
729             }
730             catch ( MavenFilteringException e )
731             {
732                 getLog().error( "Fail to build filtering wrappers " + e.getMessage() );
733                 throw new MojoExecutionException( e.getMessage(), e );
734             }
735         }
736         return filterWrappers;
737     }
738 
739     private void changeManifestClasspath( EarModule module, File original, JavaEEVersion javaEEVersion )
740         throws MojoFailureException
741     {
742         try
743         {
744             File workDirectory;
745 
746             // Handle the case that the destination might be a directory (project-038)
747             if ( original.isFile() )
748             {
749                 // Create a temporary work directory
750                 // MEAR-167 use uri as directory to prevent merging of artifacts with the same artifactId
751                 workDirectory = new File( new File( getTempFolder(), "temp" ), module.getUri() );
752                 if ( workDirectory.mkdirs() )
753                 {
754                     getLog().debug( "Created a temporary work directory: " + workDirectory.getAbsolutePath() );
755 
756                     // Unpack the archive to a temporary work directory
757                     zipUnArchiver.setSourceFile( original );
758                     zipUnArchiver.setDestDirectory( workDirectory );
759                     zipUnArchiver.extract();
760                 }
761                 else
762                 {
763                     throw new MojoFailureException( "Failed to create directory " + workDirectory );
764                 }
765             }
766             else
767             {
768                 workDirectory = original;
769             }
770 
771             // Create a META-INF/MANIFEST.MF file if it doesn't exist (project-038)
772             File metaInfDirectory = new File( workDirectory, "META-INF" );
773             boolean newMetaInfCreated = metaInfDirectory.mkdirs();
774             if ( newMetaInfCreated )
775             {
776                 getLog().debug(
777                     "This project did not have a META-INF directory before, so a new directory was created." );
778             }
779             File manifestFile = new File( metaInfDirectory, "MANIFEST.MF" );
780             boolean newManifestCreated = manifestFile.createNewFile();
781             if ( newManifestCreated )
782             {
783                 getLog().debug(
784                     "This project did not have a META-INF/MANIFEST.MF file before, so a new file was created." );
785             }
786 
787             Manifest mf = readManifest( manifestFile );
788             Attribute classPath = mf.getMainSection().getAttribute( "Class-Path" );
789             List<String> classPathElements = new ArrayList<String>();
790 
791             if ( classPath != null )
792             {
793                 classPathElements.addAll( Arrays.asList( classPath.getValue().split( " " ) ) );
794             }
795             else
796             {
797                 classPath = new Attribute( "Class-Path", "" );
798             }
799 
800             // Remove JAR modules
801             for ( JarModule jm : getAllJarModules() )
802             {
803                 if ( module.getLibDir() != null )
804                 {
805                     // MEAR-189:
806                     // We use the original name, cause in case of outputFileNameMapping
807                     // we could not not delete it and it will end up in the resulting EAR and the WAR
808                     // will not be cleaned up.
809                     File artifact = new File( new File( workDirectory, module.getLibDir() ),
810                                               module.getArtifact().getFile().getName() );
811 
812                     // MEAR-217
813                     // If WAR contains files with timestamps, but EAR strips them away (useBaseVersion=true)
814                     // the artifact is not found. Therefore respect the current fileNameMapping additionally.
815 
816                     if ( !artifact.exists() )
817                     {
818                         getLog().debug( "module does not exist with original file name." );
819                         artifact = new File( new File( workDirectory, module.getLibDir() ), jm.getBundleFileName() );
820                         getLog().debug( "Artifact with mapping:" + artifact.getAbsolutePath() );
821                     }
822 
823                     if ( !artifact.exists() )
824                     {
825                         getLog().debug( "Artifact with mapping does not exist." );
826                         artifact = new File( new File( workDirectory, module.getLibDir() ),
827                                              jm.getArtifact().getFile().getName() );
828                         getLog().debug( "Artifact with orignal file name:" + artifact.getAbsolutePath() );
829                     }
830 
831                     if ( artifact.exists() )
832                     {
833                         getLog().debug( " -> Artifact to delete: " + artifact );
834                         if ( !artifact.delete() )
835                         {
836                             getLog().error( "Could not delete '" + artifact + "'" );
837                         }
838                     }
839                 }
840             }
841 
842             // Modify the classpath entries in the manifest
843             for ( EarModule o : getModules() )
844             {
845                 if ( o instanceof JarModule )
846                 {
847                     JarModule jm = (JarModule) o;
848                     if ( classPathElements.contains( jm.getBundleFileName() ) )
849                     {
850                         classPathElements.set( classPathElements.indexOf( jm.getBundleFileName() ), jm.getUri() );
851                     }
852                     else
853                     {
854                         if ( !skipClassPathModification )
855                         {
856                             classPathElements.add( jm.getUri() );
857                         }
858                         else
859                         {
860                             if ( javaEEVersion.lt( JavaEEVersion.FIVE ) || defaultLibBundleDir == null )
861                             {
862                                 classPathElements.add( jm.getUri() );
863                             }
864                         }
865                     }
866                 }
867             }
868             classPath.setValue( StringUtils.join( classPathElements.iterator(), " " ) );
869             mf.getMainSection().addConfiguredAttribute( classPath );
870 
871             // Write the manifest to disk
872             try ( FileOutputStream out = new FileOutputStream( manifestFile );
873                   OutputStreamWriter writer = new OutputStreamWriter( out, StandardCharsets.UTF_8 ) )
874             {
875                 mf.write( writer );
876             }
877 
878             if ( original.isFile() )
879             {
880                 // Pack up the archive again from the work directory
881                 if ( !original.delete() )
882                 {
883                     getLog().error( "Could not delete original artifact file " + original );
884                 }
885 
886                 getLog().debug( "Zipping module" );
887                 zipArchiver.setDestFile( original );
888                 zipArchiver.addDirectory( workDirectory );
889                 zipArchiver.createArchive();
890             }
891         }
892         catch ( ManifestException | IOException | ArchiverException e )
893         {
894             throw new MojoFailureException( e.getMessage(), e );
895         }
896     }
897 
898     private static Manifest readManifest( File manifestFile )
899         throws IOException
900     {
901         // Read the manifest from disk
902         try ( FileInputStream in = new FileInputStream( manifestFile ) )
903         {
904             Manifest manifest = new Manifest( in );
905             return manifest;
906         }
907     }
908 
909     private Collection<String> initOutdatedResources()
910     {
911         final Collection<String> outdatedResources = new ArrayList<>();
912         
913         if ( getWorkDirectory().exists() )
914         {
915             try
916             {
917                 Files.walkFileTree( getWorkDirectory().toPath(), new SimpleFileVisitor<Path>() 
918                 {
919                     @Override
920                     public FileVisitResult visitFile( Path file, BasicFileAttributes attrs )
921                         throws IOException
922                     {
923                         outdatedResources.add( getWorkDirectory().toPath().relativize( file ).toString() );
924                         return super.visitFile( file, attrs );
925                     }
926                 } );
927             }
928             catch ( IOException e )
929             {
930                 getLog().warn( "Can't detect outdated resources", e );
931             } 
932         }
933         return outdatedResources;
934     }
935 
936     private void deleteOutdatedResources( final Collection<String> outdatedResources )
937     {
938         final long startTime = session.getStartTime().getTime();
939         
940         for ( String outdatedResource : outdatedResources )
941         {
942             if ( new File( getWorkDirectory(), outdatedResource ).lastModified() < startTime )
943             {
944                 getLog().info( "deleting outdated resource " + outdatedResource );
945                 new File( getWorkDirectory(), outdatedResource ).delete();
946             }
947         }
948     }
949 }