001 package org.rakeshv.filters;
002
003 import java.io.IOException;
004 import java.util.zip.DeflaterOutputStream;
005 import java.util.zip.GZIPOutputStream;
006 import java.util.zip.ZipEntry;
007 import java.util.zip.ZipOutputStream;
008 import javax.servlet.ServletOutputStream;
009 import javax.servlet.http.HttpServletResponse;
010
011 /**
012 * A sub-class of <code>ServletOutputStream</code> that is used to
013 * compress the response sent by the server to a client request. The
014 * only compression method supported are those that are available in the
015 * <code>java.util.zip</code> package.
016 *
017 * <p>© Copyright 2003, Rakesh Vidyadharan</p>
018 * @author Rakesh Vidyadharan 2<sup><small>nd</small></sup> September 2003
019 *
020 * @version $Id: CompressionResponseStream.java,v 1.5 2005/10/15 01:10:20 rakesh Exp $
021 */
022 public class CompressionResponseStream extends ServletOutputStream
023 {
024 /**
025 * The underlying gzip output stream to which we should write data.
026 */
027 protected DeflaterOutputStream deflaterStream = null;
028
029 /**
030 * Has this stream been closed?
031 */
032 protected boolean closed = false;
033
034 /**
035 * The response with which this servlet output stream is associated.
036 */
037 protected HttpServletResponse response = null;
038
039 /**
040 * The underlying servlet output stream to which we should write data.
041 */
042 protected ServletOutputStream output = null;
043
044 /**
045 * The type of compression algorithm to use. The supported algorithms
046 * are <code>Deflate, GZIP, Zip</code>.
047 */
048 protected int compressionType = 0;
049
050 /**
051 * Initialise a new instance of this class with the specified
052 * servlet response.
053 *
054 * @param response The response to which the contents are to be
055 * sent.
056 */
057 public CompressionResponseStream( HttpServletResponse response )
058 throws IOException
059 {
060 super();
061 this.response = response;
062 this.output = response.getOutputStream();
063 }
064
065 /**
066 * Over-ridden form of the method. Writes the specified int to the
067 * {@link #deflaterStream} if the stream has not been closed. Uses
068 * the {@link #writeToDeflater( int )} method to write the specified
069 * value to the {@link #deflaterStream}.
070 *
071 * @param value - The integer value that is being written.
072 * @throws IOException - If the stream has already been closed.
073 */
074 public void write( int value ) throws IOException
075 {
076 if ( closed )
077 {
078 throw new IOException("Cannot write to a closed output stream");
079 }
080
081 writeToDeflater( value );
082 }
083
084 /**
085 * Write the specified value to the {@link #deflaterStream}. Checks
086 * to ensure that the {@link #deflaterStream} has been created, and
087 * if not, then creates the appropriate instance of the {@link
088 * #deflaterStream} using the {@link #compressionType} value.
089 *
090 * @param value - The value to write.
091 * @throws IOException - If errors are encountered while trying to
092 * write to the stream.
093 */
094 protected void writeToDeflater( int value ) throws IOException
095 {
096 if ( deflaterStream == null )
097 {
098 createDeflaterStream();
099 }
100
101 deflaterStream.write( value );
102 }
103
104 /**
105 * Over-ridden to ensure that the method just invokes the {@link
106 * #write( byte[], int, int )} method.
107 *
108 * @param buf - The byte buffer from which the data is to
109 * read and written to the {@link #deflaterStream}.
110 * @throws IOException - If errors are encountered while writing
111 * to the stream.
112 */
113 public void write( byte[] buf ) throws IOException
114 {
115 write( buf, 0, buf.length );
116 }
117
118 /**
119 * Over-ridden form of the method. Writes the <code>byte array</code>
120 * to the {@link #deflaterStream} using {@link
121 * #writeToDeflater( byte[], int, int)}.
122 *
123 * @param buf - The byte buffer from which the data is to
124 * read and written to the {@link #deflaterStream}.
125 * @param off - The offset from the start of the buffer from
126 * which to start reading data.
127 * @param len - The length of data that is to be read from the
128 * array.
129 * @throws IOException - If errors are encountered while writing
130 * to the stream.
131 */
132 public void write( byte[] buf, int off, int len ) throws IOException
133 {
134 if ( closed )
135 {
136 throw new IOException( "Cannot write to a closed output stream" );
137 }
138
139 writeToDeflater( buf, off, len );
140 }
141
142 /**
143 * Writes the date from the specified byte array to the {@link
144 * #deflaterStream}. Checks to ensure that the {@link
145 * #deflaterStream} has been created, and if not, then it creates
146 * the appropriate instance of the {@link #deflaterStream} based
147 * upon the value of {@link #compressionType}.
148 *
149 * @param buf - The byte buffer from which the data is to
150 * read and written to the {@link #deflaterStream}.
151 * @param off - The offset from the start of the buffer from
152 * which to start reading data.
153 * @param len - The length of data that is to be read from the
154 * array.
155 * @throws IOException - If errors are encountered while writing
156 * to the stream.
157 */
158 protected void writeToDeflater( byte[] buf, int off, int len )
159 throws IOException
160 {
161 if ( deflaterStream == null )
162 {
163 createDeflaterStream();
164 }
165
166 deflaterStream.write( buf, off, len );
167 }
168
169 /**
170 * Initialise the {@link #deflaterStream} with the appropriate type
171 * of compressed output stream based upon the value in the
172 * {@link #compressionType} instance variable. If the value of
173 * {@link #compressionType} does not match one of the following,
174 * then <code>Deflate</code> compression is used.
175 *
176 * <ol>
177 * <li>{@link CompressionFilter#GZIP_COMPRESSION}
178 * <li>{@link CompressionFilter#ZIP_COMPRESSION}
179 * </ol>
180 *
181 * <p><b>Note:</b> If <code>zip</code> compression is requested, then
182 * the response will contain a <code>ZipEntry</code> object that
183 * contains the data as required by the <code>Zip</code> format.
184 * The <code>file name</code> for the <code>ZipEntry</code> object
185 * is arbitrarily set as the value of <code>System.currentTimeMillis()
186 * </code></p>.
187 *
188 * @throws IOException - If errors are encountered while creating
189 * the stream, or setting its header.
190 */
191 protected void createDeflaterStream() throws IOException
192 {
193 if ( compressionType == CompressionFilter.GZIP_COMPRESSION )
194 {
195 deflaterStream = new GZIPOutputStream( output );
196 response.addHeader( "Content-Encoding", "gzip" );
197 }
198 else if ( compressionType == CompressionFilter.ZIP_COMPRESSION )
199 {
200 deflaterStream = new ZipOutputStream( output );
201 ( (ZipOutputStream) deflaterStream ).putNextEntry( new ZipEntry( String.valueOf( System.currentTimeMillis() ) ) );
202 response.addHeader( "Content-Encoding", "zip" );
203 }
204 else
205 {
206 deflaterStream = new DeflaterOutputStream( output );
207 response.addHeader( "Content-Encoding", "deflate" );
208 }
209 }
210
211 /**
212 * Close this output stream, causing any buffered data to be flushed
213 * and any further output data to throw an IOException.
214 *
215 * @throws IOException - If the stream has already been closed.
216 */
217 public void close() throws IOException
218 {
219 if ( closed )
220 {
221 throw new IOException("This output stream has already been closed");
222 }
223
224 flush();
225
226 if ( deflaterStream != null )
227 {
228 finish();
229 deflaterStream.close();
230 }
231
232 closed = true;
233 }
234
235 /**
236 * Finishes writing compressed data to the output stream without
237 * closing the underlying stream. Use this method when applying
238 * multiple filters in succession to the same output stream.
239 *
240 * @throws IOException - If errors are encountered while writing
241 * to the stream. An exception will be thrown if this method is
242 * invoked after the stream has been closed.
243 */
244 public void finish() throws IOException
245 {
246 if ( closed )
247 {
248 throw new IOException("This output stream has already been closed");
249 }
250
251 if ( deflaterStream instanceof ZipOutputStream )
252 {
253 ( (ZipOutputStream) deflaterStream ).closeEntry();
254 deflaterStream.finish();
255 }
256 else
257 {
258 deflaterStream.finish();
259 }
260 }
261
262 /**
263 * Flushes the underlying servlet output stream. Flushes any
264 * unwritten content in the buffer to the servlet output
265 * stream.
266 *
267 * @throws IOException - If the stream has already been closed.
268 */
269 public void flush() throws IOException
270 {
271 if ( closed )
272 {
273 throw new IOException( "Cannot flush a closed output stream" );
274 }
275
276 deflaterStream.flush();
277 }
278
279 /**
280 * Returns {@link #closed}.
281 *
282 * @return boolean - The value/reference of/to closed.
283 */
284 public final boolean getClosed()
285 {
286 return closed;
287 }
288
289 /**
290 * Returns {@link #compressionType}.
291 *
292 * @return int - The value/reference of/to compressionType.
293 */
294 public final int getCompressionType()
295 {
296 return compressionType;
297 }
298
299 /**
300 * Set {@link #compressionType}.
301 *
302 * @param compressionType - The value to set.
303 */
304 public final void setCompressionType( int compressionType )
305 {
306 this.compressionType = compressionType;
307 }
308 }