Skip to content

Commit

Permalink
[MEAR-267] - Fixed detection if JAR module is included into classpath…
Browse files Browse the repository at this point in the history
… of particular EAR module manifest (#19)

* [MEAR-267] - Fixed detection if EAR JAR module is included into classpath of particular EAR module manifest

* [MEAR-267] - Fixed detection of need of modification of Class-Path entry of EAR module manifest

* [MEAR-267] - Performance optimization for the case when EAR module manifest has no Class-Path entry

* [MEAR-267] - Integration tests

* [MEAR-267] - The same rules applied to modification of Class-Path manifest entry as used for removal of JAR modules from EAR modules

* [MEAR-267] - Fixed formatting of JavaDoc in tests

* [MEAR-267] - Test for the case when unpacking of EJB JARs is turned on

* [MEAR-267] - Renamed test method parameters and rephrased / fixed formatting of JavaDoc, inlined simple single-line code.

* [MEAR-267] - Minor code style fix.

* [MEAR-267] - JavaDoc simplified.

* [MEAR-267] - Test for "dirty" build.

* [MEAR-267] - Fixed modification of Class-Path entry of manifest of EAR modules when doing "dirty" build, i.e. when element with desired path already exists in target manifest Class-Path entry

* [MEAR-267] - Support of JDK 11+ in integration tests
  • Loading branch information
mabrarov authored Oct 13, 2020
1 parent 1e35164 commit 74d28d8
Show file tree
Hide file tree
Showing 25 changed files with 1,276 additions and 49 deletions.
6 changes: 4 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@
<surefire.version>2.22.2</surefire.version>
<project.build.outputTimestamp>2020-09-26T20:10:30Z</project.build.outputTimestamp>
<mavenWarPluginVersion>2.1.1</mavenWarPluginVersion>
<mavenCompilerPluginVersion>2.5.1</mavenCompilerPluginVersion>
<mavenEjbPluginVersion>2.3</mavenEjbPluginVersion>
<invoker.skip>false</invoker.skip>
<invoker.install.skip>${invoker.skip}</invoker.install.skip>
<invoker.it.skip>${invoker.skip}</invoker.it.skip>
Expand Down Expand Up @@ -282,8 +284,8 @@
</goals>
<extraArtifacts>
<extraArtifact>org.apache.maven.plugins:maven-war-plugin:${mavenWarPluginVersion}:jar</extraArtifact>
<extraArtifact>org.apache.maven.plugins:maven-compiler-plugin:2.5.1:jar</extraArtifact>
<extraArtifact>org.apache.maven.plugins:maven-ejb-plugin:2.3:jar</extraArtifact>
<extraArtifact>org.apache.maven.plugins:maven-compiler-plugin:${mavenCompilerPluginVersion}:jar</extraArtifact>
<extraArtifact>org.apache.maven.plugins:maven-ejb-plugin:${mavenEjbPluginVersion}:jar</extraArtifact>
</extraArtifacts>
<skipInstallation>${invoker.install.skip}</skipInstallation>
<skipInvocation>${invoker.it.skip}</skipInvocation>
Expand Down
48 changes: 38 additions & 10 deletions src/main/java/org/apache/maven/plugins/ear/EarMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ private void copyModules( final JavaEEVersion javaEEVersion,
}
unpack( sourceFile, destinationFile, outdatedResources );

if ( skinnyWars && module.changeManifestClasspath() )
if ( module.changeManifestClasspath() && ( skinnyWars || module.getLibDir() == null ) )
{
changeManifestClasspath( module, destinationFile, javaEEVersion );
}
Expand All @@ -461,7 +461,7 @@ private void copyModules( final JavaEEVersion javaEEVersion,
getLog().info( "Copying artifact [" + module + "] to [" + module.getUri() + "]" );
FileUtils.copyFile( sourceFile, destinationFile );

if ( skinnyWars && module.changeManifestClasspath() )
if ( module.changeManifestClasspath() && ( skinnyWars || module.getLibDir() == null ) )
{
changeManifestClasspath( module, destinationFile, javaEEVersion );
}
Expand Down Expand Up @@ -808,8 +808,8 @@ private void changeManifestClasspath( EarModule module, File original, JavaEEVer
// 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.
File artifact = new File( new File( workDirectory, module.getLibDir() ),
module.getArtifact().getFile().getName() );
final File workLibDir = new File( workDirectory, module.getLibDir() );
File artifact = new File( workLibDir, module.getArtifact().getFile().getName() );

// MEAR-217
// If WAR contains files with timestamps, but EAR strips them away (useBaseVersion=true)
Expand All @@ -818,16 +818,15 @@ private void changeManifestClasspath( EarModule module, File original, JavaEEVer
if ( !artifact.exists() )
{
getLog().debug( "module does not exist with original file name." );
artifact = new File( new File( workDirectory, module.getLibDir() ), jm.getBundleFileName() );
artifact = new File( workLibDir, jm.getBundleFileName() );
getLog().debug( "Artifact with mapping:" + artifact.getAbsolutePath() );
}

if ( !artifact.exists() )
{
getLog().debug( "Artifact with mapping does not exist." );
artifact = new File( new File( workDirectory, module.getLibDir() ),
jm.getArtifact().getFile().getName() );
getLog().debug( "Artifact with orignal file name:" + artifact.getAbsolutePath() );
artifact = new File( workLibDir, jm.getArtifact().getFile().getName() );
getLog().debug( "Artifact with original file name:" + artifact.getAbsolutePath() );
}

if ( artifact.exists() )
Expand All @@ -847,9 +846,10 @@ private void changeManifestClasspath( EarModule module, File original, JavaEEVer
if ( o instanceof JarModule )
{
JarModule jm = (JarModule) o;
if ( classPathElements.contains( jm.getBundleFileName() ) )
final int moduleClassPathIndex = findModuleInClassPathElements( classPathElements, jm );
if ( moduleClassPathIndex != -1 )
{
classPathElements.set( classPathElements.indexOf( jm.getBundleFileName() ), jm.getUri() );
classPathElements.set( moduleClassPathIndex, jm.getUri() );
}
else
{
Expand Down Expand Up @@ -948,4 +948,32 @@ private void deleteOutdatedResources( final Collection<String> outdatedResources
}
}
}

/**
* 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 JarModule module )
{
if ( classPathElements.isEmpty() )
{
return -1;
}
int moduleClassPathIndex = classPathElements.indexOf( module.getBundleFileName() );
if ( moduleClassPathIndex != -1 )
{
return moduleClassPathIndex;
}
moduleClassPathIndex = classPathElements.indexOf( module.getArtifact().getFile().getName() );
if ( moduleClassPathIndex != -1 )
{
return moduleClassPathIndex;
}
return classPathElements.indexOf( module.getUri() );
}
}
153 changes: 124 additions & 29 deletions src/test/java/org/apache/maven/plugins/ear/it/AbstractEarPluginIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,15 @@
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
Expand All @@ -39,6 +44,7 @@
import org.apache.maven.plugins.ear.util.ResourceEntityResolver;
import org.custommonkey.xmlunit.Diff;
import org.custommonkey.xmlunit.XMLAssert;
import org.junit.Assert;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

Expand Down Expand Up @@ -126,33 +132,47 @@ protected File executeMojo( final String projectName, final Properties propertie
}

/**
* Executes the specified projects and asserts the given artifacts. Assert the deployment descriptors are valid
* Executes the specified projects and asserts the given artifacts. Asserts the deployment descriptors are valid.
* Asserts Class-Path entry of manifest of EAR modules.
*
* @param projectName the project to test
* @param earModuleName the name of 1st level EAR module in multi-module project or null if project is single-module
* @param expectedArtifacts the list of artifacts to be found in the EAR archive
* @param artifactsDirectory whether the artifact is an exploded artifactsDirectory or not
* @param artifactsToValidateManifest the list of EAR archive artifacts to validate Class-Path entry of artifact
* manifest or {@code null} if there is no need to validate Class-Path entry
* @param artifactsToValidateManifestDirectory whether the artifact from {@code artifactsToValidateManifest} list is
* an exploded or not, can be {@code null} if
* {@code artifactsToValidateManifest} is {@code null}
* @param expectedClassPathElements the list of elements of Class-Path entry of manifest, rows should match
* artifacts passed in {@code artifactsToValidateManifest} parameter;
* can be {@code null} if {@code artifactsToValidateManifest} is {@code null}
* @param cleanBeforeExecute call clean plugin before execution
* @return the base directory of the project
*/
protected File doTestProject( final String projectName, final String earModuleName, final String[] expectedArtifacts,
final boolean[] artifactsDirectory, boolean cleanBeforeExecute )
protected File doTestProject( final String projectName, final String earModuleName,
final String[] expectedArtifacts, boolean[] artifactsDirectory,
final String[] artifactsToValidateManifest,
boolean[] artifactsToValidateManifestDirectory,
final String[][] expectedClassPathElements,
final boolean cleanBeforeExecute )
throws VerificationException, IOException
{
final File baseDir = executeMojo( projectName, new Properties(), true, cleanBeforeExecute );
final File earDir = earModuleName == null ? baseDir : new File( baseDir, earModuleName );
assertEarArchive( earDir, projectName );
assertEarDirectory( earDir, projectName );

assertArchiveContent( earDir, projectName, expectedArtifacts, artifactsDirectory );

assertDeploymentDescriptors( earDir, projectName );
final File earModuleDir = getEarModuleDirectory( baseDir, earModuleName );
assertEarArchive( earModuleDir, projectName );
assertEarDirectory( earModuleDir, projectName );
assertArchiveContent( earModuleDir, projectName, expectedArtifacts, artifactsDirectory );
assertDeploymentDescriptors( earModuleDir, projectName );
assertClassPathElements( earModuleDir, projectName, artifactsToValidateManifest,
artifactsToValidateManifestDirectory, expectedClassPathElements );

return baseDir;
}

/**
* Executes the specified projects and asserts the given artifacts. Assert the deployment descriptors are valid
* Executes the specified projects and asserts the given artifacts. Assert the deployment descriptors are valid.
*
* @param projectName the project to test
* @param expectedArtifacts the list of artifacts to be found in the EAR archive
Expand All @@ -163,39 +183,22 @@ protected File doTestProject( final String projectName, final String[] expectedA
final boolean[] artifactsDirectory )
throws VerificationException, IOException
{
return doTestProject( projectName, null, expectedArtifacts, artifactsDirectory, true );
return doTestProject( projectName, null, expectedArtifacts, artifactsDirectory, null, null, null, true );
}

/**
* Executes the specified projects and asserts the given artifacts as artifacts (non directory)
*
* @param projectName the project to test
* @param expectedArtifacts the list of artifacts to be found in the EAR archive
* @param testDeploymentDescriptors whether we should test deployment descriptors
* @return the base directory of the project
*/
private File doTestProject( final String projectName, final String[] expectedArtifacts,
boolean testDeploymentDescriptors )
protected File doTestProject( final String projectName, final String[] expectedArtifacts )
throws VerificationException, IOException
{
return doTestProject( projectName, expectedArtifacts, new boolean[expectedArtifacts.length] );
}

/**
* Executes the specified projects and asserts the given artifacts as artifacts (non directory). Assert the
* deployment descriptors are valid
*
* @param projectName the project to test
* @param expectedArtifacts the list of artifacts to be found in the EAR archive
* @return the base directory of the project
* @throws Exception Mojo exception in case of an error.
*/
protected File doTestProject( final String projectName, final String[] expectedArtifacts )
throws Exception
{
return doTestProject( projectName, expectedArtifacts, true );
}

protected void assertEarArchive( final File baseDir, final String projectName )
{
assertTrue( "EAR archive does not exist", getEarArchive( baseDir, projectName ).exists() );
Expand All @@ -206,6 +209,11 @@ protected void assertEarDirectory( final File baseDir, final String projectName
assertTrue( "EAR archive directory does not exist", getEarDirectory( baseDir, projectName ).exists() );
}

protected File getEarModuleDirectory( final File baseDir, final String earModuleName)
{
return earModuleName == null ? baseDir : new File( baseDir, earModuleName );
}

protected File getTargetDirectory( final File basedir )
{
return new File( basedir, "target" );
Expand Down Expand Up @@ -404,4 +412,91 @@ public boolean accept( File dir, String name )
}
} );
}

/**
* Asserts that given EAR archive artifacts have expected elements in artifact manifest Class-Path entry.
*
* @param baseDir the directory of the tested project
* @param projectName the name of the project
* @param artifacts the list of EAR archive artifacts to validate Class-Path entry of artifact manifest or
* {@code null} if there is no need to validate Class-Path entry
* @param artifactsDirectory whether the artifact from {@code artifacts} list is an exploded or not,
* can be {@code null} if {@code artifacts} is {@code null}
* @param expectedClassPathElements the list of expected elements of Class-Path entry of manifest, rows should match
* artifacts passed in {@code artifacts} parameter; can be {@code null}
* if {@code artifacts} is {@code null}
* @throws IOException exception in case of an failure during reading of artifact manifest.
*/
protected void assertClassPathElements( final File baseDir, String projectName, String[] artifacts,
boolean[] artifactsDirectory, String[][] expectedClassPathElements )
throws IOException
{
if ( artifacts == null )
{
return;
}

assertNotNull( "artifactsDirectory should be provided if artifacts is provided",
artifactsDirectory );
assertTrue( "Size of artifactsDirectory should match size of artifacts parameter",
artifacts.length <= artifactsDirectory.length );
assertNotNull( "expectedClassPathElements should be provided if artifacts is provided",
expectedClassPathElements );
assertTrue( "Rows of expectedClassPathElements parameter should match items of artifacts parameter",
artifacts.length <= expectedClassPathElements.length );

final File earFile = getEarArchive( baseDir, projectName );
for ( int i = 0; i != artifacts.length; ++i )
{
final String moduleArtifact = artifacts[i];
Assert.assertArrayEquals( "Wrong elements of Class-Path entry of module [" + moduleArtifact + "] manifest",
expectedClassPathElements[i],
getClassPathElements( earFile, moduleArtifact, artifactsDirectory[i] ) );
}
}

/**
* Retrieves elements of Class-Path entry of manifest of given EAR module.
*
* @param earFile the EAR file to investigate
* @param artifact the name of artifact in EAR archive representing EAR module
* @return elements of Class-Path entry of manifest of EAR module which is represented by
* {@code artifact} artifact in {@code earFile} file
*/
protected String[] getClassPathElements( final File earFile, final String artifact, final boolean directory )
throws IOException
{
final String classPath;
try ( JarFile earJarFile = new JarFile( earFile ) )
{
final ZipEntry moduleEntry = earJarFile.getEntry( artifact );
assertNotNull( "Artifact [" + artifact + "] should exist in EAR", moduleEntry );
if (directory)
{
final String manifestEntryName = artifact + "/META-INF/MANIFEST.MF";
final ZipEntry manifestEntry = earJarFile.getEntry( manifestEntryName );
assertNotNull( manifestEntryName + " manifest file should exist in EAR", manifestEntry );
try ( InputStream manifestInputStream = earJarFile.getInputStream( manifestEntry ) )
{
final Manifest manifest = new Manifest(manifestInputStream);
classPath = manifest.getMainAttributes().getValue( "Class-Path" );
}
}
else
{
try ( InputStream moduleInputStream = earJarFile.getInputStream( moduleEntry );
JarInputStream moduleJarInputStream = new JarInputStream( moduleInputStream ) )
{
final Manifest manifest = moduleJarInputStream.getManifest();
assertNotNull( "Artifact [" + artifact + "] of EAR should have manifest", manifest );
classPath = manifest.getMainAttributes().getValue( "Class-Path" );
}
}
}
if ( classPath == null )
{
return new String[0];
}
return classPath.split( " " );
}
}
Loading

0 comments on commit 74d28d8

Please sign in to comment.