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    
023    import javax.servlet.ServletOutputStream;
024    import javax.servlet.http.HttpServletResponse;
025    
026    
027    /**
028     * Implementation of <b>ServletOutputStream</b> that works with
029     * the CompressionServletResponseWrapper implementation.
030     *
031     * @author Amy Roh
032     * @author Dmitri Valdin
033     * @version $Revision: 664175 $, $Date: 2008-06-06 18:43:44 -0400 (Fri, 06 Jun 2008) $
034     */
035    
036    public class CompressionResponseStream
037            extends ServletOutputStream {
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        // ----------------------------------------------------- Instance Variables
057    
058    
059        /**
060         * The threshold number which decides to compress or not.
061         * Users can configure in web.xml to set it to fit their needs.
062         */
063        protected int compressionThreshold = 0;
064    
065        /**
066         * Debug level
067         */
068        private int debug = 0;
069    
070        /**
071         * The buffer through which all of our output bytes are passed.
072         */
073        protected byte[] buffer = null;
074    
075        /**
076         * The number of data bytes currently in the buffer.
077         */
078        protected int bufferCount = 0;
079    
080        /**
081         * The underlying gzip output stream to which we should write data.
082         */
083        protected GZIPOutputStream gzipstream = null;
084    
085        /**
086         * Has this stream been closed?
087         */
088        protected boolean closed = false;
089    
090        /**
091         * The content length past which we will not write, or -1 if there is
092         * no defined content length.
093         */
094        protected int length = -1;
095    
096        /**
097         * The response with which this servlet output stream is associated.
098         */
099        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    }