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    }