jp.xet.uncommons.web.HtmlCompressionFilter.java Source code

Java tutorial

Introduction

Here is the source code for jp.xet.uncommons.web.HtmlCompressionFilter.java

Source

/*
 * Copyright 2011 Daisuke Miyamoto.
 * Created on 2012/08/23
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package jp.xet.uncommons.web;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

import com.google.common.base.Strings;
import com.googlecode.htmlcompressor.compressor.HtmlCompressor;

import org.springframework.web.filter.OncePerRequestFilter;

/**
 * TODO for daisuke
 * 
 * @since 1.7
 * @version $Id$
 * @author daisuke
 */
public class HtmlCompressionFilter extends OncePerRequestFilter {

    private boolean enabled = true;

    // default settings
    private boolean removeComments = true;

    private boolean removeMultiSpaces = true;

    // optional settings
    private boolean removeIntertagSpaces = false;

    private boolean removeQuotes = false;

    private boolean compressJavaScript = false;

    private boolean compressCss = false;

    // YUICompressor settings
    private boolean yuiJsNoMunge = false;

    private boolean yuiJsPreserveAllSemiColons = false;

    private boolean yuiJsDisableOptimizations = false;

    private int yuiJsLineBreak = -1;

    private int yuiCssLineBreak = -1;

    /**
     * @param compressCss set <code>true</code> to enable CSS compression. 
     * Default is <code>false</code>
     * @see HtmlCompressor#setCompressCss(boolean)
     */
    public void setCompressCss(boolean compressCss) {
        this.compressCss = compressCss;
    }

    /**
     * @param compressJavaScript set <code>true</code> to enable JavaScript compression. 
     * Default is <code>false</code>
     * @see HtmlCompressor#setCompressJavaScript(boolean)
     */
    public void setCompressJavaScript(boolean compressJavaScript) {
        this.compressJavaScript = compressJavaScript;
    }

    /**
     * @param enabled set <code>false</code> to bypass all compression
     * @see HtmlCompressor#setEnabled(boolean)
     */
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    /**
     * @param removeComments set <code>true</code> to remove all HTML comments
     * @see HtmlCompressor#setRemoveComments(boolean)
     */
    public void setRemoveComments(boolean removeComments) {
        this.removeComments = removeComments;
    }

    /**
     * @param removeIntertagSpaces set <code>true</code> to remove all inter-tag whitespace characters
     * @see HtmlCompressor#setRemoveIntertagSpaces(boolean)
     */
    public void setRemoveIntertagSpaces(boolean removeIntertagSpaces) {
        this.removeIntertagSpaces = removeIntertagSpaces;
    }

    /**
     * @param removeMultiSpaces set <code>true</code> to replace all multiple whitespace characters 
     * will single spaces.
     * @see HtmlCompressor#setRemoveMultiSpaces(boolean)
     */
    public void setRemoveMultiSpaces(boolean removeMultiSpaces) {
        this.removeMultiSpaces = removeMultiSpaces;
    }

    /**
     * @param removeQuotes set <code>true</code> to remove unnecessary quotes from tag attributes
     * @see HtmlCompressor#setRemoveQuotes(boolean)
     */
    public void setRemoveQuotes(boolean removeQuotes) {
        this.removeQuotes = removeQuotes;
    }

    /**
     * @param yuiCssLineBreak set number of symbols per line
     * @see HtmlCompressor#setYuiCssLineBreak(int)
     */
    public void setYuiCssLineBreak(int yuiCssLineBreak) {
        this.yuiCssLineBreak = yuiCssLineBreak;
    }

    /**
     * @param yuiJsDisableOptimizations set <code>true<code> to enable 
     * <code>disable-optimizations</code> mode
     * @see HtmlCompressor#setYuiJsDisableOptimizations(boolean)
     */
    public void setYuiJsDisableOptimizations(boolean yuiJsDisableOptimizations) {
        this.yuiJsDisableOptimizations = yuiJsDisableOptimizations;
    }

    /**
     * @param yuiJsLineBreak set number of symbols per line
     * @see HtmlCompressor#setYuiJsLineBreak(int)
     */
    public void setYuiJsLineBreak(int yuiJsLineBreak) {
        this.yuiJsLineBreak = yuiJsLineBreak;
    }

    /**
     * @param yuiJsNoMunge set <code>true</code> to enable <code>nomunge</code> mode
     * @see HtmlCompressor#setYuiJsNoMunge(boolean)
     */
    public void setYuiJsNoMunge(boolean yuiJsNoMunge) {
        this.yuiJsNoMunge = yuiJsNoMunge;
    }

    /**
     * @param yuiJsPreserveAllSemiColons set <code>true<code> to enable <code>preserve-semi</code> mode
     * @see HtmlCompressor#setYuiJsPreserveAllSemiColons(boolean)
     */
    public void setYuiJsPreserveAllSemiColons(boolean yuiJsPreserveAllSemiColons) {
        this.yuiJsPreserveAllSemiColons = yuiJsPreserveAllSemiColons;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {
        OutputStreamResponseWrapper wrappedResponse = new OutputStreamResponseWrapper(response);

        filterChain.doFilter(request, wrappedResponse);

        ByteArrayOutputStream baos = wrappedResponse.getRealOutputStream();

        if (enabled && Strings.nullToEmpty(response.getContentType()).startsWith("text/html")) {
            HtmlCompressor compressor = new HtmlCompressor();
            compressor.setEnabled(enabled);
            compressor.setRemoveComments(removeComments);
            compressor.setRemoveMultiSpaces(removeMultiSpaces);
            compressor.setRemoveIntertagSpaces(removeIntertagSpaces);
            compressor.setRemoveQuotes(removeQuotes);
            compressor.setCompressJavaScript(compressJavaScript);
            compressor.setCompressCss(compressCss);
            compressor.setYuiJsNoMunge(yuiJsNoMunge);
            compressor.setYuiJsPreserveAllSemiColons(yuiJsPreserveAllSemiColons);
            compressor.setYuiJsDisableOptimizations(yuiJsDisableOptimizations);
            compressor.setYuiJsLineBreak(yuiJsLineBreak);
            compressor.setYuiCssLineBreak(yuiCssLineBreak);

            PrintWriter writer = null;
            try {
                String compressed = compressor.compress(baos.toString());
                response.setContentLength(compressed.length());
                writer = response.getWriter();
                writer.write(compressed);
            } finally {
                if (writer != null) {
                    writer.close();
                }
            }
        } else if (baos != null) {
            ServletOutputStream outputStream = null;
            try {
                outputStream = response.getOutputStream();
                outputStream.write(baos.toByteArray());
            } finally {
                if (outputStream != null) {
                    outputStream.close();
                }
            }
        }
    }

    static class OutputStreamResponseWrapper extends HttpServletResponseWrapper {

        private ByteArrayOutputStream realOutputStream;

        private ServletOutputStream stream;

        private PrintWriter writer;

        public OutputStreamResponseWrapper(HttpServletResponse response) {
            super(response);
        }

        public ServletOutputStream createOutputStream() {
            realOutputStream = new ByteArrayOutputStream();
            return new ServletOutputStreamWrapper(realOutputStream);
        }

        public void finishResponse() {
            try {
                if (writer != null) {
                    writer.close();
                } else if (stream != null) {
                    stream.close();
                }
            } catch (IOException e) {
                // ignore
            }
        }

        @Override
        public void flushBuffer() throws IOException {
            stream.flush();
        }

        @Override
        public ServletOutputStream getOutputStream() {
            if (writer != null) {
                throw new IllegalStateException("getWriter() has already been called!");
            }

            if (stream == null) {
                stream = createOutputStream();
            }
            return stream;
        }

        /**
         * Gets the underlying instance of the output stream.
         * 
         * @return underlying instance of the output stream
         */
        public ByteArrayOutputStream getRealOutputStream() {
            return realOutputStream;
        }

        @Override
        public PrintWriter getWriter() throws IOException {
            if (writer != null) {
                return writer;
            }

            if (stream != null) {
                throw new IllegalStateException("getOutputStream() has already been called!");
            }

            stream = createOutputStream();
            writer = new PrintWriter(new OutputStreamWriter(stream, "UTF-8"));
            return writer;
        }

        @Override
        public void setContentLength(int length) {
        }
    }

    /**
     * A wrapper to provide a concrete implementation of the servlet output stream, so we can wrap other streams.
     * Such as in a filter wrapping a servlet response.
     *
     * @author thein
     * @see <a href="http://www.java-forums.org/java-servlet/20631-how-get-content-httpservletresponse.html"></a>
     */
    static class ServletOutputStreamWrapper extends ServletOutputStream {

        OutputStream out;

        boolean closed = false;

        public ServletOutputStreamWrapper(OutputStream realStream) {
            out = realStream;
        }

        @Override
        public void close() throws IOException {
            if (closed) {
                throw new IOException("This output stream has already been closed");
            }
            out.flush();
            out.close();

            closed = true;
        }

        @Override
        public void flush() throws IOException {
            if (closed) {
                throw new IOException("Cannot flush a closed output stream");
            }
            out.flush();
        }

        @Override
        public void write(byte[] b) throws IOException {
            write(b, 0, b.length);
        }

        @Override
        public void write(byte[] b, int off, int len) throws IOException {
            // System.out.println("writing...");
            if (closed) {
                throw new IOException("Cannot write to a closed output stream");
            }
            out.write(b, off, len);
        }

        @Override
        public void write(int b) throws IOException {
            if (closed) {
                throw new IOException("Cannot write to a closed output stream");
            }
            out.write((byte) b);
        }
    }
}