blob: 8ce82770976c6b6772f4c20c8bf1ce08b1327f23 [file] [log] [blame]
package org.apache.maven.plugins.ear;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import org.apache.maven.archiver.MavenArchiveConfiguration;
import org.apache.maven.archiver.MavenArchiver;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.plugins.ear.util.EarMavenArchiver;
import org.apache.maven.plugins.ear.util.JavaEEVersion;
import org.apache.maven.project.MavenProjectHelper;
import org.apache.maven.shared.filtering.MavenFileFilter;
import org.apache.maven.shared.filtering.MavenFilteringException;
import org.apache.maven.shared.filtering.MavenResourcesExecution;
import org.apache.maven.shared.filtering.MavenResourcesFiltering;
import org.apache.maven.shared.mapping.MappingUtils;
import org.apache.maven.shared.utils.io.FileUtils;
import org.codehaus.plexus.archiver.Archiver;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.UnArchiver;
import org.codehaus.plexus.archiver.ear.EarArchiver;
import org.codehaus.plexus.archiver.jar.JarArchiver;
import org.codehaus.plexus.archiver.jar.Manifest;
import org.codehaus.plexus.archiver.jar.Manifest.Attribute;
import org.codehaus.plexus.archiver.jar.ManifestException;
import org.codehaus.plexus.archiver.manager.ArchiverManager;
import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
import org.codehaus.plexus.archiver.zip.ZipArchiver;
import org.codehaus.plexus.archiver.zip.ZipUnArchiver;
import org.codehaus.plexus.components.io.filemappers.FileMapper;
import org.codehaus.plexus.interpolation.InterpolationException;
import org.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.StringUtils;
/**
* Builds J2EE Enterprise Archive (EAR) files.
*
* @author <a href="snicoll@apache.org">Stephane Nicoll</a>
*/
@Mojo( name = "ear",
defaultPhase = LifecyclePhase.PACKAGE,
threadSafe = true,
requiresDependencyResolution = ResolutionScope.TEST )
public class EarMojo
extends AbstractEarMojo
{
/**
* Default file name mapping used by artifacts located in local repository.
*/
private static final String ARTIFACT_DEFAULT_FILE_NAME_MAPPING =
"@{artifactId}@-@{version}@@{dashClassifier?}@.@{extension}@";
/**
* Single directory for extra files to include in the EAR.
*/
@Parameter( defaultValue = "${basedir}/src/main/application", required = true )
private File earSourceDirectory;
/**
* The comma separated list of tokens to include in the EAR.
*/
@Parameter( alias = "includes", defaultValue = "**" )
private String earSourceIncludes;
/**
* The comma separated list of tokens to exclude from the EAR.
*/
@Parameter( alias = "excludes" )
private String earSourceExcludes;
/**
* Specify that the EAR sources should be filtered.
*
* @since 2.3.2
*/
@Parameter( defaultValue = "false" )
private boolean filtering;
/**
* Filters (property files) to include during the interpolation of the pom.xml.
*
* @since 2.3.2
*/
@Parameter
private List<String> filters;
/**
* A list of file extensions that should not be filtered if filtering is enabled.
*
* @since 2.3.2
*/
@Parameter
private List<String> nonFilteredFileExtensions;
/**
* To escape interpolated value with Windows path c:\foo\bar will be replaced with c:\\foo\\bar.
*
* @since 2.3.2
*/
@Parameter( defaultValue = "false" )
private boolean escapedBackslashesInFilePath;
/**
* Expression preceded with this String won't be interpolated \${foo} will be replaced with ${foo}.
*
* @since 2.3.2
*/
@Parameter
protected String escapeString;
/**
* In case of using the {@link #skinnyWars} and {@link #defaultLibBundleDir} usually the classpath will be modified.
* By settings this option {@code true} you can change this and keep the classpath untouched. This option has been
* introduced to keep the backward compatibility with earlier versions of the plugin.
*
* @since 2.10
*/
@Parameter( defaultValue = "false" )
private boolean skipClassPathModification;
/**
* The location of a custom application.xml file to be used within the EAR file.
*/
@Parameter
private String applicationXml;
/**
* The directory for the generated EAR.
*/
@Parameter( defaultValue = "${project.build.directory}", required = true )
private String outputDirectory;
/**
* The name of the EAR file to generate.
*/
@Parameter( defaultValue = "${project.build.finalName}", required = true, readonly = true )
private String finalName;
/**
* The comma separated list of artifact's type(s) to unpack by default.
*/
@Parameter
private String unpackTypes;
/**
* Classifier to add to the artifact generated. If given, the artifact will be an attachment instead.
*/
@Parameter
private String classifier;
/**
* A comma separated list of tokens to exclude when packaging the EAR. By default nothing is excluded. Note that you
* can use the Java Regular Expressions engine to include and exclude specific pattern using the expression
* %regex[]. Hint: read the about (?!Pattern).
*
* @since 2.7
*/
@Parameter
private String packagingExcludes;
/**
* A comma separated list of tokens to include when packaging the EAR. By default everything is included. Note that
* you can use the Java Regular Expressions engine to include and exclude specific pattern using the expression
* %regex[].
*
* @since 2.7
*/
@Parameter
private String packagingIncludes;
/**
* Whether to create skinny WARs or not. A skinny WAR is a WAR that does not have all of its dependencies in
* WEB-INF/lib. Instead those dependencies are shared between the WARs through the EAR.
*
* @since 2.7
*/
@Parameter( defaultValue = "false" )
private boolean skinnyWars;
/**
* Whether to create skinny EAR modules or not. A skinny EAR module is a WAR, SAR, HAR, RAR or WSR module that
* does not contain all of its dependencies in it. Instead those dependencies are shared between the WARs, SARs,
* HARs, RARs and WSRs through the EAR. This option takes precedence over {@link #skinnyWars} option. That is if
* skinnyModules is {@code true} but {@link #skinnyWars} is {@code false} (explicitly or by default) then all
* modules including WARs are skinny.
*
* @since 3.2.0
*/
@Parameter( defaultValue = "false" )
private boolean skinnyModules;
/**
* The Plexus EAR archiver to create the output archive.
*/
@Component( role = Archiver.class, hint = "ear" )
private EarArchiver earArchiver;
/**
* The Plexus JAR archiver to create the output archive if not EAR application descriptor is provided (JavaEE 5+).
*/
@Component( role = Archiver.class, hint = "jar" )
private JarArchiver jarArchiver;
/**
* The Plexus Zip archiver for Skinny WAR repackaging.
*/
@Component( role = Archiver.class, hint = "zip" )
private ZipArchiver zipArchiver;
/**
* The Plexus Zip Un archiver for Skinny WAR repackaging.
*/
@Component( role = UnArchiver.class, hint = "zip" )
private ZipUnArchiver zipUnArchiver;
/**
* The archive configuration to use. See <a href="https://maven.apache.org/shared/maven-archiver/">Maven Archiver
* Reference</a>.
*/
@Parameter
private MavenArchiveConfiguration archive = new MavenArchiveConfiguration();
/**
* Timestamp for reproducible output archive entries, either formatted as ISO 8601
* <code>yyyy-MM-dd'T'HH:mm:ssXXX</code> or as an int representing seconds since the epoch (like
* <a href="https://reproducible-builds.org/docs/source-date-epoch/">SOURCE_DATE_EPOCH</a>).
*
* @since 3.1.0
*/
@Parameter( defaultValue = "${project.build.outputTimestamp}" )
private String outputTimestamp;
/**
*/
@Component
private MavenProjectHelper projectHelper;
/**
* The archive manager.
*/
@Component
private ArchiverManager archiverManager;
/**
*/
@Component( role = MavenFileFilter.class, hint = "default" )
private MavenFileFilter mavenFileFilter;
/**
*/
@Component( role = MavenResourcesFiltering.class, hint = "default" )
private MavenResourcesFiltering mavenResourcesFiltering;
/**
* @since 2.3.2
*/
@Parameter( defaultValue = "${session}", readonly = true, required = true )
private MavenSession session;
private List<FileUtils.FilterWrapper> filterWrappers;
/**
* @since 2.9
*/
@Parameter( defaultValue = "true" )
private boolean useJvmChmod = true;
/** {@inheritDoc} */
public void execute()
throws MojoExecutionException, MojoFailureException
{
// Initializes ear modules
super.execute();
File earFile = getEarFile( outputDirectory, finalName, classifier );
MavenArchiver archiver = new EarMavenArchiver( getModules() );
File ddFile = new File( getWorkDirectory(), APPLICATION_XML_URI );
JarArchiver theArchiver;
if ( ddFile.exists() )
{
earArchiver.setAppxml( ddFile );
theArchiver = earArchiver;
}
else
{
// current Plexus EarArchiver does not support application.xml-less JavaEE 5+ case
// => fallback to Plexus Jar archiver
theArchiver = jarArchiver;
}
getLog().debug( "Ear archiver implementation [" + theArchiver.getClass().getName() + "]" );
archiver.setArchiver( theArchiver );
archiver.setOutputFile( earFile );
archiver.setCreatedBy( "Maven EAR Plugin", "org.apache.maven.plugins", "maven-ear-plugin" );
// configure for Reproducible Builds based on outputTimestamp value
Date reproducibleLastModifiedDate = archiver.configureReproducible( outputTimestamp );
zipArchiver.setUseJvmChmod( useJvmChmod );
if ( reproducibleLastModifiedDate != null )
{
zipArchiver.configureReproducible( reproducibleLastModifiedDate );
}
zipUnArchiver.setUseJvmChmod( useJvmChmod );
final JavaEEVersion javaEEVersion = JavaEEVersion.getJavaEEVersion( version );
final Collection<String> outdatedResources = initOutdatedResources();
// Initializes unpack types
List<String> unpackTypesList = createUnpackList();
// Copy modules
copyModules( javaEEVersion, unpackTypesList, outdatedResources );
// Copy source files
try
{
File earSourceDir = earSourceDirectory;
if ( earSourceDir.exists() )
{
getLog().info( "Copy ear sources to " + getWorkDirectory().getAbsolutePath() );
String[] fileNames = getEarFiles( earSourceDir );
for ( String fileName : fileNames )
{
copyFile( new File( earSourceDir, fileName ), new File( getWorkDirectory(), fileName ) );
outdatedResources.remove( Paths.get( fileName ).toString() );
}
}
if ( applicationXml != null && !"".equals( applicationXml ) )
{
// rename to application.xml
getLog().info( "Including custom application.xml[" + applicationXml + "]" );
File metaInfDir = new File( getWorkDirectory(), META_INF );
copyFile( new File( applicationXml ), new File( metaInfDir, "/application.xml" ) );
outdatedResources.remove( Paths.get( "META-INF/application.xml" ).toString() );
}
}
catch ( IOException e )
{
throw new MojoExecutionException( "Error copying EAR sources", e );
}
catch ( MavenFilteringException e )
{
throw new MojoExecutionException( "Error filtering EAR sources", e );
}
// Check if deployment descriptor is there
if ( !ddFile.exists() && ( javaEEVersion.lt( JavaEEVersion.FIVE ) ) )
{
throw new MojoExecutionException( "Deployment descriptor: " + ddFile.getAbsolutePath()
+ " does not exist." );
}
// no need to check timestamp for descriptors: removing if outdated does not really make sense
outdatedResources.remove( Paths.get( APPLICATION_XML_URI ).toString() );
if ( getJbossConfiguration() != null )
{
outdatedResources.remove( Paths.get( "META-INF/jboss-app.xml" ).toString() );
}
deleteOutdatedResources( outdatedResources );
try
{
getLog().debug( "Excluding " + Arrays.asList( getPackagingExcludes() ) + " from the generated EAR." );
getLog().debug( "Including " + Arrays.asList( getPackagingIncludes() ) + " in the generated EAR." );
archiver.getArchiver().addDirectory( getWorkDirectory(), getPackagingIncludes(), getPackagingExcludes() );
archiver.createArchive( session, getProject(), archive );
}
catch ( ManifestException | IOException | DependencyResolutionRequiredException e )
{
throw new MojoExecutionException( "Error assembling EAR", e );
}
if ( classifier != null )
{
projectHelper.attachArtifact( getProject(), "ear", classifier, earFile );
}
else
{
getProject().getArtifact().setFile( earFile );
}
}
private void copyModules( final JavaEEVersion javaEEVersion,
List<String> unpackTypesList,
Collection<String> outdatedResources )
throws MojoExecutionException, MojoFailureException
{
final Path workingDir = getWorkDirectory().toPath();
try
{
for ( EarModule module : getModules() )
{
final File sourceFile = module.getArtifact().getFile();
final File destinationFile = buildDestinationFile( getWorkDirectory(), module.getUri() );
if ( !sourceFile.isFile() )
{
throw new MojoExecutionException( "Cannot copy a directory: " + sourceFile.getAbsolutePath()
+ "; Did you package/install " + module.getArtifact() + "?" );
}
if ( destinationFile.getCanonicalPath().equals( sourceFile.getCanonicalPath() ) )
{
getLog().info( "Skipping artifact [" + module + "], as it already exists at [" + module.getUri()
+ "]" );
// FIXME: Shouldn't that result in a build failure!?
continue;
}
// If the module is within the unpack list, make sure that no unpack wasn't forced (null or true)
// If the module is not in the unpack list, it should be true
if ( ( unpackTypesList.contains( module.getType() )
&& ( module.shouldUnpack() == null || module.shouldUnpack() ) )
|| ( module.shouldUnpack() != null && module.shouldUnpack() ) )
{
getLog().info( "Copying artifact [" + module + "] to [" + module.getUri() + "] (unpacked)" );
// Make sure that the destination is a directory to avoid plexus nasty stuff :)
if ( !destinationFile.isDirectory() && !destinationFile.mkdirs() )
{
throw new MojoExecutionException( "Error creating " + destinationFile );
}
unpack( sourceFile, destinationFile, outdatedResources );
if ( module.changeManifestClasspath() )
{
changeManifestClasspath( module, destinationFile, javaEEVersion );
}
}
else
{
if ( sourceFile.lastModified() > destinationFile.lastModified() )
{
getLog().info( "Copying artifact [" + module + "] to [" + module.getUri() + "]" );
createParentIfNecessary( destinationFile );
Files.copy( sourceFile.toPath(), destinationFile.toPath(),
LinkOption.NOFOLLOW_LINKS, StandardCopyOption.REPLACE_EXISTING );
if ( module.changeManifestClasspath() )
{
changeManifestClasspath( module, destinationFile, javaEEVersion );
}
}
else
{
getLog().debug( "Skipping artifact [" + module + "], as it is already up to date at ["
+ module.getUri() + "]" );
}
outdatedResources.remove( workingDir.relativize( destinationFile.toPath() ).toString() );
}
}
}
catch ( IOException e )
{
throw new MojoExecutionException( "Error copying EAR modules", e );
}
catch ( ArchiverException e )
{
throw new MojoExecutionException( "Error unpacking EAR modules", e );
}
catch ( NoSuchArchiverException e )
{
throw new MojoExecutionException( "No Archiver found for EAR modules", e );
}
}
private List<String> createUnpackList()
throws MojoExecutionException
{
List<String> unpackTypesList = new ArrayList<String>();
if ( unpackTypes != null )
{
unpackTypesList = Arrays.asList( unpackTypes.split( "," ) );
for ( String type : unpackTypesList )
{
if ( !EarModuleFactory.isStandardArtifactType( type ) )
{
throw new MojoExecutionException( "Invalid type [" + type + "] supported types are "
+ EarModuleFactory.getStandardArtifactTypes() );
}
}
getLog().debug( "Initialized unpack types " + unpackTypesList );
}
return unpackTypesList;
}
/**
* @return {@link #applicationXml}
*/
public String getApplicationXml()
{
return applicationXml;
}
/**
* @param applicationXml {@link #applicationXml}
*/
public void setApplicationXml( String applicationXml )
{
this.applicationXml = applicationXml;
}
/**
* Returns a string array of the excludes to be used when assembling/copying the ear.
*
* @return an array of tokens to exclude
*/
protected String[] getExcludes()
{
List<String> excludeList = new ArrayList<String>( FileUtils.getDefaultExcludesAsList() );
if ( earSourceExcludes != null && !"".equals( earSourceExcludes ) )
{
excludeList.addAll( Arrays.asList( StringUtils.split( earSourceExcludes, "," ) ) );
}
// if applicationXml is specified, omit the one in the source directory
if ( getApplicationXml() != null && !"".equals( getApplicationXml() ) )
{
excludeList.add( "**/" + META_INF + "/application.xml" );
}
return excludeList.toArray( new String[excludeList.size()] );
}
/**
* Returns a string array of the includes to be used when assembling/copying the ear.
*
* @return an array of tokens to include
*/
protected String[] getIncludes()
{
return StringUtils.split( Objects.toString( earSourceIncludes, "" ), "," );
}
/**
* @return The array with the packaging excludes.
*/
public String[] getPackagingExcludes()
{
if ( StringUtils.isEmpty( packagingExcludes ) )
{
return new String[0];
}
else
{
return StringUtils.split( packagingExcludes, "," );
}
}
/**
* @param packagingExcludes {@link #packagingExcludes}
*/
public void setPackagingExcludes( String packagingExcludes )
{
this.packagingExcludes = packagingExcludes;
}
/**
* @return the arrays with the includes
*/
public String[] getPackagingIncludes()
{
if ( StringUtils.isEmpty( packagingIncludes ) )
{
return new String[] { "**" };
}
else
{
return StringUtils.split( packagingIncludes, "," );
}
}
/**
* @param packagingIncludes {@link #packagingIncludes}
*/
public void setPackagingIncludes( String packagingIncludes )
{
this.packagingIncludes = packagingIncludes;
}
private static File buildDestinationFile( File buildDir, String uri )
{
return new File( buildDir, uri );
}
/**
* Returns the EAR file to generate, based on an optional classifier.
*
* @param basedir the output directory
* @param finalName the name of the ear file
* @param classifier an optional classifier
* @return the EAR file to generate
*/
private static File getEarFile( String basedir, String finalName, String classifier )
{
if ( classifier == null )
{
classifier = "";
}
else if ( classifier.trim().length() > 0 && !classifier.startsWith( "-" ) )
{
classifier = "-" + classifier;
}
return new File( basedir, finalName + classifier + ".ear" );
}
/**
* Returns a list of filenames that should be copied over to the destination directory.
*
* @param sourceDir the directory to be scanned
* @return the array of filenames, relative to the sourceDir
*/
private String[] getEarFiles( File sourceDir )
{
DirectoryScanner scanner = new DirectoryScanner();
scanner.setBasedir( sourceDir );
scanner.setExcludes( getExcludes() );
scanner.addDefaultExcludes();
scanner.setIncludes( getIncludes() );
scanner.scan();
return scanner.getIncludedFiles();
}
/**
* Unpacks the module into the EAR structure.
*
* @param source file to be unpacked
* @param destDir where to put the unpacked files
* @param outdatedResources currently outdated resources
* @throws ArchiverException a corrupt archive
* @throws NoSuchArchiverException if we don't have an appropriate archiver
* @throws IOException in case of a general IOException
*/
public void unpack( File source, final File destDir, final Collection<String> outdatedResources )
throws ArchiverException, NoSuchArchiverException, IOException
{
UnArchiver unArchiver = archiverManager.getUnArchiver( "zip" );
unArchiver.setSourceFile( source );
unArchiver.setDestDirectory( destDir );
unArchiver.setFileMappers( new FileMapper[] {
new FileMapper()
{
@Override
public String getMappedFileName( String pName )
{
Path destFile = destDir.toPath().resolve( pName );
outdatedResources.remove( getWorkDirectory().toPath().relativize( destFile ).toString() );
return pName;
}
}
} );
// Extract the module
unArchiver.extract();
}
private void copyFile( File source, File target )
throws MavenFilteringException, IOException, MojoExecutionException
{
createParentIfNecessary( target );
if ( filtering && !isNonFilteredExtension( source.getName() ) )
{
mavenFileFilter.copyFile( source, target, true, getFilterWrappers(), encoding );
}
else
{
Files.copy( source.toPath(), target.toPath(), LinkOption.NOFOLLOW_LINKS,
StandardCopyOption.REPLACE_EXISTING );
}
}
private void createParentIfNecessary( File target )
throws IOException
{
// Silly that we have to do this ourselves
File parentDirectory = target.getParentFile();
if ( parentDirectory != null && !parentDirectory.exists() )
{
Files.createDirectories( parentDirectory.toPath() );
}
}
/**
* @param fileName the name of the file which should be checked
* @return {@code true} if the name is part of the non filtered extensions; {@code false} otherwise
*/
public boolean isNonFilteredExtension( String fileName )
{
return !mavenResourcesFiltering.filteredFileExtension( fileName, nonFilteredFileExtensions );
}
private List<FileUtils.FilterWrapper> getFilterWrappers()
throws MojoExecutionException
{
if ( filterWrappers == null )
{
try
{
MavenResourcesExecution mavenResourcesExecution = new MavenResourcesExecution();
mavenResourcesExecution.setMavenProject( getProject() );
mavenResourcesExecution.setEscapedBackslashesInFilePath( escapedBackslashesInFilePath );
mavenResourcesExecution.setFilters( filters );
mavenResourcesExecution.setEscapeString( escapeString );
filterWrappers = mavenFileFilter.getDefaultFilterWrappers( mavenResourcesExecution );
}
catch ( MavenFilteringException e )
{
getLog().error( "Fail to build filtering wrappers " + e.getMessage() );
throw new MojoExecutionException( e.getMessage(), e );
}
}
return filterWrappers;
}
private void changeManifestClasspath( EarModule module, File original, JavaEEVersion javaEEVersion )
throws MojoFailureException
{
final String moduleLibDir = module.getLibDir();
if ( !( ( moduleLibDir == null ) || skinnyModules || ( skinnyWars && module instanceof WebModule ) ) )
{
return;
}
try
{
File workDirectory;
// Handle the case that the destination might be a directory (project-038)
if ( original.isFile() )
{
// Create a temporary work directory
// MEAR-167 use uri as directory to prevent merging of artifacts with the same artifactId
workDirectory = new File( new File( getTempFolder(), "temp" ), module.getUri() );
if ( !workDirectory.isDirectory() )
{
if ( workDirectory.mkdirs() )
{
getLog().debug( "Created a temporary work directory: " + workDirectory.getAbsolutePath() );
}
else
{
throw new MojoFailureException( "Failed to create directory " + workDirectory );
}
}
// Unpack the archive to a temporary work directory
zipUnArchiver.setSourceFile( original );
zipUnArchiver.setDestDirectory( workDirectory );
zipUnArchiver.extract();
}
else
{
workDirectory = original;
}
// Create a META-INF/MANIFEST.MF file if it doesn't exist (project-038)
File metaInfDirectory = new File( workDirectory, "META-INF" );
boolean newMetaInfCreated = metaInfDirectory.mkdirs();
if ( newMetaInfCreated )
{
getLog().debug(
"This project did not have a META-INF directory before, so a new directory was created." );
}
File manifestFile = new File( metaInfDirectory, "MANIFEST.MF" );
boolean newManifestCreated = manifestFile.createNewFile();
if ( newManifestCreated )
{
getLog().debug(
"This project did not have a META-INF/MANIFEST.MF file before, so a new file was created." );
}
Manifest mf = readManifest( manifestFile );
Attribute classPath = mf.getMainSection().getAttribute( "Class-Path" );
List<String> classPathElements = new ArrayList<String>();
boolean classPathExists;
if ( classPath != null )
{
classPathExists = true;
classPathElements.addAll( Arrays.asList( classPath.getValue().split( " " ) ) );
}
else
{
classPathExists = false;
classPath = new Attribute( "Class-Path", "" );
}
if ( ( moduleLibDir != null ) && ( skinnyModules || ( skinnyWars && module instanceof WebModule ) ) )
{
// Remove modules
for ( EarModule otherModule : getAllEarModules() )
{
if ( module.equals( otherModule ) )
{
continue;
}
// MEAR-189:
// We use the original name, cause in case of outputFileNameMapping
// we could not not delete it and it will end up in the resulting EAR and the WAR
// will not be cleaned up.
final File workLibDir = new File( workDirectory, moduleLibDir );
File artifact = new File( workLibDir, module.getArtifact().getFile().getName() );
// MEAR-217
// If WAR contains files with timestamps, but EAR strips them away (useBaseVersion=true)
// the artifact is not found. Therefore respect the current fileNameMapping additionally.
if ( !artifact.exists() )
{
getLog().debug( "module does not exist with original file name." );
artifact = new File( workLibDir, otherModule.getBundleFileName() );
getLog().debug( "Artifact with mapping: " + artifact.getAbsolutePath() );
}
if ( !artifact.exists() )
{
getLog().debug( "Artifact with mapping does not exist." );
artifact = new File( workLibDir, otherModule.getArtifact().getFile().getName() );
getLog().debug( "Artifact with original file name: " + artifact.getAbsolutePath() );
}
if ( !artifact.exists() )
{
getLog().debug( "Artifact with original file name does not exist." );
final Artifact otherModuleArtifact = otherModule.getArtifact();
if ( otherModuleArtifact.isSnapshot() )
{
try
{
artifact = new File( workLibDir, MappingUtils.evaluateFileNameMapping(
ARTIFACT_DEFAULT_FILE_NAME_MAPPING, otherModuleArtifact ) );
getLog()
.debug( "Artifact with default mapping file name: " + artifact.getAbsolutePath() );
}
catch ( InterpolationException e )
{
getLog().warn(
"Failed to evaluate file name for [" + otherModule + "] module using mapping: "
+ ARTIFACT_DEFAULT_FILE_NAME_MAPPING );
}
}
}
if ( artifact.exists() )
{
getLog().debug( " -> Artifact to delete: " + artifact );
if ( !artifact.delete() )
{
getLog().error( "Could not delete '" + artifact + "'" );
}
}
}
}
// Modify the classpath entries in the manifest
final boolean forceClassPathModification =
javaEEVersion.lt( JavaEEVersion.FIVE ) || defaultLibBundleDir == null;
final boolean classPathExtension = !skipClassPathModification || forceClassPathModification;
for ( EarModule otherModule : getModules() )
{
if ( module.equals( otherModule ) )
{
continue;
}
final int moduleClassPathIndex = findModuleInClassPathElements( classPathElements, otherModule );
if ( moduleClassPathIndex != -1 )
{
if ( otherModule.isClassPathItem() )
{
classPathElements.set( moduleClassPathIndex, otherModule.getUri() );
}
else
{
classPathElements.remove( moduleClassPathIndex );
}
}
else if ( otherModule.isClassPathItem() && classPathExtension )
{
classPathElements.add( otherModule.getUri() );
}
}
// Remove provided modules from classpath
for ( EarModule otherModule : getProvidedEarModules() )
{
final int moduleClassPathIndex = findModuleInClassPathElements( classPathElements, otherModule );
if ( moduleClassPathIndex != -1 )
{
classPathElements.remove( moduleClassPathIndex );
}
}
if ( !skipClassPathModification || !classPathElements.isEmpty() || classPathExists )
{
classPath.setValue( StringUtils.join( classPathElements.iterator(), " " ) );
mf.getMainSection().addConfiguredAttribute( classPath );
// Write the manifest to disk
try ( FileOutputStream out = new FileOutputStream( manifestFile );
OutputStreamWriter writer = new OutputStreamWriter( out, StandardCharsets.UTF_8 ) )
{
mf.write( writer );
}
}
if ( original.isFile() )
{
// Pack up the archive again from the work directory
if ( !original.delete() )
{
getLog().error( "Could not delete original artifact file " + original );
}
getLog().debug( "Zipping module" );
zipArchiver.setDestFile( original );
zipArchiver.addDirectory( workDirectory );
zipArchiver.createArchive();
}
}
catch ( ManifestException | IOException | ArchiverException e )
{
throw new MojoFailureException( e.getMessage(), e );
}
}
private static Manifest readManifest( File manifestFile )
throws IOException
{
// Read the manifest from disk
try ( FileInputStream in = new FileInputStream( manifestFile ) )
{
Manifest manifest = new Manifest( in );
return manifest;
}
}
private Collection<String> initOutdatedResources()
{
final Collection<String> outdatedResources = new ArrayList<>();
if ( getWorkDirectory().exists() )
{
try
{
Files.walkFileTree( getWorkDirectory().toPath(), new SimpleFileVisitor<Path>()
{
@Override
public FileVisitResult visitFile( Path file, BasicFileAttributes attrs )
throws IOException
{
outdatedResources.add( getWorkDirectory().toPath().relativize( file ).toString() );
return super.visitFile( file, attrs );
}
} );
}
catch ( IOException e )
{
getLog().warn( "Can't detect outdated resources", e );
}
}
return outdatedResources;
}
private void deleteOutdatedResources( final Collection<String> outdatedResources )
{
final long startTime = session.getStartTime().getTime();
for ( String outdatedResource : outdatedResources )
{
if ( new File( getWorkDirectory(), outdatedResource ).lastModified() < startTime )
{
getLog().info( "deleting outdated resource " + outdatedResource );
new File( getWorkDirectory(), outdatedResource ).delete();
}
}
}
/**
* Searches for the given JAR module in the list of classpath elements. If JAR module is found among specified
* classpath elements then returns index of first matching element. Returns -1 otherwise.
*
* @param classPathElements classpath elements to search among
* @param module module to find among classpath elements defined by {@code classPathElements}
* @return -1 if {@code module} was not found in {@code classPathElements} or index of item of
* {@code classPathElements} which matches {@code module}
*/
private int findModuleInClassPathElements( final List<String> classPathElements, final EarModule module )
{
if ( classPathElements.isEmpty() )
{
return -1;
}
int moduleClassPathIndex = classPathElements.indexOf( module.getBundleFileName() );
if ( moduleClassPathIndex != -1 )
{
return moduleClassPathIndex;
}
final Artifact artifact = module.getArtifact();
moduleClassPathIndex = classPathElements.indexOf( artifact.getFile().getName() );
if ( moduleClassPathIndex != -1 )
{
return moduleClassPathIndex;
}
if ( artifact.isSnapshot() )
{
try
{
moduleClassPathIndex = classPathElements
.indexOf( MappingUtils.evaluateFileNameMapping( ARTIFACT_DEFAULT_FILE_NAME_MAPPING, artifact ) );
if ( moduleClassPathIndex != -1 )
{
return moduleClassPathIndex;
}
}
catch ( InterpolationException e )
{
getLog().warn( "Failed to evaluate file name for [" + module + "] module using mapping: "
+ ARTIFACT_DEFAULT_FILE_NAME_MAPPING );
}
}
return classPathElements.indexOf( module.getUri() );
}
}