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 <gno...@gmail.com>
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 <jan.mo...@itemis.de>
    Co-authored-by: Alexander Kriegisch <alexan...@kriegisch.name>
---
 .../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 + "' (" ) );
+    }
+
 }

Reply via email to