001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.utils;
014
015import java.io.*;
016import java.util.*;
017import java.util.zip.*;
018
019import org.apache.juneau.*;
020import org.apache.juneau.http.*;
021import org.apache.juneau.http.annotation.*;
022
023/**
024 * Utility class for representing the contents of a zip file as a list of entries whose contents don't resolve until
025 * serialization time.
026 *
027 * <p>
028 * Generally associated with <code>RestServlets</code> using the <code>responseHandlers</code> annotation so that
029 * REST methods can easily create ZIP file responses by simply returning instances of this class.
030 */
031@Response
032public class ZipFileList extends LinkedList<ZipFileList.ZipFileEntry> implements Streamable {
033
034   // TODO - Change to 1L in 8.0
035   private static final long serialVersionUID = 2694299794576160344L;
036
037   /**
038    * The name of the zip file.
039    */
040   public final String fileName;
041
042   @Header("Content-Type")
043   @Override /* Streamable */
044   public MediaType getMediaType() {
045      return MediaType.forString("application/zip");
046   }
047
048   /**
049    * Returns the value for the <code>Content-Disposition</code> header.
050    *
051    * @return The value for the <code>Content-Disposition</code> header.
052    */
053   @Header("Content-Disposition")
054   public String getContentDisposition() {
055      return "attachment;filename=" + fileName;
056   }
057
058   @ResponseBody
059   @Override /* Streamable */
060   public void streamTo(OutputStream os) throws IOException {
061      try (ZipOutputStream zos = new ZipOutputStream(os)) {
062         for (ZipFileEntry e : this)
063            e.write(zos);
064      }
065      os.flush();
066   }
067
068   /**
069    * Constructor.
070    *
071    * @param fileName The file name of the zip file to create.
072    */
073   public ZipFileList(String fileName) {
074      this.fileName = fileName;
075   }
076
077   /**
078    * Add an entry to this list.
079    *
080    * @param e The zip file entry.
081    * @return This object (for method chaining).
082    */
083   public ZipFileList append(ZipFileEntry e) {
084      add(e);
085      return this;
086   }
087
088   /**
089    * Interface for ZipFileList entries.
090    */
091   public static interface ZipFileEntry {
092      /**
093       * Write this entry to the specified output stream.
094       *
095       * @param zos The output stream to write to.
096       * @throws IOException
097       */
098      void write(ZipOutputStream zos) throws IOException;
099   }
100
101   /**
102    * ZipFileList entry for File entry types.
103    */
104   public static class FileEntry implements ZipFileEntry {
105
106      /** The root file to base the entry paths on. */
107      protected File root;
108
109      /** The file being zipped. */
110      protected File file;
111
112      /**
113       * Constructor.
114       *
115       * @param root The root file that represents the base path.
116       * @param file The file to add to the zip file.
117       */
118      public FileEntry(File root, File file) {
119         this.root = root;
120         this.file = file;
121      }
122
123      /**
124       * Constructor.
125       *
126       * @param file The file to add to the zip file.
127       */
128      public FileEntry(File file) {
129         this.file = file;
130         this.root = (file.isDirectory() ? file : file.getParentFile());
131      }
132
133      @Override /* ZipFileEntry */
134      public void write(ZipOutputStream zos) throws IOException {
135         addFile(zos, file);
136      }
137
138      /**
139       * Subclasses can override this method to customize which files get added to a zip file.
140       *
141       * @param f The file being added to the zip file.
142       * @return Always returns <jk>true</jk>.
143       */
144      public boolean doAdd(File f) {
145         return true;
146      }
147
148      /**
149       * Adds the specified file to the specified output stream.
150       *
151       * @param zos The output stream.
152       * @param f The file to add.
153       * @throws IOException
154       */
155      protected void addFile(ZipOutputStream zos, File f) throws IOException {
156         if (doAdd(f)) {
157            if (f.isDirectory()) {
158               File[] fileList = f.listFiles();
159               if (fileList == null)
160                  throw new IOException(f.toString());
161               for (File fc : fileList)
162                  addFile(zos, fc);
163            } else if (f.canRead()) {
164               String path = f.getAbsolutePath().substring(root.getAbsolutePath().length() + 1).replace('\\', '/');
165               ZipEntry e = new ZipEntry(path);
166               e.setSize(f.length());
167               zos.putNextEntry(e);
168               try (FileInputStream fis = new FileInputStream(f)) {
169                  IOPipe.create(fis, zos).run();
170               }
171            }
172         }
173      }
174   }
175}