This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-shade-plugin.git
The following commit(s) were added to refs/heads/master by this push:
new 41bd72f [MSHADE-366] "Access denied" during 'minimizeJar' (#161)
41bd72f is described below
commit 41bd72f0fd0195713f4a4b73af1184ec8a6cc0a3
Author: Guillaume Nodet <[email protected]>
AuthorDate: Thu Oct 20 22:35:52 2022 +0200
[MSHADE-366] "Access denied" during 'minimizeJar' (#161)
* [MSHADE-366] - "Access denied" during 'minimizeJar'
Now ignoring directories when scanning the classpath for services.
* [MSHADE-366] Refactor fix by @JanMosigItemis from #83
- Simplify Jan's solution from #83 in order to use 'continue' instead of
nested 'if-else'.
- Factor out two helper methods from 'removeServices', because that
method was way too big to still be readable.
- DRY-refactor Jan's new test cases into one checking two conditions.
* Another attempt to clarify the problem
- do not ignore directories, print a warning as before
- ignore the project's build output directory which is always returned by
getRuntimeClassPathElements()
* Fix the test to work on all platforms, irrespective of the actual
exception sent by the JDK
Co-authored-by: Jan Mosig <[email protected]>
Co-authored-by: Alexander Kriegisch <[email protected]>
---
.../maven/plugins/shade/filter/MinijarFilter.java | 121 +++++++++++++--------
.../plugins/shade/filter/MinijarFilterTest.java | 86 +++++++++++----
2 files changed, 137 insertions(+), 70 deletions(-)
diff --git
a/src/main/java/org/apache/maven/plugins/shade/filter/MinijarFilter.java
b/src/main/java/org/apache/maven/plugins/shade/filter/MinijarFilter.java
index 818339e..a996bd4 100644
--- a/src/main/java/org/apache/maven/plugins/shade/filter/MinijarFilter.java
+++ b/src/main/java/org/apache/maven/plugins/shade/filter/MinijarFilter.java
@@ -129,58 +129,22 @@ public class MinijarFilter
neededClasses.removeAll( removable );
try
{
+ // getRuntimeClasspathElements returns a list of
+ // - the build output directory
+ // - all the paths to the dependencies' jars
+ // We thereby need to ignore the build directory because we
don't want
+ // to remove anything from it, as it's the starting point of
the
+ // minification process.
for ( final String fileName :
project.getRuntimeClasspathElements() )
{
- try ( final JarFile jar = new JarFile( fileName ) )
+ // Ignore the build directory from this project
+ if ( fileName.equals(
project.getBuild().getOutputDirectory() ) )
{
- for ( final Enumeration<JarEntry> entries =
jar.entries(); entries.hasMoreElements(); )
- {
- final JarEntry jarEntry = entries.nextElement();
- if ( jarEntry.isDirectory() ||
!jarEntry.getName().startsWith( "META-INF/services/" ) )
- {
- continue;
- }
-
- final String serviceClassName =
- jarEntry.getName().substring(
"META-INF/services/".length() );
- final boolean isNeededClass =
neededClasses.contains( cp.getClazz( serviceClassName ) );
- if ( !isNeededClass )
- {
- continue;
- }
-
- try ( final BufferedReader bufferedReader =
- new BufferedReader( new InputStreamReader(
jar.getInputStream( jarEntry ), UTF_8 ) ) )
- {
- for ( String line = bufferedReader.readLine();
line != null;
- line = bufferedReader.readLine() )
- {
- final String className = line.split( "#",
2 )[0].trim();
- if ( className.isEmpty() )
- {
- continue;
- }
-
- final Clazz clazz = cp.getClazz( className
);
- if ( clazz == null || !removable.contains(
clazz ) )
- {
- continue;
- }
-
- log.debug( className + " was not removed
because it is a service" );
- removeClass( clazz );
- repeatScan = true; // check whether the
found classes use services in turn
- }
- }
- catch ( final IOException e )
- {
- log.warn( e.getMessage() );
- }
- }
+ continue;
}
- catch ( final IOException e )
+ if ( removeServicesFromJar( cp, neededClasses, fileName ) )
{
- log.warn( e.getMessage() );
+ repeatScan = true;
}
}
}
@@ -192,6 +156,69 @@ public class MinijarFilter
while ( repeatScan );
}
+ private boolean removeServicesFromJar( Clazzpath cp, Set<Clazz>
neededClasses, String fileName )
+ {
+ boolean repeatScan = false;
+ try ( final JarFile jar = new JarFile( fileName ) )
+ {
+ for ( final Enumeration<JarEntry> entries = jar.entries();
entries.hasMoreElements(); )
+ {
+ final JarEntry jarEntry = entries.nextElement();
+ if ( jarEntry.isDirectory() || !jarEntry.getName().startsWith(
"META-INF/services/" ) )
+ {
+ continue;
+ }
+
+ final String serviceClassName = jarEntry.getName().substring(
"META-INF/services/".length() );
+ final boolean isNeededClass = neededClasses.contains(
cp.getClazz( serviceClassName ) );
+ if ( !isNeededClass )
+ {
+ continue;
+ }
+
+ try ( final BufferedReader configFileReader = new
BufferedReader(
+ new InputStreamReader( jar.getInputStream( jarEntry ),
UTF_8 ) ) )
+ {
+ // check whether the found classes use services in turn
+ repeatScan = scanServiceProviderConfigFile( cp,
configFileReader );
+ }
+ catch ( final IOException e )
+ {
+ log.warn( e.getMessage() );
+ }
+ }
+ }
+ catch ( final IOException e )
+ {
+ log.warn( "Not a JAR file candidate. Ignoring classpath element '"
+ fileName + "' (" + e + ")." );
+ }
+ return repeatScan;
+ }
+
+ private boolean scanServiceProviderConfigFile( Clazzpath cp,
BufferedReader configFileReader ) throws IOException
+ {
+ boolean serviceClassFound = false;
+ for ( String line = configFileReader.readLine(); line != null; line =
configFileReader.readLine() )
+ {
+ final String className = line.split( "#", 2 )[0].trim();
+ if ( className.isEmpty() )
+ {
+ continue;
+ }
+
+ final Clazz clazz = cp.getClazz( className );
+ if ( clazz == null || !removable.contains( clazz ) )
+ {
+ continue;
+ }
+
+ log.debug( className + " was not removed because it is a service"
);
+ removeClass( clazz );
+ serviceClassFound = true;
+ }
+ return serviceClassFound;
+ }
+
private void removeClass( final Clazz clazz )
{
removable.remove( clazz );
diff --git
a/src/test/java/org/apache/maven/plugins/shade/filter/MinijarFilterTest.java
b/src/test/java/org/apache/maven/plugins/shade/filter/MinijarFilterTest.java
index bfbaee2..25be4d8 100644
--- a/src/test/java/org/apache/maven/plugins/shade/filter/MinijarFilterTest.java
+++ b/src/test/java/org/apache/maven/plugins/shade/filter/MinijarFilterTest.java
@@ -19,7 +19,10 @@ package org.apache.maven.plugins.shade.filter;
* under the License.
*/
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -27,15 +30,23 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import java.util.Set;
import java.util.TreeSet;
+import java.util.jar.JarOutputStream;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DefaultArtifact;
+import org.apache.maven.artifact.DependencyResolutionRequiredException;
+import org.apache.maven.model.Build;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.ArgumentCaptor;
@@ -43,16 +54,25 @@ import org.mockito.ArgumentCaptor;
public class MinijarFilterTest
{
+ @Rule
+ public TemporaryFolder tempFolder =
TemporaryFolder.builder().assureDeletion().build();
+
+ private File outputDirectory;
private File emptyFile;
+ private File jarFile;
+ private Log log;
+ private ArgumentCaptor<CharSequence> logCaptor;
@Before
public void init()
throws IOException
{
- TemporaryFolder tempFolder = new TemporaryFolder();
- tempFolder.create();
+ this.outputDirectory = tempFolder.newFolder();
this.emptyFile = tempFolder.newFile();
-
+ this.jarFile = tempFolder.newFile();
+ new JarOutputStream( new FileOutputStream( this.jarFile ) ).close();
+ this.log = mock(Log.class);
+ logCaptor = ArgumentCaptor.forClass(CharSequence.class);
}
/**
@@ -64,11 +84,7 @@ public class MinijarFilterTest
{
assumeFalse( "Expected to run under JDK8+",
System.getProperty("java.version").startsWith("1.7") );
- ArgumentCaptor<CharSequence> logCaptor = ArgumentCaptor.forClass(
CharSequence.class );
-
- MavenProject mavenProject = mockProject( emptyFile );
-
- Log log = mock( Log.class );
+ MavenProject mavenProject = mockProject( outputDirectory, emptyFile );
MinijarFilter mf = new MinijarFilter( mavenProject, log );
@@ -84,14 +100,10 @@ public class MinijarFilterTest
public void testWithPomProject()
throws IOException
{
- ArgumentCaptor<CharSequence> logCaptor = ArgumentCaptor.forClass(
CharSequence.class );
-
// project with pom packaging and no artifact.
- MavenProject mavenProject = mockProject( null );
+ MavenProject mavenProject = mockProject( outputDirectory, null );
mavenProject.setPackaging( "pom" );
- Log log = mock( Log.class );
-
MinijarFilter mf = new MinijarFilter( mavenProject, log );
mf.finished();
@@ -105,7 +117,7 @@ public class MinijarFilterTest
}
- private MavenProject mockProject( File file )
+ private MavenProject mockProject( File outputDirectory, File file,
String... classPathElements )
{
MavenProject mavenProject = mock( MavenProject.class );
@@ -129,17 +141,29 @@ public class MinijarFilterTest
when( mavenProject.getArtifact().getFile() ).thenReturn( file );
- return mavenProject;
+ Build build = new Build();
+ build.setOutputDirectory( outputDirectory.toString() );
+
+ List<String> classpath = new ArrayList<>();
+ classpath.add( outputDirectory.toString() );
+ if ( file != null )
+ {
+ classpath.add(file.toString());
+ }
+ classpath.addAll( Arrays.asList( classPathElements ) );
+ when( mavenProject.getBuild() ).thenReturn( build );
+ try {
+
when(mavenProject.getRuntimeClasspathElements()).thenReturn(classpath);
+ } catch (DependencyResolutionRequiredException e) {
+ fail("Encountered unexpected exception: " +
e.getClass().getSimpleName() + ": " + e.getMessage());
+ }
+ return mavenProject;
}
@Test
public void finsishedShouldProduceMessageForClassesTotalNonZero()
{
- ArgumentCaptor<CharSequence> logCaptor = ArgumentCaptor.forClass(
CharSequence.class );
-
- Log log = mock( Log.class );
-
MinijarFilter m = new MinijarFilter( 1, 50, log );
m.finished();
@@ -153,10 +177,6 @@ public class MinijarFilterTest
@Test
public void finishedShouldProduceMessageForClassesTotalZero()
{
- ArgumentCaptor<CharSequence> logCaptor = ArgumentCaptor.forClass(
CharSequence.class );
-
- Log log = mock( Log.class );
-
MinijarFilter m = new MinijarFilter( 0, 0, log );
m.finished();
@@ -166,4 +186,24 @@ public class MinijarFilterTest
assertEquals( "Minimized 0 -> 0", logCaptor.getValue() );
}
+
+ /**
+ * Verify that directories are ignored when scanning the classpath for
JARs containing services,
+ * but warnings are logged instead
+ *
+ * @see <a
href="https://issues.apache.org/jira/browse/MSHADE-366">MSHADE-366</a>
+ */
+ @Test
+ public void removeServicesShouldIgnoreDirectories() throws Exception {
+ String classPathElementToIgnore =
tempFolder.newFolder().getAbsolutePath();
+ MavenProject mockedProject = mockProject( outputDirectory, jarFile,
classPathElementToIgnore );
+
+ new MinijarFilter(mockedProject, log);
+
+ verify( log, times( 1 ) ).warn( logCaptor.capture() );
+
+ assertThat( logCaptor.getValue().toString(), startsWith(
+ "Not a JAR file candidate. Ignoring classpath element '" +
classPathElementToIgnore + "' (" ) );
+ }
+
}