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    package compressionFilters;
018    
019    import java.io.IOException;
020    import java.util.zip.GZIPOutputStream;
021    
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: 664175 $, $Date: 2008-06-06 18:43:44 -0400 (Fri, 06 Jun 2008) $
033     */
034    
035    public class CompressionResponseStream
036            extends ServletOutputStream {
037    
038        // ----------------------------------------------------------- Constructors
039    
040    
041        /**
042         * Construct a servlet output stream associated with the specified Response.
043         *
044         * @param response The associated response
045         */
046        public CompressionResponseStream(HttpServletResponse response) throws IOException {
047    
048            super();
049            closed = false;
050            this.response = response;
051            this.output = response.getOutputStream();
052    
053        }
054    
055        // ----------------------------------------------------- Instance Variables
056    
057    
058        /**
059         * The threshold number which decides to compress or not.
060         * Users can configure in web.xml to set it to fit their needs.
061         */
062        protected int compressionThreshold = 0;
063    
064        /**
065         * Debug level
066         */
067        private int debug = 0;
068    
069        /**
070         * The buffer through which all of our output bytes are passed.
071         */
072        protected byte[] buffer = null;
073    
074        /**
075         * The number of data bytes currently in the buffer.
076         */
077        protected int bufferCount = 0;
078    
079        /**
080         * The underlying gzip output stream to which we should write data.
081         */
082        protected GZIPOutputStream gzipstream = null;
083    
084        /**
085         * Has this stream been closed?
086         */
087        protected boolean closed = false;
088    
089        /**
090         * The content length past which we will not write, or -1 if there is
091         * no defined content length.
092         */
093        protected int length = -1;
094    
095        /**
096         * The response with which this servlet output stream is associated.
097         */
098        protected HttpServletResponse response = null;
099    
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    }