    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.internal.gzip;
    016
    017import org.apache.tapestry5.internal.InternalConstants;
    018import org.apache.tapestry5.services.assets.CompressionAnalyzer;
    019
    020import javax.servlet.ServletOutputStream;
    021import javax.servlet.http.HttpServletResponse;
    022import java.io.BufferedOutputStream;
    023import java.io.ByteArrayOutputStream;
    024import java.io.IOException;
    025import java.io.OutputStream;
    026import 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 */
    032public class BufferedGZipOutputStream extends ServletOutputStream
    033{
    034    private final String contentType;
    035
    036    private final HttpServletResponse response;
    037
    038    private final CompressionAnalyzer 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                                    CompressionAnalyzer 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        {
    085            response.setHeader(InternalConstants.CONTENT_ENCODING_HEADER, InternalConstants.GZIP_CONTENT_ENCODING);
    086        }
    087
    088        currentOutputStream =
    089                new BufferedOutputStream(possiblyCompressed);
    090
    091        // Write what content we already have to the new stream.
    092
    093        byteArrayOutputStream.writeTo(currentOutputStream);
    094
    095        byteArrayOutputStream = null;
    096    }
    097
    098    public void write(int b) throws IOException
    099    {
    100        currentOutputStream.write(b);
    101
    102        checkForCutover();
    103    }
    104
    105    @Override
    106    public void write(byte[] b) throws IOException
    107    {
    108        currentOutputStream.write(b);
    109
    110        checkForCutover();
    111    }
    112
    113    @Override
    114    public void write(byte[] b, int off, int len) throws IOException
    115    {
    116        currentOutputStream.write(b, off, len);
    117
    118        checkForCutover();
    119    }
    120
    121    @Override
    122    public void flush() throws IOException
    123    {
    124        forceOutputStream().flush();
    125    }
    126
    127    @Override
    128    public void close() throws IOException
    129    {
    130        // When closing, if we haven't accumulated enough output yet to start compressing,
    131        // then send what we have, uncompressed.
    132
    133        forceOutputStream().close();
    134    }
    135
    136    private OutputStream forceOutputStream() throws IOException
    137    {
    138        if (byteArrayOutputStream != null)
    139            openResponseOutputStream(false);
    140
    141        return currentOutputStream;
    142    }
    143}