001    // Copyright 2009 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry5.internal.gzip;
016    
017    import org.apache.tapestry5.internal.InternalConstants;
018    import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
019    
020    import javax.servlet.ServletOutputStream;
021    import javax.servlet.http.HttpServletResponse;
022    import java.io.BufferedOutputStream;
023    import java.io.ByteArrayOutputStream;
024    import java.io.IOException;
025    import java.io.OutputStream;
026    import java.util.zip.GZIPOutputStream;
027    
028    /**
029     * A buffered output stream that, when a certain number of bytes is buffered (the cutover point) will open a compressed
030     * stream (via {@link org.apache.tapestry5.services.Response#getOutputStream(String)}
031     */
032    public class BufferedGZipOutputStream extends ServletOutputStream
033    {
034        private final String contentType;
035    
036        private final HttpServletResponse response;
037    
038        private final ResponseCompressionAnalyzer analyzer;
039    
040        private final int cutover;
041    
042        private ByteArrayOutputStream byteArrayOutputStream;
043    
044        /**
045         * Initially the ByteArrayOutputStream, later the response output stream (possibly wrapped with a
046         * GZIPOutputStream).
047         */
048        private OutputStream currentOutputStream;
049    
050        public BufferedGZipOutputStream(String contentType, HttpServletResponse response, int cutover,
051                                        ResponseCompressionAnalyzer analyzer)
052        {
053            this.contentType = contentType;
054            this.response = response;
055            this.cutover = cutover;
056            this.analyzer = analyzer;
057    
058            byteArrayOutputStream = new ByteArrayOutputStream(cutover);
059    
060            currentOutputStream = byteArrayOutputStream;
061        }
062    
063        private void checkForCutover() throws IOException
064        {
065            if (byteArrayOutputStream == null) return;
066    
067            if (byteArrayOutputStream.size() < cutover) return;
068    
069            // Time to switch over to GZIP.
070            openResponseOutputStream(true);
071        }
072    
073        private void openResponseOutputStream(boolean gzip) throws IOException
074        {
075            OutputStream responseOutputStream = response.getOutputStream();
076    
077            boolean useCompression = gzip && analyzer.isCompressable(contentType);
078    
079            OutputStream possiblyCompressed = useCompression
080                                              ? new GZIPOutputStream(responseOutputStream)
081                                              : responseOutputStream;
082    
083            if (useCompression)
084                response.setHeader(InternalConstants.CONTENT_ENCODING_HEADER, InternalConstants.GZIP_CONTENT_ENCODING);
085    
086            currentOutputStream =
087                    new BufferedOutputStream(possiblyCompressed);
088    
089            // Write what content we already have to the new stream.
090    
091            byteArrayOutputStream.writeTo(currentOutputStream);
092    
093            byteArrayOutputStream = null;
094        }
095    
096        public void write(int b) throws IOException
097        {
098            currentOutputStream.write(b);
099    
100            checkForCutover();
101        }
102    
103        @Override
104        public void write(byte[] b) throws IOException
105        {
106            currentOutputStream.write(b);
107    
108            checkForCutover();
109        }
110    
111        @Override
112        public void write(byte[] b, int off, int len) throws IOException
113        {
114            currentOutputStream.write(b, off, len);
115    
116            checkForCutover();
117        }
118    
119        @Override
120        public void flush() throws IOException
121        {
122            forceOutputStream().flush();
123        }
124    
125        @Override
126        public void close() throws IOException
127        {
128            // When closing, if we haven't accumulated enough output yet to start compressing,
129            // then send what we have, uncompressed.
130    
131            forceOutputStream().close();
132        }
133    
134        private OutputStream forceOutputStream() throws IOException
135        {
136            if (byteArrayOutputStream != null)
137                openResponseOutputStream(false);
138    
139            return currentOutputStream;
140        }
141    }