    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    }