View Javadoc

1   /*
2   * Licensed to the Apache Software Foundation (ASF) under one or more
3   * contributor license agreements.  See the NOTICE file distributed with
4   * this work for additional information regarding copyright ownership.
5   * The ASF licenses this file to You under the Apache License, Version 2.0
6   * (the "License"); you may not use this file except in compliance with
7   * the License.  You may obtain a copy of the License at
8   *
9   *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17  package compressionFilters;
18  
19  import java.io.IOException;
20  import java.util.zip.GZIPOutputStream;
21  
22  import javax.servlet.ServletOutputStream;
23  import javax.servlet.http.HttpServletResponse;
24  
25  
26  /**
27   * Implementation of <b>ServletOutputStream</b> that works with
28   * the CompressionServletResponseWrapper implementation.
29   *
30   * @author Amy Roh
31   * @author Dmitri Valdin
32   * @version $Revision: 664175 $, $Date: 2008-06-06 18:43:44 -0400 (Fri, 06 Jun 2008) $
33   */
34  
35  public class CompressionResponseStream
36          extends ServletOutputStream {
37  
38      // ----------------------------------------------------------- Constructors
39  
40  
41      /**
42       * Construct a servlet output stream associated with the specified Response.
43       *
44       * @param response The associated response
45       */
46      public CompressionResponseStream(HttpServletResponse response) throws IOException {
47  
48          super();
49          closed = false;
50          this.response = response;
51          this.output = response.getOutputStream();
52  
53      }
54  
55      // ----------------------------------------------------- Instance Variables
56  
57  
58      /**
59       * The threshold number which decides to compress or not.
60       * Users can configure in web.xml to set it to fit their needs.
61       */
62      protected int compressionThreshold = 0;
63  
64      /**
65       * Debug level
66       */
67      private int debug = 0;
68  
69      /**
70       * The buffer through which all of our output bytes are passed.
71       */
72      protected byte[] buffer = null;
73  
74      /**
75       * The number of data bytes currently in the buffer.
76       */
77      protected int bufferCount = 0;
78  
79      /**
80       * The underlying gzip output stream to which we should write data.
81       */
82      protected GZIPOutputStream gzipstream = null;
83  
84      /**
85       * Has this stream been closed?
86       */
87      protected boolean closed = false;
88  
89      /**
90       * The content length past which we will not write, or -1 if there is
91       * no defined content length.
92       */
93      protected int length = -1;
94  
95      /**
96       * The response with which this servlet output stream is associated.
97       */
98      protected HttpServletResponse response = null;
99  
100     /**
101      * The underlying servket output stream to which we should write data.
102      */
103     protected ServletOutputStream output = null;
104 
105     // --------------------------------------------------------- Public Methods
106 
107     /**
108      * Set debug level
109      */
110     public void setDebugLevel(int debug) {
111         this.debug = debug;
112     }
113 
114 
115     /**
116      * Set the compressionThreshold number and create buffer for this size
117      */
118     protected void setBuffer(int threshold) {
119         compressionThreshold = threshold;
120         buffer = new byte[compressionThreshold];
121         if (debug > 1) {
122             System.out.println("buffer is set to " + compressionThreshold);
123         }
124     }
125 
126     /**
127      * Close this output stream, causing any buffered data to be flushed and
128      * any further output data to throw an IOException.
129      */
130     public void close() throws IOException {
131 
132         if (debug > 1) {
133             System.out.println("close() @ CompressionResponseStream");
134         }
135         if (closed)
136             throw new IOException("This output stream has already been closed");
137 
138         if (gzipstream != null) {
139             flushToGZip();
140             gzipstream.close();
141             gzipstream = null;
142         } else {
143             if (bufferCount > 0) {
144                 if (debug > 2) {
145                     System.out.print("output.write(");
146                     System.out.write(buffer, 0, bufferCount);
147                     System.out.println(")");
148                 }
149                 output.write(buffer, 0, bufferCount);
150                 bufferCount = 0;
151             }
152         }
153 
154         output.close();
155         closed = true;
156 
157     }
158 
159 
160     /**
161      * Flush any buffered data for this output stream, which also causes the
162      * response to be committed.
163      */
164     public void flush() throws IOException {
165 
166         if (debug > 1) {
167             System.out.println("flush() @ CompressionResponseStream");
168         }
169         if (closed) {
170             throw new IOException("Cannot flush a closed output stream");
171         }
172 
173         if (gzipstream != null) {
174             gzipstream.flush();
175         }
176 
177     }
178 
179     public void flushToGZip() throws IOException {
180 
181         if (debug > 1) {
182             System.out.println("flushToGZip() @ CompressionResponseStream");
183         }
184         if (bufferCount > 0) {
185             if (debug > 1) {
186                 System.out.println("flushing out to GZipStream, bufferCount = " + bufferCount);
187             }
188             writeToGZip(buffer, 0, bufferCount);
189             bufferCount = 0;
190         }
191 
192     }
193 
194     /**
195      * Write the specified byte to our output stream.
196      *
197      * @param b The byte to be written
198      * @throws IOException if an input/output error occurs
199      */
200     public void write(int b) throws IOException {
201 
202         if (debug > 1) {
203             System.out.println("write " + b + " in CompressionResponseStream ");
204         }
205         if (closed)
206             throw new IOException("Cannot write to a closed output stream");
207 
208         if (bufferCount >= buffer.length) {
209             flushToGZip();
210         }
211 
212         buffer[bufferCount++] = (byte) b;
213 
214     }
215 
216 
217     /**
218      * Write <code>b.length</code> bytes from the specified byte array
219      * to our output stream.
220      *
221      * @param b The byte array to be written
222      * @throws IOException if an input/output error occurs
223      */
224     public void write(byte b[]) throws IOException {
225 
226         write(b, 0, b.length);
227 
228     }
229 
230 
231     /**
232      * Write <code>len</code> bytes from the specified byte array, starting
233      * at the specified offset, to our output stream.
234      *
235      * @param b   The byte array containing the bytes to be written
236      * @param off Zero-relative starting offset of the bytes to be written
237      * @param len The number of bytes to be written
238      * @throws IOException if an input/output error occurs
239      */
240     public void write(byte b[], int off, int len) throws IOException {
241 
242         if (debug > 1) {
243             System.out.println("write, bufferCount = " + bufferCount + " len = " + len + " off = " + off);
244         }
245         if (debug > 2) {
246             System.out.print("write(");
247             System.out.write(b, off, len);
248             System.out.println(")");
249         }
250 
251         if (closed)
252             throw new IOException("Cannot write to a closed output stream");
253 
254         if (len == 0)
255             return;
256 
257         // Can we write into buffer ?
258         if (len <= (buffer.length - bufferCount)) {
259             System.arraycopy(b, off, buffer, bufferCount, len);
260             bufferCount += len;
261             return;
262         }
263 
264         // There is not enough space in buffer. Flush it ...
265         flushToGZip();
266 
267         // ... and try again. Note, that bufferCount = 0 here !
268         if (len <= (buffer.length - bufferCount)) {
269             System.arraycopy(b, off, buffer, bufferCount, len);
270             bufferCount += len;
271             return;
272         }
273 
274         // write direct to gzip
275         writeToGZip(b, off, len);
276     }
277 
278     public void writeToGZip(byte b[], int off, int len) throws IOException {
279 
280         if (debug > 1) {
281             System.out.println("writeToGZip, len = " + len);
282         }
283         if (debug > 2) {
284             System.out.print("writeToGZip(");
285             System.out.write(b, off, len);
286             System.out.println(")");
287         }
288         if (gzipstream == null) {
289             if (debug > 1) {
290                 System.out.println("new GZIPOutputStream");
291             }
292             response.addHeader("Content-Encoding", "gzip");
293             gzipstream = new GZIPOutputStream(output);
294         }
295         gzipstream.write(b, off, len);
296 
297     }
298 
299     // -------------------------------------------------------- Package Methods
300 
301 
302     /**
303      * Has this response stream been closed?
304      */
305     public boolean closed() {
306 
307         return (this.closed);
308 
309     }
310 
311 }