/* vim: set ts=2 et sw=2 cindent fo=qroca: */ package com.globant.katari.core.web; import java.io.IOException; import java.io.PrintWriter; import java.io.OutputStream; import java.io.OutputStreamWriter; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import org.apache.commons.lang.Validate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** A response wrapper that intercepts the body data written by the servlets. * * This is used to post process the response, either by transforming it (like * sitemesh) or by validating it (like HtmlValidationFilter). * * You must provide an output stream by implementing * * protected abstract OutputStream createOutputStream(); * * The stream you provide will receive the body sent to the response object. * This interceptor has a write through option that, when set, also copies the * output to the original response. */ public abstract class ServletOutputInterceptor extends HttpServletResponseWrapper { /** The class logger. */ private static Logger log = LoggerFactory.getLogger(ServletOutputInterceptor.class); /** Indicates whether the data is written through to the original output * stream. */ private boolean writeThrough; /** A servlet output stream that makes the written data as a string. */ private static final class OutputStreamWrapper extends ServletOutputStream { /** An output stream where the servlets store its outputs. * * This is never null. */ private OutputStream outputStream; /** The original output stream. * * If this is not null, output will be written both here and to * outputStream. */ private OutputStream originalOutputStream; /** Creates a servlet output stream that writes to the provided stream. * * @param output The output stream, it cannot be null. */ private OutputStreamWrapper(final OutputStream output) { Validate.notNull(output, "The output stream cannot be null"); outputStream = output; } /** Creates a servlet output stream that writes to both provided streams. * * @param output The output stream, it cannot be null. * * @param originalOutput The original output stream, it cannot be null. */ private OutputStreamWrapper(final OutputStream output, final OutputStream originalOutput) { Validate.notNull(output, "The output stream cannot be null"); Validate.notNull(originalOutput, "The original output stream cannot be null"); outputStream = output; originalOutputStream = originalOutput; } /** Writes the specified byte to the output stream. * * @param theByte The byte to write. * * @throws IOException in case of a write error. */ @Override public void write(final int theByte) throws IOException { if (originalOutputStream != null) { originalOutputStream.write(theByte); } outputStream.write(theByte); } }; /** The output stream that intercepts the data written by the servlets. * * It is null until the user requests the output stream through * getOutputStream. */ private OutputStreamWrapper outputStreamWrapper = null; /** A writer that intercepts character data written by the servlets and * writes it to the outputStreamWrapper. * * It is null until the client requests the writer through getWriter. */ private PrintWriter writer = null; /** Creates a new ServletOutputInterceptor. * * @param response The wrapped response. It cannot be null. */ public ServletOutputInterceptor(final HttpServletResponse response) { super(response); Validate.notNull(response, "The response cannot be null"); } /** Creates a new ServletOutputInterceptor that can write the output to the * original stream. * * @param response The wrapped response. It cannot be null. * * @param shouldWriteThrough Indicates whether the data is written through to * the original output stream. */ public ServletOutputInterceptor(final HttpServletResponse response, final boolean shouldWriteThrough) { super(response); Validate.notNull(response, "The response cannot be null"); writeThrough = shouldWriteThrough; } /** Called when ServletOutputInterceptor must create the output stream. * * Subclasses must implement this to return the output stream that receives * the written data. * * @return The output stream. Implementations must not return null. */ protected abstract OutputStream createOutputStream(); /** {@inheritDoc} */ @Override public ServletOutputStream getOutputStream() throws IOException { log.trace("Entering getOutputStream"); if (writer != null) { throw new IllegalStateException("getWriter() has already been" + " called for this response"); } // The client can request the output stream any times he wants. if (outputStreamWrapper == null) { initializeOutputStream(); } log.trace("Leaving getOutputStream"); return outputStreamWrapper; } /** {@inheritDoc} */ @Override public PrintWriter getWriter() throws IOException { log.trace("Entering getWriter"); if (writer == null) { if (outputStreamWrapper != null) { throw new IllegalStateException("getOutputStream() has already been" + " called for this response"); } initializeOutputStream(); String encoding = getResponse().getCharacterEncoding(); if (encoding == null) { writer = new PrintWriter(outputStreamWrapper); } else { writer = new PrintWriter(new OutputStreamWriter( outputStreamWrapper, encoding)); } } log.trace("Leaving getWriter"); return writer; } /** Initializes the wrapped output stream. * * @throws IOException in case of error. */ private void initializeOutputStream() throws IOException { if (writeThrough) { outputStreamWrapper = new OutputStreamWrapper(createOutputStream(), getResponse().getOutputStream()); } else { outputStreamWrapper = new OutputStreamWrapper(createOutputStream()); } } /** Flushes all the buffers. * * This operation must be called before accessing the buffered output stream. * * If we are in writeThrough mode, we also flush the wrapped response. * * @throws IOException in case of error. */ @Override public void flushBuffer() throws IOException { if (writer != null) { writer.flush(); } if (outputStreamWrapper != null) { outputStreamWrapper.flush(); } if (writeThrough) { super.flushBuffer(); } } }