/*
 * Copyright (c) 2008, Harald Kuhr
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * * Neither the name of the copyright holder nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.twelvemonkeys.servlet.gzip;

import com.twelvemonkeys.servlet.GenericFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * A filter to reduce the output size of web resources.
 * <p>
 * The HTTP protocol supports compression of the content to reduce network
 * bandwidth. The important headers involved, are the {@code Accept-Encoding}
 * request header, and the {@code Content-Encoding} response header.
 * This feature can be used to further reduce the number of bytes transferred
 * over the network, at the cost of some extra processing time at both endpoints.
 * Most modern browsers supports compression in GZIP format, which is fairly
 * efficient in cost/compression ratio.
 * </p>
 * <p>
 * The filter tests for the presence of an {@code Accept-Encoding} header with a
 * value of {@code "gzip"} (several different encoding header values are
 * possible in one header). If not present, the filter simply passes the
 * request/response pair through, leaving it untouched. If present, the
 * {@code Content-Encoding} header is set, with the value {@code "gzip"},
 * and the response is wrapped.
 * The response output stream is wrapped in a
 * {@link java.util.zip.GZIPOutputStream} which performs the GZIP encoding.
 * For efficiency, the filter does not buffer the response, but writes through
 * the gzipped output stream.
 * </p>
 * <p>
 * <b>Configuration</b>
 * <br>
 * To use {@code GZIPFilter} in your web-application, you simply need to add it
 * to your web descriptor ({@code web.xml}). If using a servlet container that
 *  supports the Servlet 2.4 spec, the new {@code dispatcher} element should be
 * used, and set to {@code REQUEST/FORWARD}, to make sure the filter is invoked
 * only once for requests.
 * If using an older web descriptor, set the {@code init-param}
 * {@code "once-per-request"} to {@code "true"} (this will have the same effect,
 * but might perform slightly worse than the 2.4 version).
 * Please see the examples below.
 * <b>Servlet 2.4 version, filter section:</b>
 * </p>
 * <br>
 * <pre>
 * &lt;!-- GZIP Filter Configuration --&gt;
 * &lt;filter&gt;
 *      &lt;filter-name&gt;gzip&lt;/filter-name&gt;
 *      &lt;filter-class&gt;com.twelvemonkeys.servlet.GZIPFilter&lt;/filter-class&gt;
 * &lt;/filter&gt;
 * </pre>
 * <b>Filter-mapping section:</b>
 * <br>
 * <pre>
 * &lt;!-- GZIP Filter Mapping --&gt;
 * &lt;filter-mapping&gt;
 *      &lt;filter-name&gt;gzip&lt;/filter-name&gt;
 *      &lt;url-pattern&gt;*.html&lt;/url-pattern&gt;
 *      &lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;
 *      &lt;dispatcher&gt;FORWARD&lt;/dispatcher&gt;
 * &lt;/filter-mapping&gt;
 * &lt;filter-mapping&gt;
 *      &lt;filter-name&gt;gzip&lt;/filter-name&gt;
 *      &lt;url-pattern&gt;*.jsp&lt; /url-pattern&gt;
 *      &lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;
 *      &lt;dispatcher&gt;FORWARD&lt;/dispatcher&gt;
 * &lt;/filter-mapping&gt;
 * </pre>
 * <p>
 * Based on ideas and code found in the ONJava article
 * <a href="http://www.onjava.com/pub/a/onjava/2003/11/19/filters.html">Two
 * Servlet Filters Every Web Application Should Have</a>
 * by Jayson Falkner.
 * </p>
 *
 * @author Jayson Falkner
 * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
 * @author last modified by $Author: haku $
 * @version $Id: GZIPFilter.java#1 $
 */
public class GZIPFilter extends GenericFilter {

    {
        oncePerRequest = true;
    }

    protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
        // Can only filter HTTP responses
        if (pRequest instanceof HttpServletRequest) {
            HttpServletRequest request = (HttpServletRequest) pRequest;
            HttpServletResponse response = (HttpServletResponse) pResponse;

            // If GZIP is supported, use compression
            String accept = request.getHeader("Accept-Encoding");
            if (accept != null && accept.contains("gzip")) {
                //System.out.println("GZIP supported, compressing.");
                GZIPResponseWrapper wrapped = new GZIPResponseWrapper(response);

                try {
                    pChain.doFilter(pRequest, wrapped);
                }
                finally {
                    wrapped.flushResponse();
                }

                return;
            }
        }

        // Else, continue chain
        pChain.doFilter(pRequest, pResponse);
    }
}
