    001// Copyright 2009, 2012 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
    015package org.apache.tapestry5.http.internal.gzip;
    016
    017import java.io.BufferedOutputStream;
    018import java.io.ByteArrayOutputStream;
    019import java.io.IOException;
    020import java.io.OutputStream;
    021import java.util.zip.GZIPOutputStream;
    022
    023import javax.servlet.ServletOutputStream;
    024import javax.servlet.http.HttpServletResponse;
    025
    026import org.apache.tapestry5.http.internal.TapestryHttpInternalConstants;
    027import org.apache.tapestry5.http.services.CompressionAnalyzer;
    028
    029/**
    030 * A buffered output stream that, when a certain number of bytes is buffered (the cutover point) will open a compressed
    031 * stream (via {@link org.apache.tapestry5.http.services.Response#getOutputStream(String)}
    032 */
    033public class BufferedGZipOutputStream extends ServletOutputStream
    034{
    035    private final String contentType;
    036
    037    private final HttpServletResponse response;
    038
    039    private final CompressionAnalyzer analyzer;
    040
    041    private final int cutover;
    042
    043    private ByteArrayOutputStream byteArrayOutputStream;
    044
    045    /**
    046     * Initially the ByteArrayOutputStream, later the response output stream (possibly wrapped with a
    047     * GZIPOutputStream).
    048     */
    049    private OutputStream currentOutputStream;
    050
    051    public BufferedGZipOutputStream(String contentType, HttpServletResponse response, int cutover,
    052                                    CompressionAnalyzer analyzer)
    053    {
    054        this.contentType = contentType;
    055        this.response = response;
    056        this.cutover = cutover;
    057        this.analyzer = analyzer;
    058
    059        byteArrayOutputStream = new ByteArrayOutputStream(cutover);
    060
    061        currentOutputStream = byteArrayOutputStream;
    062    }
    063
    064    private void checkForCutover() throws IOException
    065    {
    066        if (byteArrayOutputStream == null) return;
    067
    068        if (byteArrayOutputStream.size() < cutover) return;
    069
    070        // Time to switch over to GZIP.
    071        openResponseOutputStream(true);
    072    }
    073
    074    private void openResponseOutputStream(boolean gzip) throws IOException
    075    {
    076        OutputStream responseOutputStream = response.getOutputStream();
    077
    078        boolean useCompression = gzip && analyzer.isCompressable(contentType);
    079
    080        OutputStream possiblyCompressed = useCompression
    081                ? new GZIPOutputStream(responseOutputStream)
    082                : responseOutputStream;
    083
    084        if (useCompression)
    085        {
    086            response.setHeader(
    087                    TapestryHttpInternalConstants.CONTENT_ENCODING_HEADER,
    088                    TapestryHttpInternalConstants.GZIP_CONTENT_ENCODING);
    089        }
    090
    091        currentOutputStream =
    092                new BufferedOutputStream(possiblyCompressed);
    093
    094        // Write what content we already have to the new stream.
    095
    096        byteArrayOutputStream.writeTo(currentOutputStream);
    097
    098        byteArrayOutputStream = null;
    099    }
    100
    101    public void write(int b) throws IOException
    102    {
    103        currentOutputStream.write(b);
    104
    105        checkForCutover();
    106    }
    107
    108    @Override
    109    public void write(byte[] b) throws IOException
    110    {
    111        currentOutputStream.write(b);
    112
    113        checkForCutover();
    114    }
    115
    116    @Override
    117    public void write(byte[] b, int off, int len) throws IOException
    118    {
    119        currentOutputStream.write(b, off, len);
    120
    121        checkForCutover();
    122    }
    123
    124    @Override
    125    public void flush() throws IOException
    126    {
    127        forceOutputStream().flush();
    128    }
    129
    130    @Override
    131    public void close() throws IOException
    132    {
    133        // When closing, if we haven't accumulated enough output yet to start compressing,
    134        // then send what we have, uncompressed.
    135
    136        forceOutputStream().close();
    137    }
    138
    139    private OutputStream forceOutputStream() throws IOException
    140    {
    141        if (byteArrayOutputStream != null)
    142            openResponseOutputStream(false);
    143
    144        return currentOutputStream;
    145    }
    146}