Skip to content

Commit 71256f5

Browse files
plamentotevmichael-o
authored andcommitted
Fix setRecompressAddedZips has no effect on the resulting archive
To check if a given entry is zip file or not the first four bit should be read. There is an issue when entries are being added asynchronously. Because it's faster to submit entries than to compress them, input streams are opened faster than they are closed. Eventually this could lead to too many open files error. There was a workaround for this issue. Wrapper around the `InputStreamSupplier` so the first four bytes are read when the entry is compressed and not when it's submitted. That solves the too many open files problem, but unfortunately has no effect on the resulting archive. The compression method is determined when the entry is submitted so changing it afterwards has no effect. Use the newly introduced `ZipArchiveEntryRequestSupplier` to supply the whole `ZipArchiveEntryRequest` (including the compression method) when the entry is compressed. This fixes #53 and closes #55
1 parent 9351c12 commit 71256f5

File tree

4 files changed

+175
-49
lines changed

4 files changed

+175
-49
lines changed

src/main/java/org/codehaus/plexus/archiver/jar/JarArchiver.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,8 @@ protected boolean createEmptyZip( File zipFile )
569569
{
570570
zipArchiveOutputStream.setMethod( ZipArchiveOutputStream.STORED );
571571
}
572-
ConcurrentJarCreator ps = new ConcurrentJarCreator( Runtime.getRuntime().availableProcessors() );
572+
ConcurrentJarCreator ps =
573+
new ConcurrentJarCreator( isRecompressAddedZips(), Runtime.getRuntime().availableProcessors() );
573574
initZipOutputStream( ps );
574575
finalizeZipOutputStream( ps );
575576
}

src/main/java/org/codehaus/plexus/archiver/zip/AbstractZipArchiver.java

+2-46
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ private void createArchiveMain()
322322
zipArchiveOutputStream.setMethod(
323323
doCompress ? ZipArchiveOutputStream.DEFLATED : ZipArchiveOutputStream.STORED );
324324

325-
zOut = new ConcurrentJarCreator( Runtime.getRuntime().availableProcessors() );
325+
zOut = new ConcurrentJarCreator( recompressAddedZips, Runtime.getRuntime().availableProcessors() );
326326
}
327327
initZipOutputStream( zOut );
328328

@@ -508,21 +508,11 @@ protected void zipFile( InputStreamSupplier in, ConcurrentJarCreator zOut, Strin
508508
}
509509
else
510510
{
511-
zOut.addArchiveEntry( ze, wrappedRecompressor( ze, in ), addInParallel );
511+
zOut.addArchiveEntry( ze, in, addInParallel );
512512
}
513513
}
514514
}
515515

516-
private InputStream maybeSequence( byte[] header, int hdrBytes, InputStream in )
517-
{
518-
return hdrBytes > 0 ? new SequenceInputStream( new ByteArrayInputStream( header, 0, hdrBytes ), in ) : in;
519-
}
520-
521-
private boolean isZipHeader( byte[] header )
522-
{
523-
return header[0] == 0x50 && header[1] == 0x4b && header[2] == 3 && header[3] == 4;
524-
}
525-
526516
/**
527517
* Method that gets called when adding from java.io.File instances.
528518
* <p/>
@@ -661,40 +651,6 @@ protected void zipDir( PlexusIoResource dir, ConcurrentJarCreator zOut, String v
661651
}
662652
}
663653

664-
private InputStreamSupplier wrappedRecompressor( final ZipArchiveEntry ze, final InputStreamSupplier other )
665-
{
666-
667-
return new InputStreamSupplier()
668-
{
669-
670-
@Override
671-
public InputStream get()
672-
{
673-
InputStream is = other.get();
674-
byte[] header = new byte[ 4 ];
675-
try
676-
{
677-
int read = is.read( header );
678-
boolean compressThis = doCompress;
679-
if ( !recompressAddedZips && isZipHeader( header ) )
680-
{
681-
compressThis = false;
682-
}
683-
684-
ze.setMethod( compressThis ? ZipArchiveEntry.DEFLATED : ZipArchiveEntry.STORED );
685-
686-
return maybeSequence( header, read, is );
687-
}
688-
catch ( IOException e )
689-
{
690-
throw new RuntimeException( e );
691-
}
692-
693-
}
694-
695-
};
696-
}
697-
698654
protected InputStreamSupplier createInputStreamSupplier( final InputStream inputStream )
699655
{
700656
return new InputStreamSupplier()

src/main/java/org/codehaus/plexus/archiver/zip/ConcurrentJarCreator.java

+122-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.io.ByteArrayInputStream;
2121
import java.io.IOException;
2222
import java.io.InputStream;
23+
import java.io.SequenceInputStream;
2324
import java.util.concurrent.ExecutionException;
2425
import java.util.concurrent.Executors;
2526
import java.util.zip.Deflater;
@@ -28,15 +29,21 @@
2829
import org.apache.commons.compress.archivers.zip.ScatterZipOutputStream;
2930
import org.apache.commons.compress.archivers.zip.StreamCompressor;
3031
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
32+
import org.apache.commons.compress.archivers.zip.ZipArchiveEntryRequest;
33+
import org.apache.commons.compress.archivers.zip.ZipArchiveEntryRequestSupplier;
3134
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
3235
import org.apache.commons.compress.parallel.InputStreamSupplier;
3336
import org.apache.commons.compress.parallel.ScatterGatherBackingStore;
3437
import org.apache.commons.compress.parallel.ScatterGatherBackingStoreSupplier;
38+
import org.codehaus.plexus.util.IOUtil;
39+
3540
import static org.apache.commons.compress.archivers.zip.ZipArchiveEntryRequest.createZipArchiveEntryRequest;
3641

3742
public class ConcurrentJarCreator
3843
{
3944

45+
private final boolean compressAddedZips;
46+
4047
private final ScatterZipOutputStream directories;
4148

4249
private final ScatterZipOutputStream metaInfDir;
@@ -77,8 +84,44 @@ public static ScatterZipOutputStream createDeferred(
7784
return new ScatterZipOutputStream( bs, sc );
7885
}
7986

87+
/**
88+
* Creates a new {@code ConcurrentJarCreator} instance.
89+
* <p/>
90+
* {@code ConcurrentJarCreator} creates zip files using several concurrent threads.
91+
* <p/>
92+
* This constructor has the same effect as
93+
* {@link #ConcurrentJarCreator(boolean, int) ConcurrentJarCreator(true, nThreads) }
94+
*
95+
* @param nThreads The number of concurrent thread used to create the archive
96+
*
97+
* @throws IOException
98+
*/
8099
public ConcurrentJarCreator( int nThreads ) throws IOException
81100
{
101+
this( true, nThreads );
102+
}
103+
104+
/**
105+
* Creates a new {@code ConcurrentJarCreator} instance.
106+
* <p/>
107+
* {@code ConcurrentJarCreator} creates zip files using several concurrent threads.
108+
* Entries that are already zip file could be just stored or compressed again.
109+
*
110+
* @param compressAddedZips Indicates if entries that are zip files should be compressed.
111+
* If set to {@code false} entries that are zip files will be added using
112+
* {@link ZipEntry#STORED} method.
113+
* If set to {@code true} entries that are zip files will be added using
114+
* the compression method indicated by the {@code ZipArchiveEntry} passed
115+
* to {@link #addArchiveEntry(ZipArchiveEntry, InputStreamSupplier, boolean)}.
116+
* The compression method for all entries that are not zip files will not be changed
117+
* regardless of the value of this parameter
118+
* @param nThreads The number of concurrent thread used to create the archive
119+
*
120+
* @throws IOException
121+
*/
122+
public ConcurrentJarCreator( boolean compressAddedZips, int nThreads ) throws IOException
123+
{
124+
this.compressAddedZips = compressAddedZips;
82125
ScatterGatherBackingStoreSupplier defaultSupplier = new DeferredSupplier( 100000000 / nThreads );
83126
directories = createDeferred( defaultSupplier );
84127
manifest = createDeferred( defaultSupplier );
@@ -146,11 +189,11 @@ else if ( "META-INF/MANIFEST.MF".equals( zipArchiveEntry.getName() ) )
146189
}
147190
else if ( addInParallel )
148191
{
149-
parallelScatterZipCreator.addArchiveEntry( zipArchiveEntry, source );
192+
parallelScatterZipCreator.addArchiveEntry( createEntrySupplier( zipArchiveEntry, source ) );
150193
}
151194
else
152195
{
153-
synchronousEntries.addArchiveEntry( createZipArchiveEntryRequest( zipArchiveEntry, source ) );
196+
synchronousEntries.addArchiveEntry( createEntry( zipArchiveEntry, source ) );
154197
}
155198
}
156199

@@ -195,4 +238,81 @@ public String getStatisticsMessage()
195238
return parallelScatterZipCreator.getStatisticsMessage() + " Zip Close: " + zipCloseElapsed + "ms";
196239
}
197240

241+
private ZipArchiveEntryRequestSupplier createEntrySupplier( final ZipArchiveEntry zipArchiveEntry,
242+
final InputStreamSupplier inputStreamSupplier )
243+
{
244+
245+
return new ZipArchiveEntryRequestSupplier()
246+
{
247+
248+
@Override
249+
public ZipArchiveEntryRequest get()
250+
{
251+
try
252+
{
253+
return createEntry( zipArchiveEntry, inputStreamSupplier );
254+
}
255+
catch ( IOException e )
256+
{
257+
throw new RuntimeException( e );
258+
}
259+
}
260+
261+
};
262+
}
263+
264+
private ZipArchiveEntryRequest createEntry( final ZipArchiveEntry zipArchiveEntry,
265+
final InputStreamSupplier inputStreamSupplier ) throws IOException
266+
{
267+
// if we re-compress the zip files there is no need to look at the input stream
268+
269+
if ( compressAddedZips )
270+
{
271+
return createZipArchiveEntryRequest( zipArchiveEntry, inputStreamSupplier );
272+
}
273+
274+
// otherwise we should inspect the first four bites to see if the input stream is zip file or not
275+
276+
InputStream is = inputStreamSupplier.get();
277+
byte[] header = new byte[4];
278+
try
279+
{
280+
int read = is.read( header );
281+
int compressionMethod = zipArchiveEntry.getMethod();
282+
if ( isZipHeader( header ) ) {
283+
compressionMethod = ZipEntry.STORED;
284+
}
285+
286+
zipArchiveEntry.setMethod( compressionMethod );
287+
288+
return createZipArchiveEntryRequest( zipArchiveEntry, prependBytesToStream( header, read, is ) );
289+
}
290+
catch ( IOException e )
291+
{
292+
IOUtil.close( is );
293+
throw e;
294+
}
295+
}
296+
297+
private boolean isZipHeader( byte[] header )
298+
{
299+
return header[0] == 0x50 && header[1] == 0x4b && header[2] == 3 && header[3] == 4;
300+
}
301+
302+
private InputStreamSupplier prependBytesToStream( final byte[] bytes, final int len, final InputStream stream )
303+
{
304+
return new InputStreamSupplier() {
305+
306+
@Override
307+
public InputStream get()
308+
{
309+
return len > 0
310+
? new SequenceInputStream( new ByteArrayInputStream( bytes, 0, len ), stream )
311+
: stream;
312+
}
313+
314+
};
315+
316+
}
317+
198318
}

src/test/java/org/codehaus/plexus/archiver/zip/ZipArchiverTest.java

+49
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.io.ByteArrayInputStream;
2727
import java.io.ByteArrayOutputStream;
2828
import java.io.File;
29+
import java.io.FileInputStream;
2930
import java.io.FileWriter;
3031
import java.io.IOException;
3132
import java.io.InputStream;
@@ -341,6 +342,54 @@ public void testCreateArchive()
341342
createArchive( archiver );
342343
}
343344

345+
public void testRecompressAddedZips() throws Exception
346+
{
347+
// check that by default the zip archives are re-compressed
348+
349+
final File zipFileRecompress = getTestFile( "target/output/recompress-added-zips.zip" );
350+
final ZipArchiver zipArchiverRecompress = getZipArchiver( zipFileRecompress );
351+
zipArchiverRecompress.addDirectory( getTestFile( "src/test/jars" ) );
352+
FileUtils.removePath( zipFileRecompress.getPath() );
353+
zipArchiverRecompress.createArchive();
354+
355+
final ZipFile zfRecompress = new ZipFile( zipFileRecompress );
356+
assertEquals( ZipEntry.DEFLATED, zfRecompress.getEntry( "test.zip" ).getMethod() );
357+
assertEquals( ZipEntry.DEFLATED, zfRecompress.getEntry( "test.jar" ).getMethod() );
358+
assertEquals( ZipEntry.DEFLATED, zfRecompress.getEntry( "test.rar" ).getMethod() );
359+
assertEquals( ZipEntry.DEFLATED, zfRecompress.getEntry( "test.tar.gz" ).getMethod() );
360+
zfRecompress.close();
361+
362+
// make sure the zip files are not re-compressed when recompressAddedZips is set to false
363+
364+
final File zipFileDontRecompress = getTestFile( "target/output/dont-recompress-added-zips.zip" );
365+
ZipArchiver zipArchiver = getZipArchiver( zipFileDontRecompress );
366+
zipArchiver.addDirectory( getTestFile( "src/test/jars" ) );
367+
zipArchiver.setRecompressAddedZips( false );
368+
FileUtils.removePath( zipFileDontRecompress.getPath() );
369+
zipArchiver.createArchive();
370+
371+
final ZipFile zfDontRecompress = new ZipFile( zipFileDontRecompress );
372+
final ZipArchiveEntry zipEntry = zfDontRecompress.getEntry( "test.zip" );
373+
final ZipArchiveEntry jarEntry = zfDontRecompress.getEntry( "test.jar" );
374+
final ZipArchiveEntry rarEntry = zfDontRecompress.getEntry( "test.rar" );
375+
final ZipArchiveEntry tarEntry = zfDontRecompress.getEntry( "test.tar.gz" );
376+
// check if only zip files are not compressed...
377+
assertEquals( ZipEntry.STORED, zipEntry.getMethod() );
378+
assertEquals( ZipEntry.STORED, jarEntry.getMethod() );
379+
assertEquals( ZipEntry.STORED, rarEntry.getMethod() );
380+
assertEquals( ZipEntry.DEFLATED, tarEntry.getMethod() );
381+
// ...and no file is corrupted in the process
382+
assertTrue( IOUtil.contentEquals( new FileInputStream( getTestFile( "src/test/jars/test.zip" ) ),
383+
zfDontRecompress.getInputStream( zipEntry ) ) );
384+
assertTrue( IOUtil.contentEquals( new FileInputStream( getTestFile( "src/test/jars/test.jar" ) ),
385+
zfDontRecompress.getInputStream( jarEntry ) ) );
386+
assertTrue( IOUtil.contentEquals( new FileInputStream( getTestFile( "src/test/jars/test.rar" ) ),
387+
zfDontRecompress.getInputStream( rarEntry ) ) );
388+
assertTrue( IOUtil.contentEquals( new FileInputStream( getTestFile( "src/test/jars/test.tar.gz" ) ),
389+
zfDontRecompress.getInputStream( tarEntry ) ) );
390+
zfDontRecompress.close();
391+
}
392+
344393
public void testAddArchivedFileSet()
345394
throws Exception
346395
{

0 commit comments

Comments
 (0)