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