|
20 | 20 | import java.io.ByteArrayInputStream;
|
21 | 21 | import java.io.IOException;
|
22 | 22 | import java.io.InputStream;
|
| 23 | +import java.io.SequenceInputStream; |
23 | 24 | import java.util.concurrent.ExecutionException;
|
24 | 25 | import java.util.concurrent.Executors;
|
25 | 26 | import java.util.zip.Deflater;
|
|
28 | 29 | import org.apache.commons.compress.archivers.zip.ScatterZipOutputStream;
|
29 | 30 | import org.apache.commons.compress.archivers.zip.StreamCompressor;
|
30 | 31 | 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; |
31 | 34 | import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
|
32 | 35 | import org.apache.commons.compress.parallel.InputStreamSupplier;
|
33 | 36 | import org.apache.commons.compress.parallel.ScatterGatherBackingStore;
|
34 | 37 | import org.apache.commons.compress.parallel.ScatterGatherBackingStoreSupplier;
|
| 38 | +import org.codehaus.plexus.util.IOUtil; |
| 39 | + |
35 | 40 | import static org.apache.commons.compress.archivers.zip.ZipArchiveEntryRequest.createZipArchiveEntryRequest;
|
36 | 41 |
|
37 | 42 | public class ConcurrentJarCreator
|
38 | 43 | {
|
39 | 44 |
|
| 45 | + private final boolean compressAddedZips; |
| 46 | + |
40 | 47 | private final ScatterZipOutputStream directories;
|
41 | 48 |
|
42 | 49 | private final ScatterZipOutputStream metaInfDir;
|
@@ -77,8 +84,44 @@ public static ScatterZipOutputStream createDeferred(
|
77 | 84 | return new ScatterZipOutputStream( bs, sc );
|
78 | 85 | }
|
79 | 86 |
|
| 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 | + */ |
80 | 99 | public ConcurrentJarCreator( int nThreads ) throws IOException
|
81 | 100 | {
|
| 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; |
82 | 125 | ScatterGatherBackingStoreSupplier defaultSupplier = new DeferredSupplier( 100000000 / nThreads );
|
83 | 126 | directories = createDeferred( defaultSupplier );
|
84 | 127 | manifest = createDeferred( defaultSupplier );
|
@@ -146,11 +189,11 @@ else if ( "META-INF/MANIFEST.MF".equals( zipArchiveEntry.getName() ) )
|
146 | 189 | }
|
147 | 190 | else if ( addInParallel )
|
148 | 191 | {
|
149 |
| - parallelScatterZipCreator.addArchiveEntry( zipArchiveEntry, source ); |
| 192 | + parallelScatterZipCreator.addArchiveEntry( createEntrySupplier( zipArchiveEntry, source ) ); |
150 | 193 | }
|
151 | 194 | else
|
152 | 195 | {
|
153 |
| - synchronousEntries.addArchiveEntry( createZipArchiveEntryRequest( zipArchiveEntry, source ) ); |
| 196 | + synchronousEntries.addArchiveEntry( createEntry( zipArchiveEntry, source ) ); |
154 | 197 | }
|
155 | 198 | }
|
156 | 199 |
|
@@ -195,4 +238,81 @@ public String getStatisticsMessage()
|
195 | 238 | return parallelScatterZipCreator.getStatisticsMessage() + " Zip Close: " + zipCloseElapsed + "ms";
|
196 | 239 | }
|
197 | 240 |
|
| 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 | + |
198 | 318 | }
|
0 commit comments