This is an automated email from the ASF dual-hosted git repository. rmannibucau 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 c798d01 [MSHADE-378] Shade plugin changes the compression level of nested jar… (#73) c798d01 is described below commit c798d01138e9fecdd6422a2a8acce22ca8987924 Author: jenrryyou <flyaway.y...@qq.com> AuthorDate: Sun Nov 22 23:17:56 2020 +0800 [MSHADE-378] Shade plugin changes the compression level of nested jar… (#73) * [MSHADE-378] Shade plugin changes the compression level of nested jar entries * [MSHADE-378] Shade plugin changes the compression level of nested jar entries Co-authored-by: shaoyao <jeremy....@alibaba-inc.com> --- .../apache/maven/plugins/shade/DefaultShader.java | 107 +++++++++++++++++++-- .../maven/plugins/shade/DefaultShaderTest.java | 67 +++++++++++++ 2 files changed, 165 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/apache/maven/plugins/shade/DefaultShader.java b/src/main/java/org/apache/maven/plugins/shade/DefaultShader.java index 6f3c4e6..540eccd 100644 --- a/src/main/java/org/apache/maven/plugins/shade/DefaultShader.java +++ b/src/main/java/org/apache/maven/plugins/shade/DefaultShader.java @@ -21,14 +21,17 @@ package org.apache.maven.plugins.shade; import java.io.BufferedOutputStream; import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; +import java.io.PushbackInputStream; import java.io.Writer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; @@ -42,6 +45,8 @@ import java.util.jar.JarFile; import java.util.jar.JarOutputStream; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.zip.CRC32; +import java.util.zip.ZipEntry; import java.util.zip.ZipException; import org.apache.commons.lang3.StringUtils; @@ -71,6 +76,7 @@ public class DefaultShader extends AbstractLogEnabled implements Shader { + private static final int BUFFER_SIZE = 32 * 1024; public void shade( ShadeRequest shadeRequest ) throws IOException, MojoExecutionException @@ -150,6 +156,73 @@ public class DefaultShader } } + /** + * {@link InputStream} that can peek ahead at zip header bytes. + */ + private static class ZipHeaderPeekInputStream extends PushbackInputStream + { + + private static final byte[] ZIP_HEADER = new byte[] {0x50, 0x4b, 0x03, 0x04}; + + private static final int HEADER_LEN = 4; + + protected ZipHeaderPeekInputStream( InputStream in ) + { + super( in, HEADER_LEN ); + } + + public boolean hasZipHeader() throws IOException + { + final byte[] header = new byte[HEADER_LEN]; + super.read( header, 0, HEADER_LEN ); + super.unread( header ); + return Arrays.equals( header, ZIP_HEADER ); + } + } + + /** + * Data holder for CRC and Size. + */ + private static class CrcAndSize + { + + private final CRC32 crc = new CRC32(); + + private long size; + + CrcAndSize( File file ) throws IOException + { + try ( FileInputStream inputStream = new FileInputStream( file ) ) + { + load( inputStream ); + } + } + + CrcAndSize( InputStream inputStream ) throws IOException + { + load( inputStream ); + } + + private void load( InputStream inputStream ) throws IOException + { + byte[] buffer = new byte[BUFFER_SIZE]; + int bytesRead; + while ( ( bytesRead = inputStream.read( buffer ) ) != -1 ) + { + this.crc.update( buffer, 0, bytesRead ); + this.size += bytesRead; + } + } + + public void setupStoredEntry( JarEntry entry ) + { + entry.setSize( this.size ); + entry.setCompressedSize( this.size ); + entry.setCrc( this.crc.getValue() ); + entry.setMethod( ZipEntry.STORED ); + } + } + private void shadeJars( ShadeRequest shadeRequest, Set<String> resources, List<ResourceTransformer> transformers, RelocatorRemapper remapper, JarOutputStream jos, Multimap<String, File> duplicates ) throws IOException, MojoExecutionException @@ -255,7 +328,7 @@ public class DefaultShader return; } - addResource( resources, jos, mappedName, entry.getTime(), in ); + addResource( resources, jos, mappedName, entry, jarFile ); } else { @@ -584,19 +657,35 @@ public class DefaultShader resources.add( name ); } - private void addResource( Set<String> resources, JarOutputStream jos, String name, long time, - InputStream is ) - throws IOException + private void addResource( Set<String> resources, JarOutputStream jos, String name, JarEntry originalEntry, + JarFile jarFile ) throws IOException { - final JarEntry entry = new JarEntry( name ); + ZipHeaderPeekInputStream inputStream = new ZipHeaderPeekInputStream( jarFile.getInputStream( originalEntry ) ); + try + { + final JarEntry entry = new JarEntry( name ); - entry.setTime( time ); + // We should not change compressed level of uncompressed entries, otherwise JVM can't load these nested jars + if ( inputStream.hasZipHeader() && originalEntry.getMethod() == ZipEntry.STORED ) + { + new CrcAndSize( inputStream ).setupStoredEntry( entry ); + inputStream.close(); + inputStream = new ZipHeaderPeekInputStream( jarFile.getInputStream( originalEntry ) ); + } - jos.putNextEntry( entry ); - IOUtil.copy( is, jos ); + entry.setTime( originalEntry.getTime() ); - resources.add( name ); + jos.putNextEntry( entry ); + + IOUtil.copy( inputStream, jos ); + + resources.add( name ); + } + finally + { + inputStream.close(); + } } static class RelocatorRemapper diff --git a/src/test/java/org/apache/maven/plugins/shade/DefaultShaderTest.java b/src/test/java/org/apache/maven/plugins/shade/DefaultShaderTest.java index 7eedcf5..249258f 100644 --- a/src/test/java/org/apache/maven/plugins/shade/DefaultShaderTest.java +++ b/src/test/java/org/apache/maven/plugins/shade/DefaultShaderTest.java @@ -20,6 +20,7 @@ package org.apache.maven.plugins.shade; */ import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.Field; @@ -33,7 +34,10 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.jar.JarEntry; +import java.util.jar.JarFile; import java.util.jar.JarOutputStream; +import java.util.zip.CRC32; +import java.util.zip.ZipEntry; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.shade.filter.Filter; @@ -45,6 +49,8 @@ import org.apache.maven.plugins.shade.resource.ResourceTransformer; import org.codehaus.plexus.logging.AbstractLogger; import org.codehaus.plexus.logging.Logger; import org.codehaus.plexus.logging.console.ConsoleLogger; +import org.codehaus.plexus.util.IOUtil; +import org.junit.Assert; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.objectweb.asm.ClassReader; @@ -262,6 +268,67 @@ public class DefaultShaderTest } } + @Test + public void testShaderWithNestedJar() throws Exception + { + TemporaryFolder temporaryFolder = new TemporaryFolder(); + + final String innerJarFileName = "inner.jar"; + + temporaryFolder.create(); + File innerJar = temporaryFolder.newFile( innerJarFileName ); + try ( JarOutputStream jos = new JarOutputStream( new FileOutputStream( innerJar ) ) ) + { + jos.putNextEntry( new JarEntry( "foo.txt" ) ); + jos.write( "c1".getBytes( StandardCharsets.UTF_8 ) ); + jos.closeEntry(); + } + + File outerJar = temporaryFolder.newFile( "outer.jar" ); + try ( JarOutputStream jos = new JarOutputStream( new FileOutputStream( outerJar ) ) ) + { + FileInputStream innerStream = new FileInputStream( innerJar ); + byte[] bytes = IOUtil.toByteArray( innerStream, 32 * 1024 ); + innerStream.close(); + writeEntryWithoutCompression( innerJarFileName, bytes, jos ); + } + + + ShadeRequest shadeRequest = new ShadeRequest(); + shadeRequest.setJars( new LinkedHashSet<>( Collections.singleton( outerJar ) ) ); + shadeRequest.setFilters( new ArrayList<Filter>() ); + shadeRequest.setRelocators( new ArrayList<Relocator>() ); + shadeRequest.setResourceTransformers( new ArrayList<ResourceTransformer>() ); + File shadedFile = temporaryFolder.newFile( "shaded.jar" ); + shadeRequest.setUberJar( shadedFile ); + + DefaultShader shader = newShader(); + shader.shade( shadeRequest ); + + JarFile shadedJarFile = new JarFile( shadedFile ); + JarEntry entry = shadedJarFile.getJarEntry( innerJarFileName ); + + //After shading, entry compression method should not be changed. + Assert.assertEquals( entry.getMethod(), ZipEntry.STORED ); + + temporaryFolder.delete(); + } + + private void writeEntryWithoutCompression( String entryName, byte[] entryBytes, JarOutputStream jos ) throws IOException + { + final JarEntry entry = new JarEntry( entryName ); + final int size = entryBytes.length; + final CRC32 crc = new CRC32(); + crc.update( entryBytes, 0, size ); + entry.setSize( size ); + entry.setCompressedSize( size ); + entry.setMethod( ZipEntry.STORED ); + entry.setCrc( crc.getValue() ); + jos.putNextEntry( entry ); + jos.write( entryBytes ); + jos.closeEntry(); + } + private void shaderWithPattern( String shadedPattern, File jar, String[] excludes ) throws Exception {