001    /*
002    * Copyright 2004 The Apache Software Foundation
003    *
004    * Licensed under the Apache License, Version 2.0 (the "License");
005    * you may not use this file except in compliance with the License.
006    * You may obtain a copy of the License at
007    *
008    *     http://www.apache.org/licenses/LICENSE-2.0
009    *
010    * Unless required by applicable law or agreed to in writing, software
011    * distributed under the License is distributed on an "AS IS" BASIS,
012    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013    * See the License for the specific language governing permissions and
014    * limitations under the License.
015    */
016    package compressionFilters;
017    
018    import java.io.IOException;
019    import java.io.OutputStream;
020    import java.util.zip.GZIPOutputStream;
021    import javax.servlet.ServletOutputStream;
022    import javax.servlet.http.HttpServletResponse;
023    
024    
025    /**
026     * Implementation of <b>ServletOutputStream</b> that works with
027     * the CompressionServletResponseWrapper implementation.
028     *
029     * @author Amy Roh
030     * @author Dmitri Valdin
031     * @version $Revision: 267129 $, $Date: 2004-03-18 08:40:35 -0800 (Thu, 18 Mar 2004) $
032     */
033    
034    public class CompressionResponseStream
035        extends ServletOutputStream {
036    
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    
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    
107        // --------------------------------------------------------- Public Methods
108    
109        /**
110         * Set debug level
111         */
112        public void setDebugLevel(int debug) {
113            this.debug = debug;
114        }
115    
116    
117        /**
118         * Set the compressionThreshold number and create buffer for this size
119         */
120        protected void setBuffer(int threshold) {
121            compressionThreshold = threshold;
122            buffer = new byte[compressionThreshold];
123            if (debug > 1) {
124                System.out.println("buffer is set to "+compressionThreshold);
125            }
126        }
127    
128        /**
129         * Close this output stream, causing any buffered data to be flushed and
130         * any further output data to throw an IOException.
131         */
132        public void close() throws IOException {
133    
134            if (debug > 1) {
135                System.out.println("close() @ CompressionResponseStream");
136            }
137            if (closed)
138                throw new IOException("This output stream has already been closed");
139    
140            if (gzipstream != null) {
141                flushToGZip();
142                gzipstream.close();
143                gzipstream = null;
144            } else {
145                if (bufferCount > 0) {
146                    if (debug > 2) {
147                        System.out.print("output.write(");
148                        System.out.write(buffer, 0, bufferCount);
149                        System.out.println(")");
150                    }
151                    output.write(buffer, 0, bufferCount);
152                    bufferCount = 0;
153                }
154            }
155    
156            output.close();
157            closed = true;
158    
159        }
160    
161    
162        /**
163         * Flush any buffered data for this output stream, which also causes the
164         * response to be committed.
165         */
166        public void flush() throws IOException {
167    
168            if (debug > 1) {
169                System.out.println("flush() @ CompressionResponseStream");
170            }
171            if (closed) {
172                throw new IOException("Cannot flush a closed output stream");
173            }
174    
175            if (gzipstream != null) {
176                gzipstream.flush();
177            }
178    
179        }
180    
181        public void flushToGZip() throws IOException {
182    
183            if (debug > 1) {
184                System.out.println("flushToGZip() @ CompressionResponseStream");
185            }
186            if (bufferCount > 0) {
187                if (debug > 1) {
188                    System.out.println("flushing out to GZipStream, bufferCount = " + bufferCount);
189                }
190                writeToGZip(buffer, 0, bufferCount);
191                bufferCount = 0;
192            }
193    
194        }
195    
196        /**
197         * Write the specified byte to our output stream.
198         *
199         * @param b The byte to be written
200         *
201         * @exception IOException if an input/output error occurs
202         */
203        public void write(int b) throws IOException {
204    
205            if (debug > 1) {
206                System.out.println("write "+b+" in CompressionResponseStream ");
207            }
208            if (closed)
209                throw new IOException("Cannot write to a closed output stream");
210    
211            if (bufferCount >= buffer.length) {
212                flushToGZip();
213            }
214    
215            buffer[bufferCount++] = (byte) b;
216    
217        }
218    
219    
220        /**
221         * Write <code>b.length</code> bytes from the specified byte array
222         * to our output stream.
223         *
224         * @param b The byte array to be written
225         *
226         * @exception IOException if an input/output error occurs
227         */
228        public void write(byte b[]) throws IOException {
229    
230            write(b, 0, b.length);
231    
232        }
233    
234    
235        /**
236         * Write <code>len</code> bytes from the specified byte array, starting
237         * at the specified offset, to our output stream.
238         *
239         * @param b The byte array containing the bytes to be written
240         * @param off Zero-relative starting offset of the bytes to be written
241         * @param len The number of bytes to be written
242         *
243         * @exception IOException if an input/output error occurs
244         */
245        public void write(byte b[], int off, int len) throws IOException {
246    
247            if (debug > 1) {
248                System.out.println("write, bufferCount = " + bufferCount + " len = " + len + " off = " + off);
249            }
250            if (debug > 2) {
251                System.out.print("write(");
252                System.out.write(b, off, len);
253                System.out.println(")");
254            }
255    
256            if (closed)
257                throw new IOException("Cannot write to a closed output stream");
258    
259            if (len == 0)
260                return;
261    
262            // Can we write into buffer ?
263            if (len <= (buffer.length - bufferCount)) {
264                System.arraycopy(b, off, buffer, bufferCount, len);
265                bufferCount += len;
266                return;
267            }
268    
269            // There is not enough space in buffer. Flush it ...
270            flushToGZip();
271    
272            // ... and try again. Note, that bufferCount = 0 here !
273            if (len <= (buffer.length - bufferCount)) {
274                System.arraycopy(b, off, buffer, bufferCount, len);
275                bufferCount += len;
276                return;
277            }
278    
279            // write direct to gzip
280            writeToGZip(b, off, len);
281        }
282    
283        public void writeToGZip(byte b[], int off, int len) throws IOException {
284    
285            if (debug > 1) {
286                System.out.println("writeToGZip, len = " + len);
287            }
288            if (debug > 2) {
289                System.out.print("writeToGZip(");
290                System.out.write(b, off, len);
291                System.out.println(")");
292            }
293            if (gzipstream == null) {
294                if (debug > 1) {
295                    System.out.println("new GZIPOutputStream");
296                }
297                response.addHeader("Content-Encoding", "gzip");
298                gzipstream = new GZIPOutputStream(output);
299            }
300            gzipstream.write(b, off, len);
301    
302        }
303    
304    
305        // -------------------------------------------------------- Package Methods
306    
307    
308        /**
309         * Has this response stream been closed?
310         */
311        public boolean closed() {
312    
313            return (this.closed);
314    
315        }
316    
317    }