001: /*
002: License $Id: JoFileTransferCompression.java,v 1.5 2004/04/16 01:30:04 hendriks73 Exp $
003:
004: Copyright (c) 2001-2005 tagtraum industries.
005:
006: LGPL
007: ====
008:
009: jo! is free software; you can redistribute it and/or
010: modify it under the terms of the GNU Lesser General Public
011: License as published by the Free Software Foundation; either
012: version 2.1 of the License, or (at your option) any later version.
013:
014: jo! is distributed in the hope that it will be useful,
015: but WITHOUT ANY WARRANTY; without even the implied warranty of
016: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017: Lesser General Public License for more details.
018:
019: You should have received a copy of the GNU Lesser General Public
020: License along with this library; if not, write to the Free Software
021: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
022:
023: For LGPL see <http://www.fsf.org/copyleft/lesser.txt>
024:
025:
026: Sun license
027: ===========
028:
029: This release contains software by Sun Microsystems. Therefore
030: the following conditions have to be met, too. They apply to the
031: files
032:
033: - lib/mail.jar
034: - lib/activation.jar
035: - lib/jsse.jar
036: - lib/jcert.jar
037: - lib/jaxp.jar
038: - lib/crimson.jar
039: - lib/servlet.jar
040: - lib/jnet.jar
041: - lib/jaas.jar
042: - lib/jaasmod.jar
043:
044: contained in this release.
045:
046: a. Licensee may not modify the Java Platform
047: Interface (JPI, identified as classes contained within the javax
048: package or any subpackages of the javax package), by creating additional
049: classes within the JPI or otherwise causing the addition to or modification
050: of the classes in the JPI. In the event that Licensee creates any
051: Java-related API and distribute such API to others for applet or
052: application development, you must promptly publish broadly, an accurate
053: specification for such API for free use by all developers of Java-based
054: software.
055:
056: b. Software is confidential copyrighted information of Sun and
057: title to all copies is retained by Sun and/or its licensors. Licensee
058: shall not modify, decompile, disassemble, decrypt, extract, or otherwise
059: reverse engineer Software. Software may not be leased, assigned, or
060: sublicensed, in whole or in part. Software is not designed or intended
061: for use in on-line control of aircraft, air traffic, aircraft navigation
062: or aircraft communications; or in the design, construction, operation or
063: maintenance of any nuclear facility. Licensee warrants that it will not
064: use or redistribute the Software for such purposes.
065:
066: c. Software is provided "AS IS," without a warranty
067: of any kind. ALL EXPRESS OR IMPLIED REPRESENTATIONS AND WARRANTIES,
068: INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
069: PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED.
070:
071: d. This License is effective until terminated. Licensee may
072: terminate this License at any time by destroying all copies of Software.
073: This License will terminate immediately without notice from Sun if Licensee
074: fails to comply with any provision of this License. Upon such termination,
075: Licensee must destroy all copies of Software.
076:
077: e. Software, including technical data, is subject to U.S.
078: export control laws, including the U.S. Export Administration Act and its
079: associated regulations, and may be subject to export or import regulations
080: in other countries. Licensee agrees to comply strictly with all such
081: regulations and acknowledges that it has the responsibility to obtain
082: licenses to export, re-export, or import Software. Software may not be
083: downloaded, or otherwise exported or re-exported (i) into, or to a national
084: or resident of, Cuba, Iraq, Iran, North Korea, Libya, Sudan, Syria or any
085: country to which the U.S. has embargoed goods; or (ii) to anyone on the
086: U.S. Treasury Department's list of Specially Designated Nations or the U.S.
087: Commerce Department's Table of Denial Orders.
088:
089:
090: Feedback
091: ========
092:
093: We encourage your feedback and suggestions and want to use your feedback to
094: improve the Software. Send all such feedback to:
095: <feedback@tagtraum.com>
096:
097: For more information on tagtraum industries and jo!
098: please see <http://www.tagtraum.com/>.
099:
100:
101: */
102: package com.tagtraum.jo;
103:
104: import com.tagtraum.framework.http.C_Http;
105: import com.tagtraum.framework.util.PlatformHelper;
106: import com.tagtraum.framework.util.UnSyncStringBuffer;
107:
108: import javax.servlet.http.HttpServletRequest;
109: import javax.servlet.http.HttpServletRequestWrapper;
110: import java.io.*;
111: import java.lang.reflect.Constructor;
112: import java.lang.reflect.InvocationTargetException;
113: import java.util.*;
114: import java.util.zip.GZIPOutputStream;
115: import java.util.zip.ZipOutputStream;
116:
117: /**
118: * Manages the creation and deletion of compressed files.
119: *
120: * @author <a href="mailto:hs@tagtraum.com">Hendrik Schreiber</a>
121: * @version 1.1beta1 $Id: JoFileTransferCompression.java,v 1.5 2004/04/16 01:30:04 hendriks73 Exp $
122: */
123: public class JoFileTransferCompression implements
124: I_JoFileTransferCompression, C_Jo {
125:
126: /**
127: * Source-Version
128: */
129: public static String vcid = "$Id: JoFileTransferCompression.java,v 1.5 2004/04/16 01:30:04 hendriks73 Exp $";
130:
131: /**
132: * The hashtable with registered constructors for the streams to use.
133: * Algorithmname:Streamconstructor.
134: */
135: private Hashtable algorithms;
136:
137: /**
138: * Ordered list of registered algorithms.
139: */
140: private ArrayList algorithmOrder;
141:
142: /**
143: * The set of extensions that should be compressed.
144: */
145: private HashSet extensions;
146:
147: /**
148: * The repository directory name.
149: */
150: private String repository;
151:
152: /**
153: * Cache of compressable files.
154: */
155: protected HashSet compressableFilesCache;
156: private static ResourceBundle localStrings = ResourceBundle
157: .getBundle("com.tagtraum.jo.localStrings");
158:
159: public JoFileTransferCompression() {
160: algorithms = new Hashtable();
161: algorithmOrder = new ArrayList();
162: extensions = new HashSet();
163: compressableFilesCache = new HashSet();
164: }
165:
166: /**
167: * Adds an algorithm. Will throw an IllegalArgumentException if the
168: * algorithm is not supported.
169: */
170: public void addAlgorithm(String anAlgorithm) {
171: anAlgorithm = anAlgorithm.toLowerCase();
172:
173: Class streamClass;
174:
175: if (anAlgorithm.equals("gzip")) {
176: streamClass = GZIPOutputStream.class;
177: } else if (anAlgorithm.equals("zip")) {
178: streamClass = ZipOutputStream.class;
179: } else {
180: throw new IllegalArgumentException(localStrings
181: .getString("algorithm_not_supported")
182: + anAlgorithm);
183: }
184: Class type[] = new Class[] { OutputStream.class };
185:
186: try {
187: Constructor constructor = streamClass.getConstructor(type);
188: algorithms.put(anAlgorithm, constructor);
189: algorithmOrder.add(anAlgorithm);
190: } catch (NoSuchMethodException nsme) {
191: nsme.printStackTrace();
192: }
193: }
194:
195: /**
196: * Removes an algorithm.
197: */
198: public void removeAlgorithm(String anAlgorithm) {
199: algorithms.remove(anAlgorithm);
200: algorithmOrder.remove(anAlgorithm);
201: }
202:
203: /**
204: * Returns a sorted iterator of the registered algorithms.
205: */
206: public Iterator getAlgorithmNames() {
207: return algorithmOrder.iterator();
208: }
209:
210: /**
211: * Returns the OutputStream constructor of the algorithm.
212: */
213: public Constructor getAlgorithmConstructor(String anAlgorithmName) {
214: return (Constructor) algorithms.get(anAlgorithmName);
215: }
216:
217: /**
218: * Registers an extension as suitable for compression.
219: */
220: public void addExtension(String anExtension) {
221: extensions.add(anExtension);
222: }
223:
224: /**
225: * Unregisters an extension.
226: */
227: public void removeExtension(String anExtension) {
228: extensions.remove(anExtension);
229: }
230:
231: /**
232: * Returns an Iterator for the registered extensions.
233: */
234: public Iterator getExtensions() {
235: return extensions.iterator();
236: }
237:
238: /**
239: * Indicates whether the extension is registered.
240: */
241: public boolean containsExtension(String anExtension) {
242: return extensions.contains(anExtension);
243: }
244:
245: /**
246: * Returns the filename of the repository.
247: */
248: public String getRepository() {
249: return repository;
250: }
251:
252: /**
253: * Sets the filename of the repository.
254: */
255: public void setRepository(String aRepositoryDirName) {
256: repository = aRepositoryDirName;
257: }
258:
259: /**
260: * Returns a suitable algorithm for the given request or null,
261: * if there is none.
262: */
263: public String getAlgorithm(String filename, HttpServletRequest req) {
264: // compressing at this level works only if the servlet has not been called from
265: // another servlet, Therefore we have to check...
266: // This is nasty (rik)
267: if (req instanceof HttpServletRequestWrapper
268: || !(req instanceof I_JoServletRequest)) {
269: return null;
270: }
271: String acceptEncoding = req
272: .getHeader(C_Http.C_HTTP_Accept_Encoding);
273: if (acceptEncoding == null) {
274: return null;
275:
276: }
277: String theAlgorithm = null;
278: Loop: for (int i = 0; i < algorithmOrder.size(); i++) {
279: String algName = (String) algorithmOrder.get(i);
280: if (acceptEncoding.indexOf(algName) != -1) {
281: theAlgorithm = algName;
282: break Loop;
283: }
284: }
285: if (theAlgorithm == null) {
286: return null;
287: }
288: if (compressableFilesCache.contains(filename)) {
289: return theAlgorithm;
290: }
291: int i = filename.lastIndexOf('.');
292: if (i == -1 || i + 1 == filename.length()) {
293: return null;
294: }
295: String suffix = filename.substring(i + 1);
296: if (!extensions.contains(suffix)) {
297: return null;
298: }
299: compressableFilesCache.add(filename);
300: return theAlgorithm;
301: }
302:
303: public File getCompressedFile(File file, HttpServletRequest req,
304: long fileLastModified) throws IOException {
305: String algorithm = getAlgorithm(file.toString(), req);
306: if (algorithm == null) {
307: return null;
308: }
309: return getCompressedFile(file, req, fileLastModified, algorithm);
310: }
311:
312: public File getCompressedFile(File file, HttpServletRequest req)
313: throws IOException {
314: String algorithm = getAlgorithm(file.toString(), req);
315: if (algorithm == null) {
316: return null;
317: }
318: return getCompressedFile(file, req, file.lastModified(),
319: algorithm);
320: }
321:
322: public File getCompressedFile(File file, HttpServletRequest req,
323: String algorithm) throws IOException {
324: return getCompressedFile(file, req, file.lastModified(),
325: algorithm);
326: }
327:
328: /**
329: * Returns the compressed file or null.
330: */
331: public File getCompressedFile(File file, HttpServletRequest req,
332: long fileLastModified, String algorithm) throws IOException {
333: UnSyncStringBuffer theRelativePath = new UnSyncStringBuffer(64);
334: String temp = null;
335: File compressedFile;
336:
337: // find out the directory and filenames
338: try {
339: temp = (String) req
340: .getAttribute(C_Request_Attribute_RequestURI);
341: if (temp == null) {
342: temp = req.getRequestURI();
343: }
344: temp = com.tagtraum.framework.util.URLDecoder.decode(req
345: .getRequestURI()); // decoding necessary?
346: } catch (Exception e) {
347: /* ignore */
348: }
349:
350: int idx = temp.lastIndexOf('/');
351:
352: theRelativePath.append(temp.substring(0, idx + 1));
353: theRelativePath.append(file.getName());
354: theRelativePath.append("." + algorithm);
355:
356: compressedFile = new File(repository, PlatformHelper
357: .getOSSpecificPath(theRelativePath.toString())
358: .substring(1));
359: // check if compressed file exists, delete it, if it is old.
360: if (isCurrent(compressedFile, fileLastModified))
361: return compressedFile;
362:
363: File compressedFilePart = new File(compressedFile.toString()
364: + ".part");
365:
366: if (doesPartExist(compressedFilePart))
367: return null;
368:
369: createCompressedFile(file, algorithm, compressedFilePart,
370: compressedFile);
371:
372: return compressedFile;
373: }
374:
375: /**
376: * Compresses the sourceFile and writes the result first to compressedFilePart which is then renamed to compressedFile.
377: *
378: * @param algorithm Algorithm for compressing the file
379: */
380: private void createCompressedFile(File sourceFile,
381: String algorithm, File compressedFilePart,
382: File compressedFile) throws IOException {
383: FileInputStream in = null;
384: OutputStream out = null;
385:
386: try {
387: in = new FileInputStream(sourceFile);
388: Constructor outConstructor = (Constructor) algorithms
389: .get(algorithm);
390: Object args[] = new Object[] { new FileOutputStream(
391: compressedFilePart) };
392: out = (OutputStream) outConstructor.newInstance(args);
393: int len;
394: byte[] buf = new byte[4096];
395: while ((len = in.read(buf)) != -1) {
396: out.write(buf, 0, len);
397: }
398: out.close();
399: out = null;
400: in.close();
401: in = null;
402: compressedFilePart.renameTo(compressedFile);
403: } catch (InstantiationException ie) {
404: ie.printStackTrace();
405: } catch (IllegalAccessException ia) {
406: ia.printStackTrace();
407: } catch (InvocationTargetException ite) {
408: if (ite.getTargetException() instanceof IOException) {
409: throw (IOException) ite.getTargetException();
410: }
411: ite.printStackTrace();
412: } finally {
413: if (in != null) {
414: try {
415: in.close();
416: } catch (IOException ignore) {
417: }
418: }
419:
420: if (out != null) {
421: try {
422: out.close();
423: } catch (IOException ignore) {
424: }
425: }
426: }
427: }
428:
429: /**
430: * Checks if a filepart exists. If it does exist it is deleted if it is more than
431: * 10 min old. If it does not exist, it is created.
432: */
433: private synchronized boolean doesPartExist(File filePart)
434: throws IOException {
435: if (filePart.exists()) {
436: if (filePart.lastModified() < System.currentTimeMillis() - 1000 * 60 * 10) {
437: if (!filePart.delete()) {
438: // what is this for? (rik)
439: return true;
440: }
441: } else {
442: return true;
443: }
444: }
445: // make sure we have a path to it
446: filePart.getParentFile().mkdirs();
447: if (!filePart.createNewFile()) {
448: return true;
449: }
450: return false;
451: }
452:
453: private synchronized boolean isCurrent(File file, long lastModified) {
454: long compressedFileLastModified = file.lastModified();
455: if (compressedFileLastModified != 0) {
456: if (lastModified > compressedFileLastModified) {
457: file.delete();
458: } else {
459: return true;
460: }
461: }
462: return false;
463: }
464:
465: }
|