View Javadoc
1   /**
2    *
3    * Copyright 2004 The Apache Software Foundation
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    * http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.codehaus.plexus.archiver.zip;
18  
19  import java.io.ByteArrayInputStream;
20  import java.io.File;
21  import java.io.FileOutputStream;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.OutputStream;
25  import java.io.SequenceInputStream;
26  import java.nio.ByteBuffer;
27  import java.nio.charset.Charset;
28  import java.util.Hashtable;
29  import java.util.Stack;
30  import java.util.concurrent.ExecutionException;
31  import java.util.zip.CRC32;
32  import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
33  import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
34  import org.apache.commons.compress.archivers.zip.ZipEncoding;
35  import org.apache.commons.compress.archivers.zip.ZipEncodingHelper;
36  import org.apache.commons.compress.parallel.InputStreamSupplier;
37  import org.apache.commons.compress.utils.Charsets;
38  import org.codehaus.plexus.archiver.AbstractArchiver;
39  import org.codehaus.plexus.archiver.ArchiveEntry;
40  import org.codehaus.plexus.archiver.Archiver;
41  import org.codehaus.plexus.archiver.ArchiverException;
42  import org.codehaus.plexus.archiver.ResourceIterator;
43  import org.codehaus.plexus.archiver.UnixStat;
44  import org.codehaus.plexus.archiver.exceptions.EmptyArchiveException;
45  import org.codehaus.plexus.archiver.util.ResourceUtils;
46  import org.codehaus.plexus.components.io.functions.SymlinkDestinationSupplier;
47  import org.codehaus.plexus.components.io.resources.PlexusIoResource;
48  import org.codehaus.plexus.util.FileUtils;
49  import org.codehaus.plexus.util.IOUtil;
50  import static org.codehaus.plexus.archiver.util.Streams.bufferedOutputStream;
51  import static org.codehaus.plexus.archiver.util.Streams.fileOutputStream;
52  
53  @SuppressWarnings(
54  {
55      "NullableProblems", "UnusedDeclaration"
56  } )
57  public abstract class AbstractZipArchiver
58      extends AbstractArchiver
59  {
60  
61      private String comment;
62  
63      /**
64       * Encoding to use for filenames, defaults to the platform's
65       * default encoding.
66       */
67      private String encoding = "UTF8";
68  
69      private boolean doCompress = true;
70  
71      private boolean recompressAddedZips = true;
72  
73      private boolean doUpdate = false;
74  
75      // shadow of the above if the value is altered in execute
76      private boolean savedDoUpdate = false;
77  
78      protected String archiveType = "zip";
79  
80      private boolean doFilesonly = false;
81  
82      protected final Hashtable<String, String> entries = new Hashtable<String, String>();
83  
84      protected final AddedDirs addedDirs = new AddedDirs();
85  
86      private static final long EMPTY_CRC = new CRC32().getValue();
87  
88      protected boolean doubleFilePass = false;
89  
90      protected boolean skipWriting = false;
91  
92      /**
93       * @deprecated Use {@link Archiver#setDuplicateBehavior(String)} instead.
94       */
95      protected final String duplicate = Archiver.DUPLICATES_SKIP;
96  
97      /**
98       * true when we are adding new files into the Zip file, as opposed
99       * to adding back the unchanged files
100      */
101     protected boolean addingNewFiles = false;
102 
103     // Renamed version of original file, if it exists
104     private File renamedFile = null;
105 
106     private File zipFile;
107 
108     private boolean success;
109 
110     private ConcurrentJarCreator zOut;
111 
112     protected ZipArchiveOutputStream zipArchiveOutputStream;
113 
114     public String getComment()
115     {
116         return comment;
117     }
118 
119     public void setComment( String comment )
120     {
121         this.comment = comment;
122     }
123 
124     public String getEncoding()
125     {
126         return encoding;
127     }
128 
129     public void setEncoding( String encoding )
130     {
131         this.encoding = encoding;
132     }
133 
134     public void setCompress( boolean compress )
135     {
136         this.doCompress = compress;
137     }
138 
139     public boolean isCompress()
140     {
141         return doCompress;
142     }
143 
144     public boolean isRecompressAddedZips()
145     {
146         return recompressAddedZips;
147     }
148 
149     public void setRecompressAddedZips( boolean recompressAddedZips )
150     {
151         this.recompressAddedZips = recompressAddedZips;
152     }
153 
154     public void setUpdateMode( boolean update )
155     {
156         this.doUpdate = update;
157         savedDoUpdate = doUpdate;
158     }
159 
160     public boolean isInUpdateMode()
161     {
162         return doUpdate;
163     }
164 
165     /**
166      * If true, emulate Sun's jar utility by not adding parent directories;
167      * optional, defaults to false.
168      *
169      * @param f true to emilate sun jar utility
170      */
171     public void setFilesonly( boolean f )
172     {
173         doFilesonly = f;
174     }
175 
176     public boolean isFilesonly()
177     {
178         return doFilesonly;
179     }
180 
181     @Override
182     protected void execute()
183         throws ArchiverException, IOException
184     {
185         if ( !checkForced() )
186         {
187             return;
188         }
189 
190         if ( doubleFilePass )
191         {
192             skipWriting = true;
193             createArchiveMain();
194             skipWriting = false;
195             createArchiveMain();
196         }
197         else
198         {
199             createArchiveMain();
200         }
201 
202         finalizeZipOutputStream( zOut );
203     }
204 
205     protected void finalizeZipOutputStream( ConcurrentJarCreator zOut )
206         throws IOException, ArchiverException
207     {
208     }
209 
210     private void createArchiveMain()
211         throws ArchiverException, IOException
212     {
213         //noinspection deprecation
214         if ( !Archiver.DUPLICATES_SKIP.equals( duplicate ) )
215         {
216             //noinspection deprecation
217             setDuplicateBehavior( duplicate );
218         }
219 
220         ResourceIterator iter = getResources();
221         if ( !iter.hasNext() && !hasVirtualFiles() )
222         {
223             throw new EmptyArchiveException( "archive cannot be empty" );
224         }
225 
226         zipFile = getDestFile();
227 
228         if ( zipFile == null )
229         {
230             throw new ArchiverException( "You must set the destination " + archiveType + "file." );
231         }
232 
233         if ( zipFile.exists() && !zipFile.isFile() )
234         {
235             throw new ArchiverException( zipFile + " isn't a file." );
236         }
237 
238         if ( zipFile.exists() && !zipFile.canWrite() )
239         {
240             throw new ArchiverException( zipFile + " is read-only." );
241         }
242 
243         // Whether or not an actual update is required -
244         // we don't need to update if the original file doesn't exist
245         addingNewFiles = true;
246 
247         if ( doUpdate && !zipFile.exists() )
248         {
249             doUpdate = false;
250             getLogger().debug( "ignoring update attribute as " + archiveType + " doesn't exist." );
251         }
252 
253         success = false;
254 
255         if ( doUpdate )
256         {
257             renamedFile = FileUtils.createTempFile( "zip", ".tmp", zipFile.getParentFile() );
258             renamedFile.deleteOnExit();
259 
260             try
261             {
262                 FileUtils.rename( zipFile, renamedFile );
263             }
264             catch ( SecurityException e )
265             {
266                 getLogger().debug( e.toString() );
267                 throw new ArchiverException(
268                     "Not allowed to rename old file (" + zipFile.getAbsolutePath() + ") to temporary file", e );
269             }
270             catch ( IOException e )
271             {
272                 getLogger().debug( e.toString() );
273                 throw new ArchiverException(
274                     "Unable to rename old file (" + zipFile.getAbsolutePath() + ") to temporary file", e );
275             }
276         }
277 
278         String action = doUpdate ? "Updating " : "Building ";
279 
280         getLogger().info( action + archiveType + ": " + zipFile.getAbsolutePath() );
281 
282         if ( !skipWriting )
283         {
284             zipArchiveOutputStream =
285                 new ZipArchiveOutputStream( bufferedOutputStream( fileOutputStream( zipFile, "zip" ) ) );
286             zipArchiveOutputStream.setEncoding( encoding );
287             zipArchiveOutputStream.setCreateUnicodeExtraFields( this.getUnicodeExtraFieldPolicy() );
288             zipArchiveOutputStream.setMethod(
289                 doCompress ? ZipArchiveOutputStream.DEFLATED : ZipArchiveOutputStream.STORED );
290 
291             zOut = new ConcurrentJarCreator( recompressAddedZips, Runtime.getRuntime().availableProcessors() );
292         }
293         initZipOutputStream( zOut );
294 
295         // Add the new files to the archive.
296         addResources( iter, zOut );
297 
298         // If we've been successful on an update, delete the
299         // temporary file
300         if ( doUpdate )
301         {
302             if ( !renamedFile.delete() )
303             {
304                 getLogger().warn( "Warning: unable to delete temporary file " + renamedFile.getName() );
305             }
306         }
307         success = true;
308     }
309 
310     /**
311      * Gets the {@code UnicodeExtraFieldPolicy} to apply.
312      *
313      * @return {@link ZipArchiveOutputStream.UnicodeExtraFieldPolicy.NEVER}, if the effective encoding is
314      * UTF-8; {@link ZipArchiveOutputStream.UnicodeExtraFieldPolicy.ALWAYS}, if the effective encoding is not
315      * UTF-8.
316      *
317      * @see #getEncoding()
318      */
319     private ZipArchiveOutputStream.UnicodeExtraFieldPolicy getUnicodeExtraFieldPolicy()
320     {
321         // Copied from ZipEncodingHelper.isUTF8()
322         String effectiveEncoding = this.getEncoding();
323 
324         if ( effectiveEncoding == null )
325         {
326             effectiveEncoding = Charset.defaultCharset().name();
327         }
328 
329         boolean utf8 = Charsets.UTF_8.name().equalsIgnoreCase( effectiveEncoding );
330 
331         if ( !utf8 )
332         {
333             for ( String alias : Charsets.UTF_8.aliases() )
334             {
335                 if ( alias.equalsIgnoreCase( effectiveEncoding ) )
336                 {
337                     utf8 = true;
338                     break;
339                 }
340             }
341         }
342 
343         // Using ZipArchiveOutputStream.UnicodeExtraFieldPolicy.NOT_ENCODEABLE as a fallback makes no sense here.
344         // If the encoding is UTF-8 and a name is not encodeable using UTF-8, the Info-ZIP Unicode Path extra field
345         // is not encodeable as well. If the effective encoding is not UTF-8, we always add the extra field. If it is
346         // UTF-8, we never add the extra field.
347         return utf8
348                    ? ZipArchiveOutputStream.UnicodeExtraFieldPolicy.NEVER
349                    : ZipArchiveOutputStream.UnicodeExtraFieldPolicy.ALWAYS;
350 
351     }
352 
353     /**
354      * Add the given resources.
355      *
356      * @param resources the resources to add
357      * @param zOut the stream to write to
358      */
359     @SuppressWarnings(
360     {
361         "JavaDoc"
362     } )
363     protected final void addResources( ResourceIterator resources, ConcurrentJarCreator zOut )
364         throws IOException, ArchiverException
365     {
366         while ( resources.hasNext() )
367         {
368             ArchiveEntry entry = resources.next();
369             String name = entry.getName();
370             name = name.replace( File.separatorChar, '/' );
371 
372             if ( "".equals( name ) )
373             {
374                 continue;
375             }
376 
377             if ( entry.getResource().isDirectory() && !name.endsWith( "/" ) )
378             {
379                 name = name + "/";
380             }
381 
382             addParentDirs( entry, null, name, zOut );
383 
384             if ( entry.getResource().isFile() )
385             {
386                 zipFile( entry, zOut, name );
387             }
388             else
389             {
390                 zipDir( entry.getResource(), zOut, name, entry.getMode(), encoding );
391             }
392         }
393     }
394 
395     /**
396      * Ensure all parent dirs of a given entry have been added.
397      * <p/>
398      * This method is computed in terms of the potentially remapped entry (that may be disconnected from the file system)
399      * we do not *relly* know the entry's connection to the file system so establishing the attributes of the parents can
400      * be impossible and is not really supported.
401      */
402     @SuppressWarnings(
403     {
404         "JavaDoc"
405     } )
406     private void addParentDirs( ArchiveEntry archiveEntry, File baseDir, String entry, ConcurrentJarCreator zOut )
407         throws IOException
408     {
409         if ( !doFilesonly && getIncludeEmptyDirs() )
410         {
411             Stack<String> directories = addedDirs.asStringStack( entry );
412 
413             while ( !directories.isEmpty() )
414             {
415                 String dir = directories.pop();
416                 File f;
417                 if ( baseDir != null )
418                 {
419                     f = new File( baseDir, dir );
420                 }
421                 else
422                 {
423                     f = new File( dir );
424                 }
425                 // the
426                 // At this point we could do something like read the atr
427                 final PlexusIoResource res = new AnonymousResource( f );
428                 zipDir( res, zOut, dir, archiveEntry.getDefaultDirMode(), encoding );
429             }
430         }
431     }
432 
433     /**
434      * Adds a new entry to the archive, takes care of duplicates as well.
435      *
436      * @param in the stream to read data for the entry from.
437      * @param zOut the stream to write to.
438      * @param vPath the name this entry shall have in the archive.
439      * @param lastModified last modification time for the entry.
440      * @param fromArchive the original archive we are copying this
441      * @param symlinkDestination
442      * @param addInParallel Indicates if the entry should be add in parallel.
443      * If set to {@code false} it is added synchronously.
444      * If the entry is symbolic link this parameter is ignored.
445      */
446     @SuppressWarnings(
447     {
448         "JavaDoc"
449     } )
450     protected void zipFile( InputStreamSupplier in, ConcurrentJarCreator zOut, String vPath,
451                             long lastModified,
452                             File fromArchive, int mode, String symlinkDestination, boolean addInParallel )
453         throws IOException, ArchiverException
454     {
455         getLogger().debug( "adding entry " + vPath );
456 
457         entries.put( vPath, vPath );
458 
459         if ( !skipWriting )
460         {
461             ZipArchiveEntry ze = new ZipArchiveEntry( vPath );
462             setTime( ze, lastModified );
463 
464             ze.setMethod( doCompress ? ZipArchiveEntry.DEFLATED : ZipArchiveEntry.STORED );
465             ze.setUnixMode( UnixStat.FILE_FLAG | mode );
466 
467             InputStream payload;
468             if ( ze.isUnixSymlink() )
469             {
470                 final byte[] bytes = encodeArchiveEntry( symlinkDestination, getEncoding() );
471                 payload = new ByteArrayInputStream( bytes );
472                 zOut.addArchiveEntry( ze, createInputStreamSupplier( payload ), true );
473             }
474             else
475             {
476                 zOut.addArchiveEntry( ze, in, addInParallel );
477             }
478         }
479     }
480 
481     /**
482      * Method that gets called when adding from java.io.File instances.
483      * <p>
484      * This implementation delegates to the six-arg version.</p>
485      *
486      * @param entry the file to add to the archive
487      * @param zOut the stream to write to
488      * @param vPath the name this entry shall have in the archive
489      */
490     @SuppressWarnings(
491     {
492         "JavaDoc"
493     } )
494     protected void zipFile( final ArchiveEntry entry, ConcurrentJarCreator zOut, String vPath )
495         throws IOException, ArchiverException
496     {
497         final PlexusIoResource resource = entry.getResource();
498         if ( ResourceUtils.isSame( resource, getDestFile() ) )
499         {
500             throw new ArchiverException( "A zip file cannot include itself" );
501         }
502 
503         final boolean b = entry.getResource() instanceof SymlinkDestinationSupplier;
504         String symlinkTarget = b ? ( (SymlinkDestinationSupplier) entry.getResource() ).getSymlinkDestination() : null;
505         InputStreamSupplier in = new InputStreamSupplier()
506         {
507 
508             @Override
509             public InputStream get()
510             {
511                 try
512                 {
513                     return entry.getInputStream();
514                 }
515                 catch ( IOException e )
516                 {
517                     throw new RuntimeException( e );
518                 }
519             }
520 
521         };
522         try
523         {
524             zipFile( in, zOut, vPath, resource.getLastModified(), null, entry.getMode(), symlinkTarget,
525                      !entry.shouldAddSynchronously() );
526         }
527         catch ( IOException e )
528         {
529             throw new ArchiverException( "IOException when zipping r" + entry.getName() + ": " + e.getMessage(), e );
530         }
531     }
532 
533     private void setTime( java.util.zip.ZipEntry zipEntry, long lastModified )
534     {
535         // Zip archives store file modification times with a
536         // granularity of two seconds, so the times will either be rounded
537         // up or down. If you round down, the archive will always seem
538         // out-of-date when you rerun the task, so the default is to round
539         // up. Rounding up may lead to a different type of problems like
540         // JSPs inside a web archive that seem to be slightly more recent
541         // than precompiled pages, rendering precompilation useless.
542         // plexus-archiver chooses to round up.
543         zipEntry.setTime( lastModified + 1999 );
544 
545         /*   Consider adding extended file stamp support.....
546 
547          X5455_ExtendedTimestamp ts =  new X5455_ExtendedTimestamp();
548          ts.setModifyJavaTime(new Date(lastModified));
549          if (zipEntry.getExtra() != null){
550          // Uh-oh. What do we do now.
551          throw new IllegalStateException("DIdnt expect to see xtradata here ?");
552 
553          }  else {
554          zipEntry.setExtra(ts.getLocalFileDataData());
555          }
556          */
557     }
558 
559     protected void zipDir( PlexusIoResource dir, ConcurrentJarCreator zOut, String vPath, int mode,
560                            String encodingToUse )
561         throws IOException
562     {
563         if ( addedDirs.update( vPath ) )
564         {
565             return;
566         }
567 
568         getLogger().debug( "adding directory " + vPath );
569 
570         if ( !skipWriting )
571         {
572             final boolean isSymlink = dir instanceof SymlinkDestinationSupplier;
573 
574             if ( isSymlink && vPath.endsWith( File.separator ) )
575             {
576                 vPath = vPath.substring( 0, vPath.length() - 1 );
577             }
578 
579             ZipArchiveEntry ze = new ZipArchiveEntry( vPath );
580 
581             /*
582              * ZipOutputStream.putNextEntry expects the ZipEntry to
583              * know its size and the CRC sum before you start writing
584              * the data when using STORED mode - unless it is seekable.
585              *
586              * This forces us to process the data twice.
587              */
588             if ( isSymlink )
589             {
590                 mode = UnixStat.LINK_FLAG | mode;
591             }
592 
593             if ( dir != null && dir.isExisting() )
594             {
595                 setTime( ze, dir.getLastModified() );
596             }
597             else
598             {
599                 // ZIPs store time with a granularity of 2 seconds, round up
600                 setTime( ze, System.currentTimeMillis() );
601             }
602             if ( !isSymlink )
603             {
604                 ze.setSize( 0 );
605                 ze.setMethod( ZipArchiveEntry.STORED );
606                 // This is faintly ridiculous:
607                 ze.setCrc( EMPTY_CRC );
608             }
609             ze.setUnixMode( mode );
610 
611             if ( !isSymlink )
612             {
613                 zOut.addArchiveEntry( ze, createInputStreamSupplier( new ByteArrayInputStream( "".getBytes() ) ), true );
614             }
615             else
616             {
617                 String symlinkDestination = ( (SymlinkDestinationSupplier) dir ).getSymlinkDestination();
618                 final byte[] bytes = encodeArchiveEntry( symlinkDestination, encodingToUse );
619                 ze.setMethod( ZipArchiveEntry.DEFLATED );
620                 zOut.addArchiveEntry( ze, createInputStreamSupplier( new ByteArrayInputStream( bytes ) ), true );
621             }
622         }
623     }
624 
625     private byte[] encodeArchiveEntry( String payload, String encoding )
626         throws IOException
627     {
628         ZipEncoding enc = ZipEncodingHelper.getZipEncoding( encoding );
629         ByteBuffer encodedPayloadByteBuffer = enc.encode( payload );
630         byte[] encodedPayloadBytes = new byte[encodedPayloadByteBuffer.limit()];
631         encodedPayloadByteBuffer.get( encodedPayloadBytes );
632 
633         return encodedPayloadBytes;
634     }
635 
636     protected InputStreamSupplier createInputStreamSupplier( final InputStream inputStream )
637     {
638         return new InputStreamSupplier()
639         {
640 
641             @Override
642             public InputStream get()
643             {
644                 return inputStream;
645             }
646 
647         };
648     }
649 
650     /**
651      * Create an empty zip file
652      *
653      * @param zipFile The file
654      *
655      * @return true for historic reasons
656      */
657     @SuppressWarnings(
658     {
659         "JavaDoc"
660     } )
661     protected boolean createEmptyZip( File zipFile )
662         throws ArchiverException
663     {
664         // In this case using java.util.zip will not work
665         // because it does not permit a zero-entry archive.
666         // Must create it manually.
667         getLogger().info( "Note: creating empty " + archiveType + " archive " + zipFile );
668         OutputStream os = null;
669         try
670         {
671             os = new FileOutputStream( zipFile );
672             // Cf. PKZIP specification.
673             byte[] empty = new byte[ 22 ];
674             empty[0] = 80; // P
675             empty[1] = 75; // K
676             empty[2] = 5;
677             empty[3] = 6;
678             // remainder zeros
679             os.write( empty );
680             os.close();
681             os = null;
682         }
683         catch ( IOException ioe )
684         {
685             throw new ArchiverException( "Could not create empty ZIP archive " + "(" + ioe.getMessage() + ")", ioe );
686         }
687         finally
688         {
689             IOUtil.close( os );
690         }
691         return true;
692     }
693 
694     /**
695      * Do any clean up necessary to allow this instance to be used again.
696      * <p>
697      * When we get here, the Zip file has been closed and all we
698      * need to do is to reset some globals.</p>
699      * <p>
700      * This method will only reset globals that have been changed
701      * during execute(), it will not alter the attributes or nested
702      * child elements. If you want to reset the instance so that you
703      * can later zip a completely different set of files, you must use
704      * the reset method.</p>
705      *
706      * @see #reset
707      */
708     @Override
709     protected void cleanUp()
710         throws IOException
711     {
712         super.cleanUp();
713         addedDirs.clear();
714         entries.clear();
715         addingNewFiles = false;
716         doUpdate = savedDoUpdate;
717         success = false;
718         zOut = null;
719         renamedFile = null;
720         zipFile = null;
721     }
722 
723     /**
724      * Makes this instance reset all attributes to their default
725      * values and forget all children.
726      *
727      * @see #cleanUp
728      */
729     public void reset()
730     {
731         setDestFile( null );
732 //        duplicate = "add";
733         archiveType = "zip";
734         doCompress = true;
735         doUpdate = false;
736         doFilesonly = false;
737         encoding = null;
738     }
739 
740     /**
741      * method for subclasses to override
742      *
743      * @param zOut The output stream
744      */
745     protected void initZipOutputStream( ConcurrentJarCreator zOut )
746         throws ArchiverException, IOException
747     {
748     }
749 
750     /**
751      * method for subclasses to override
752      */
753     @Override
754     public boolean isSupportingForced()
755     {
756         return true;
757     }
758 
759     @Override
760     protected boolean revert( StringBuffer messageBuffer )
761     {
762         int initLength = messageBuffer.length();
763 
764         // delete a bogus ZIP file (but only if it's not the original one)
765         if ( ( !doUpdate || renamedFile != null ) && !zipFile.delete() )
766         {
767             messageBuffer.append( " (and the archive is probably corrupt but I could not delete it)" );
768         }
769 
770         if ( doUpdate && renamedFile != null )
771         {
772             try
773             {
774                 FileUtils.rename( renamedFile, zipFile );
775             }
776             catch ( IOException e )
777             {
778                 messageBuffer.append( " (and I couldn't rename the temporary file " );
779                 messageBuffer.append( renamedFile.getName() );
780                 messageBuffer.append( " back)" );
781             }
782         }
783 
784         return messageBuffer.length() == initLength;
785     }
786 
787     @Override
788     protected void close()
789         throws IOException
790     {
791         // Close the output stream.
792         try
793         {
794             if ( zipArchiveOutputStream != null )
795             {
796                 if ( zOut != null )
797                 {
798                     zOut.writeTo( zipArchiveOutputStream );
799                 }
800                 zipArchiveOutputStream.close();
801             }
802         }
803         catch ( IOException ex )
804         {
805             // If we're in this finally clause because of an
806             // exception, we don't really care if there's an
807             // exception when closing the stream. E.g. if it
808             // throws "ZIP file must have at least one entry",
809             // because an exception happened before we added
810             // any files, then we must swallow this
811             // exception. Otherwise, the error that's reported
812             // will be the close() error, which is not the
813             // real cause of the problem.
814             if ( success )
815             {
816                 throw ex;
817             }
818 
819         }
820         catch ( InterruptedException e )
821         {
822             IOException ex = new IOException( "InterruptedException exception" );
823             ex.initCause( e.getCause() );
824             throw ex;
825         }
826         catch ( ExecutionException e )
827         {
828             IOException ex = new IOException( "Execution exception" );
829             ex.initCause( e.getCause() );
830             throw ex;
831         }
832     }
833 
834     @Override
835     protected String getArchiveType()
836     {
837         return archiveType;
838     }
839 
840 }