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 */
017
018 package compressionFilters;
019
020 import java.io.IOException;
021 import java.util.zip.GZIPOutputStream;
022 import javax.servlet.ServletOutputStream;
023 import javax.servlet.http.HttpServletResponse;
024
025
026 /**
027 * Implementation of <b>ServletOutputStream</b> that works with
028 * the CompressionServletResponseWrapper implementation.
029 *
030 * @author Amy Roh
031 * @author Dmitri Valdin
032 * @version $Revision: 514091 $, $Date: 2007-03-03 01:26:39 -0500 (Sat, 03 Mar 2007) $
033 */
034
035 public class CompressionResponseStream
036 extends ServletOutputStream {
037
038
039 // ----------------------------------------------------------- Constructors
040
041
042 /**
043 * Construct a servlet output stream associated with the specified Response.
044 *
045 * @param response The associated response
046 */
047 public CompressionResponseStream(HttpServletResponse response) throws IOException{
048
049 super();
050 closed = false;
051 this.response = response;
052 this.output = response.getOutputStream();
053
054 }
055
056
057 // ----------------------------------------------------- Instance Variables
058
059
060 /**
061 * The threshold number which decides to compress or not.
062 * Users can configure in web.xml to set it to fit their needs.
063 */
064 protected int compressionThreshold = 0;
065
066 /**
067 * Debug level
068 */
069 private int debug = 0;
070
071 /**
072 * The buffer through which all of our output bytes are passed.
073 */
074 protected byte[] buffer = null;
075
076 /**
077 * The number of data bytes currently in the buffer.
078 */
079 protected int bufferCount = 0;
080
081 /**
082 * The underlying gzip output stream to which we should write data.
083 */
084 protected GZIPOutputStream gzipstream = null;
085
086 /**
087 * Has this stream been closed?
088 */
089 protected boolean closed = false;
090
091 /**
092 * The content length past which we will not write, or -1 if there is
093 * no defined content length.
094 */
095 protected int length = -1;
096
097 /**
098 * The response with which this servlet output stream is associated.
099 */
100 protected HttpServletResponse response = null;
101
102 /**
103 * The underlying servket output stream to which we should write data.
104 */
105 protected ServletOutputStream output = null;
106
107
108 // --------------------------------------------------------- Public Methods
109
110 /**
111 * Set debug level
112 */
113 public void setDebugLevel(int debug) {
114 this.debug = debug;
115 }
116
117
118 /**
119 * Set the compressionThreshold number and create buffer for this size
120 */
121 protected void setBuffer(int threshold) {
122 compressionThreshold = threshold;
123 buffer = new byte[compressionThreshold];
124 if (debug > 1) {
125 System.out.println("buffer is set to "+compressionThreshold);
126 }
127 }
128
129 /**
130 * Close this output stream, causing any buffered data to be flushed and
131 * any further output data to throw an IOException.
132 */
133 public void close() throws IOException {
134
135 if (debug > 1) {
136 System.out.println("close() @ CompressionResponseStream");
137 }
138 if (closed)
139 throw new IOException("This output stream has already been closed");
140
141 if (gzipstream != null) {
142 flushToGZip();
143 gzipstream.close();
144 gzipstream = null;
145 } else {
146 if (bufferCount > 0) {
147 if (debug > 2) {
148 System.out.print("output.write(");
149 System.out.write(buffer, 0, bufferCount);
150 System.out.println(")");
151 }
152 output.write(buffer, 0, bufferCount);
153 bufferCount = 0;
154 }
155 }
156
157 output.close();
158 closed = true;
159
160 }
161
162
163 /**
164 * Flush any buffered data for this output stream, which also causes the
165 * response to be committed.
166 */
167 public void flush() throws IOException {
168
169 if (debug > 1) {
170 System.out.println("flush() @ CompressionResponseStream");
171 }
172 if (closed) {
173 throw new IOException("Cannot flush a closed output stream");
174 }
175
176 if (gzipstream != null) {
177 gzipstream.flush();
178 }
179
180 }
181
182 public void flushToGZip() throws IOException {
183
184 if (debug > 1) {
185 System.out.println("flushToGZip() @ CompressionResponseStream");
186 }
187 if (bufferCount > 0) {
188 if (debug > 1) {
189 System.out.println("flushing out to GZipStream, bufferCount = " + bufferCount);
190 }
191 writeToGZip(buffer, 0, bufferCount);
192 bufferCount = 0;
193 }
194
195 }
196
197 /**
198 * Write the specified byte to our output stream.
199 *
200 * @param b The byte to be written
201 *
202 * @exception IOException if an input/output error occurs
203 */
204 public void write(int b) throws IOException {
205
206 if (debug > 1) {
207 System.out.println("write "+b+" in CompressionResponseStream ");
208 }
209 if (closed)
210 throw new IOException("Cannot write to a closed output stream");
211
212 if (bufferCount >= buffer.length) {
213 flushToGZip();
214 }
215
216 buffer[bufferCount++] = (byte) b;
217
218 }
219
220
221 /**
222 * Write <code>b.length</code> bytes from the specified byte array
223 * to our output stream.
224 *
225 * @param b The byte array to be written
226 *
227 * @exception IOException if an input/output error occurs
228 */
229 public void write(byte b[]) throws IOException {
230
231 write(b, 0, b.length);
232
233 }
234
235
236 /**
237 * Write <code>len</code> bytes from the specified byte array, starting
238 * at the specified offset, to our output stream.
239 *
240 * @param b The byte array containing the bytes to be written
241 * @param off Zero-relative starting offset of the bytes to be written
242 * @param len The number of bytes to be written
243 *
244 * @exception IOException if an input/output error occurs
245 */
246 public void write(byte b[], int off, int len) throws IOException {
247
248 if (debug > 1) {
249 System.out.println("write, bufferCount = " + bufferCount + " len = " + len + " off = " + off);
250 }
251 if (debug > 2) {
252 System.out.print("write(");
253 System.out.write(b, off, len);
254 System.out.println(")");
255 }
256
257 if (closed)
258 throw new IOException("Cannot write to a closed output stream");
259
260 if (len == 0)
261 return;
262
263 // Can we write into buffer ?
264 if (len <= (buffer.length - bufferCount)) {
265 System.arraycopy(b, off, buffer, bufferCount, len);
266 bufferCount += len;
267 return;
268 }
269
270 // There is not enough space in buffer. Flush it ...
271 flushToGZip();
272
273 // ... and try again. Note, that bufferCount = 0 here !
274 if (len <= (buffer.length - bufferCount)) {
275 System.arraycopy(b, off, buffer, bufferCount, len);
276 bufferCount += len;
277 return;
278 }
279
280 // write direct to gzip
281 writeToGZip(b, off, len);
282 }
283
284 public void writeToGZip(byte b[], int off, int len) throws IOException {
285
286 if (debug > 1) {
287 System.out.println("writeToGZip, len = " + len);
288 }
289 if (debug > 2) {
290 System.out.print("writeToGZip(");
291 System.out.write(b, off, len);
292 System.out.println(")");
293 }
294 if (gzipstream == null) {
295 if (debug > 1) {
296 System.out.println("new GZIPOutputStream");
297 }
298 response.addHeader("Content-Encoding", "gzip");
299 gzipstream = new GZIPOutputStream(output);
300 }
301 gzipstream.write(b, off, len);
302
303 }
304
305
306 // -------------------------------------------------------- Package Methods
307
308
309 /**
310 * Has this response stream been closed?
311 */
312 public boolean closed() {
313
314 return (this.closed);
315
316 }
317
318 }