001 package org.apache.tapestry.util.io; 002 003 import org.apache.commons.codec.binary.Base64; 004 import org.apache.hivemind.ApplicationRuntimeException; 005 import org.apache.hivemind.HiveMind; 006 import org.apache.hivemind.util.Defense; 007 008 import java.io.*; 009 import java.util.zip.GZIPInputStream; 010 import java.util.zip.GZIPOutputStream; 011 012 /** 013 * Utility class used by {@link org.apache.tapestry.IRequestCycle} to compress {@link org.apache.tapestry.util.IdAllocator} 014 * state. 015 */ 016 public class CompressedDataEncoder { 017 018 /** 019 * Prefix on the MIME encoding that indicates that the encoded data is not encoded. 020 */ 021 022 public static final String BYTESTREAM_PREFIX = "B"; 023 024 /** 025 * Prefix on the MIME encoding that indicates that the encoded data is encoded with GZIP. 026 */ 027 028 public static final String GZIP_BYTESTREAM_PREFIX = "Z"; 029 030 private CompressedDataEncoder() {} 031 032 /** 033 * Encodes the given string into a compressed string representation that can later be decoded. 034 * 035 * @param input 036 * String input to compress and encode into a persistable form. 037 * 038 * @return encoded string (possibly empty, but not null) 039 */ 040 public static String encodeString(String input) 041 { 042 Defense.notNull(input, "input"); 043 044 if (HiveMind.isBlank(input)) 045 return ""; 046 047 try { 048 ByteArrayOutputStream bosPlain = new ByteArrayOutputStream(); 049 ByteArrayOutputStream bosCompressed = new ByteArrayOutputStream(); 050 051 GZIPOutputStream gos = new GZIPOutputStream(bosCompressed); 052 TeeOutputStream tos = new TeeOutputStream(bosPlain, gos); 053 ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(tos)); 054 055 oos.writeUTF(input); 056 057 oos.close(); 058 059 boolean useCompressed = bosCompressed.size() < bosPlain.size(); 060 061 byte[] data = useCompressed ? bosCompressed.toByteArray() : bosPlain.toByteArray(); 062 063 byte[] encoded = Base64.encodeBase64(data); 064 065 String prefix = useCompressed ? GZIP_BYTESTREAM_PREFIX : BYTESTREAM_PREFIX; 066 067 return prefix + new String(encoded); 068 } 069 catch (Exception ex) { 070 throw new ApplicationRuntimeException(IoMessages.encodeFailure(input, ex), ex); 071 } 072 } 073 074 /** 075 * Takes a string with an encoded and compressed input as produced by {@link #encodeString(String)} , and converts it back 076 * into the original String representation. 077 * 078 * @param input 079 * The data to un-encode, which should be equivalent to the same that 080 * was passed in to {@link #encodeString(String)}. 081 * 082 * @return The decoded string data. 083 */ 084 public static String decodeString(String input) 085 { 086 if (HiveMind.isBlank(input)) 087 return ""; 088 089 String prefix = input.substring(0, 1); 090 091 if (!(prefix.equals(BYTESTREAM_PREFIX) || prefix.equals(GZIP_BYTESTREAM_PREFIX))) 092 throw new ApplicationRuntimeException(IoMessages.unknownPrefix(prefix)); 093 094 try { 095 // Strip off the prefix, feed that in as a MIME stream. 096 097 byte[] decoded = Base64.decodeBase64(input.substring(1).getBytes()); 098 099 InputStream is = new ByteArrayInputStream(decoded); 100 101 if (prefix.equals(GZIP_BYTESTREAM_PREFIX)) 102 is = new GZIPInputStream(is); 103 104 // I believe this is more efficient; the buffered input stream should ask the 105 // GZIP stream for large blocks of un-gzipped bytes, with should be more efficient. 106 // The object input stream will probably be looking for just a few bytes at 107 // a time. We use a resolving object input stream that knows how to find 108 // classes not normally acessible. 109 110 ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(is)); 111 112 String result = ois.readUTF(); 113 114 is.close(); 115 116 return result; 117 } 118 catch (Exception ex) { 119 throw new ApplicationRuntimeException(IoMessages.decodeFailure(ex), ex); 120 } 121 } 122 }