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 <c>RestServlets</c> using the <c>responseHandlers</c> 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   private static final long serialVersionUID = 1L;
035
036   /**
037    * The name of the zip file.
038    */
039   public final String fileName;
040
041   @Header("Content-Type")
042   @Override /* Streamable */
043   public MediaType getMediaType() {
044      return MediaType.forString("application/zip");
045   }
046
047   /**
048    * Returns the value for the <c>Content-Disposition</c> header.
049    *
050    * @return The value for the <c>Content-Disposition</c> header.
051    */
052   @Header("Content-Disposition")
053   public String getContentDisposition() {
054      return "attachment;filename=" + fileName;
055   }
056
057   @ResponseBody
058   @Override /* Streamable */
059   public void streamTo(OutputStream os) throws IOException {
060      try (ZipOutputStream zos = new ZipOutputStream(os)) {
061         for (ZipFileEntry e : this)
062            e.write(zos);
063      }
064      os.flush();
065   }
066
067   /**
068    * Constructor.
069    *
070    * @param fileName The file name of the zip file to create.
071    */
072   public ZipFileList(String fileName) {
073      this.fileName = fileName;
074   }
075
076   /**
077    * Add an entry to this list.
078    *
079    * @param e The zip file entry.
080    * @return This object (for method chaining).
081    */
082   public ZipFileList append(ZipFileEntry e) {
083      add(e);
084      return this;
085   }
086
087   /**
088    * Interface for ZipFileList entries.
089    */
090   public static interface ZipFileEntry {
091      /**
092       * Write this entry to the specified output stream.
093       *
094       * @param zos The output stream to write to.
095       * @throws IOException Thrown by underlying stream.
096       */
097      void write(ZipOutputStream zos) throws IOException;
098   }
099
100   /**
101    * ZipFileList entry for File entry types.
102    */
103   public static class FileEntry implements ZipFileEntry {
104
105      /** The root file to base the entry paths on. */
106      protected File root;
107
108      /** The file being zipped. */
109      protected File file;
110
111      /**
112       * Constructor.
113       *
114       * @param root The root file that represents the base path.
115       * @param file The file to add to the zip file.
116       */
117      public FileEntry(File root, File file) {
118         this.root = root;
119         this.file = file;
120      }
121
122      /**
123       * Constructor.
124       *
125       * @param file The file to add to the zip file.
126       */
127      public FileEntry(File file) {
128         this.file = file;
129         this.root = (file.isDirectory() ? file : file.getParentFile());
130      }
131
132      @Override /* ZipFileEntry */
133      public void write(ZipOutputStream zos) throws IOException {
134         addFile(zos, file);
135      }
136
137      /**
138       * Subclasses can override this method to customize which files get added to a zip file.
139       *
140       * @param f The file being added to the zip file.
141       * @return Always returns <jk>true</jk>.
142       */
143      public boolean doAdd(File f) {
144         return true;
145      }
146
147      /**
148       * Adds the specified file to the specified output stream.
149       *
150       * @param zos The output stream.
151       * @param f The file to add.
152       * @throws IOException Thrown by underlying stream.
153       */
154      protected void addFile(ZipOutputStream zos, File f) throws IOException {
155         if (doAdd(f)) {
156            if (f.isDirectory()) {
157               File[] fileList = f.listFiles();
158               if (fileList == null)
159                  throw new IOException(f.toString());
160               for (File fc : fileList)
161                  addFile(zos, fc);
162            } else if (f.canRead()) {
163               String path = f.getAbsolutePath().substring(root.getAbsolutePath().length() + 1).replace('\\', '/');
164               ZipEntry e = new ZipEntry(path);
165               e.setSize(f.length());
166               zos.putNextEntry(e);
167               try (FileInputStream fis = new FileInputStream(f)) {
168                  IOPipe.create(fis, zos).run();
169               }
170            }
171         }
172      }
173   }
174}