001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.util.resource; 018 019import java.io.BufferedInputStream; 020import java.io.ByteArrayInputStream; 021import java.io.ByteArrayOutputStream; 022import java.io.FileInputStream; 023import java.io.IOException; 024import java.io.InputStream; 025import java.time.Instant; 026import java.util.zip.ZipEntry; 027import java.util.zip.ZipOutputStream; 028import org.apache.wicket.util.file.File; 029import org.apache.wicket.util.lang.Args; 030import org.apache.wicket.util.lang.Bytes; 031import org.slf4j.Logger; 032import org.slf4j.LoggerFactory; 033 034 035/** 036 * An IResourceStream that ZIPs a directory's contents on the fly 037 * 038 * <p> 039 * <b>NOTE 1.</b> As a future improvement, cache a map of generated ZIP files for every directory 040 * and use a Watcher to detect modifications in this directory. Using ehcache would be good for 041 * that, but it's not in Wicket dependencies yet. <b>No caching of the generated ZIP files is done 042 * yet.</b> 043 * </p> 044 * 045 * <p> 046 * <b>NOTE 2.</b> As a future improvement, implement getLastModified() and request 047 * ResourceStreamRequestTarget to generate Last-Modified and Expires HTTP headers. <b>No HTTP cache 048 * headers are provided yet</b>. See WICKET-385 049 * </p> 050 * 051 * @author <a href="mailto:jbq@apache.org">Jean-Baptiste Quenot</a> 052 */ 053public class ZipResourceStream extends AbstractResourceStream 054{ 055 private static final long serialVersionUID = 1L; 056 057 private static final Logger log = LoggerFactory.getLogger(ZipResourceStream.class); 058 059 private final transient ByteArrayOutputStream bytearray; 060 061 /** 062 * Construct. 063 * 064 * @param dir 065 * The directory where to look for files. The directory itself will not be included 066 * in the ZIP. 067 * @param recursive 068 * If true, all subdirs will be zipped as well 069 */ 070 public ZipResourceStream(final File dir, final boolean recursive) 071 { 072 Args.notNull(dir, "dir"); 073 Args.isTrue(dir.isDirectory(), "Not a directory: '{}'", dir); 074 075 bytearray = new ByteArrayOutputStream(); 076 try 077 { 078 ZipOutputStream out = new ZipOutputStream(bytearray); 079 try 080 { 081 zipDir(dir, out, "", recursive); 082 } finally { 083 out.close(); 084 } 085 } 086 catch (RuntimeException e) 087 { 088 throw e; 089 } 090 catch (Exception e) 091 { 092 throw new RuntimeException(e); 093 } 094 } 095 096 /** 097 * Construct. Until Wicket 1.4-RC3 recursive zip was not supported. In order not to change the 098 * behavior, using this constructor will default to recursive == false. 099 * 100 * @param dir 101 * The directory where to look for files. The directory itself will not be included 102 * in the ZIP. 103 */ 104 public ZipResourceStream(final File dir) 105 { 106 this(dir, false); 107 } 108 109 /** 110 * Recursive method for zipping the contents of a directory including nested directories. 111 * 112 * @param dir 113 * dir to be zipped 114 * @param out 115 * ZipOutputStream to write to 116 * @param path 117 * Path to nested dirs (used in resursive calls) 118 * @param recursive 119 * If true, all subdirs will be zipped as well 120 * @throws IOException 121 */ 122 private static void zipDir(final File dir, final ZipOutputStream out, final String path, 123 final boolean recursive) throws IOException 124 { 125 Args.notNull(dir, "dir"); 126 Args.isTrue(dir.isDirectory(), "Not a directory: '{}'", dir); 127 128 String[] files = dir.list(); 129 130 int BUFFER = 2048; 131 BufferedInputStream origin; 132 byte data[] = new byte[BUFFER]; 133 134 if (files != null) 135 { 136 for (String file : files) 137 { 138 log.debug("Adding: '{}'", file); 139 140 File f = new File(dir, file); 141 if (f.isDirectory()) 142 { 143 if (recursive) 144 { 145 zipDir(f, out, path + f.getName() + "/", recursive); 146 } 147 } else 148 { 149 out.putNextEntry(new ZipEntry(path + f.getName())); 150 151 FileInputStream fi = new FileInputStream(f); 152 origin = new BufferedInputStream(fi, BUFFER); 153 154 try 155 { 156 int count; 157 while ((count = origin.read(data, 0, BUFFER)) != -1) 158 { 159 out.write(data, 0, count); 160 } 161 } finally 162 { 163 origin.close(); 164 } 165 } 166 } 167 } 168 169 if (path.isEmpty()) 170 { 171 out.close(); 172 } 173 } 174 175 /** 176 * @see org.apache.wicket.util.resource.IResourceStream#close() 177 */ 178 @Override 179 public void close() throws IOException 180 { 181 } 182 183 /** 184 * @see org.apache.wicket.util.resource.IResourceStream#getContentType() 185 */ 186 @Override 187 public String getContentType() 188 { 189 return null; 190 } 191 192 /** 193 * @see org.apache.wicket.util.resource.IResourceStream#getInputStream() 194 */ 195 @Override 196 public InputStream getInputStream() throws ResourceStreamNotFoundException 197 { 198 return new ByteArrayInputStream(bytearray.toByteArray()); 199 } 200 201 /** 202 * @see org.apache.wicket.util.resource.AbstractResourceStream#length() 203 */ 204 @Override 205 public Bytes length() 206 { 207 return Bytes.bytes(bytearray.size()); 208 } 209 210 /** 211 * @see org.apache.wicket.util.resource.AbstractResourceStream#lastModifiedTime() 212 */ 213 @Override 214 public Instant lastModifiedTime() 215 { 216 return null; 217 } 218}