Package ch.entwine.weblounge.cache.impl

Source Code of ch.entwine.weblounge.cache.impl.CacheableHttpServletResponse

/*
*  Weblounge: Web Content Management System
*  Copyright (c) 2003 - 2011 The Weblounge Team
*  http://entwinemedia.com/weblounge
*
*  This program is free software; you can redistribute it and/or
*  modify it under the terms of the GNU Lesser General Public License
*  as published by the Free Software Foundation; either version 2
*  of the License, or (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU Lesser General Public License for more details.
*
*  You should have received a copy of the GNU Lesser General Public License
*  along with this program; if not, write to the Free Software Foundation
*  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package ch.entwine.weblounge.cache.impl;

import ch.entwine.weblounge.cache.StreamFilter;
import ch.entwine.weblounge.cache.impl.filter.FilterWriter;
import ch.entwine.weblounge.common.impl.request.CachedOutputStream;
import ch.entwine.weblounge.common.impl.request.RequestUtils;
import ch.entwine.weblounge.common.request.CacheHandle;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.TeeOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

/**
* Implementation of a <code>HttpServletResponseWrapper</code> that allows for
* response caching by installing a custom version of an output stream which
* works like the <code>tee</code> command in un*x systems. Like this, the
* output can be written to the response cache <i>and</i> to the client at the
* same time.
*/
class CacheableHttpServletResponse extends HttpServletResponseWrapper {

  /** The logging facility */
  private static final Logger logger = LoggerFactory.getLogger(CacheableHttpServletResponse.class);

  /**
   * Holds the special tee writer that copies the output to the network and to
   * the cache.
   */
  private PrintWriter out = null;

  /** The cache transaction for this response */
  private CacheTransaction tx = null;

  /** The format used for date headers */
  private DateFormat format = null;

  /** The content type */
  private String contentType = null;

  /** Whether the getOuputStream has already been called */
  private boolean osCalled = false;

  /** Default encoding */
  private static final String DEFAULT_ENCODING = "utf-8";

  /**
   * Creates a <code>CacheableHttpServletResponse</code> that is writing any
   * content to the wrapped response as well as to the cached output stream,
   * given a preceding call to {@link #startTransaction(CacheTransaction)}.
   *
   * @param tx
   *          the cached transaction represented by this cacheable response
   */
  CacheableHttpServletResponse(HttpServletResponse response) {
    super(response);
  }

  /**
   * Starts a cache transaction.
   *
   * @param handle
   *          the cache handle
   * @param filter
   *          the stream filter
   * @return the transaction
   */
  public CacheTransaction startTransaction(CacheHandle handle,
      StreamFilter filter) {
    tx = new CacheTransaction(handle, filter);
    return tx;
  }

  /**
   * Returns the modified writer that enables the <code>CacheManager</cache>
   * to copy the response to the cache.
   *
   * @return a PrintWriter object that can return character data to the client
   * @throws IOException
   *           if the writer could not be allocated
   * @see javax.servlet.ServletResponse#getWriter()
   * @see ch.entwine.weblounge.OldCacheManager.cache.CacheManager
   */
  @Override
  public PrintWriter getWriter() throws IOException {
    // Check whether there's already a writer allocated
    if (out != null)
      return out;

    // Check whether getOutputStream() has already been called
    if (osCalled)
      throw new IllegalStateException("An output stream has already been allocated");

    // Get the character encoding
    String encoding = getCharacterEncoding();
    if (encoding == null) {
      encoding = DEFAULT_ENCODING;
      setCharacterEncoding(encoding);
    }

    // Allocate a new writer. If there is a transaction, the output is written
    // to both the original response and the cache output stream.
    try {
      if (tx == null || tx.getFilter() == null)
        out = new PrintWriter(new OutputStreamWriter(super.getOutputStream(), encoding));
      else
        out = new PrintWriter(new BufferedWriter(new FilterWriter(new OutputStreamWriter(new TeeOutputStream(super.getOutputStream(), tx.getOutputStream()), encoding), tx.getFilter(), contentType)));
    } catch (UnsupportedEncodingException e) {
      throw new IOException(e.getMessage());
    }

    // Check whether the new writer is usable
    if (out == null)
      throw new IOException("Unable to allocate writer");

    // Return the new writer
    return out;
  }

  /**
   * @see javax.servlet.ServletResponseWrapper#getOutputStream()
   */
  @Override
  public ServletOutputStream getOutputStream() throws IOException {
    if (out != null)
      throw new IllegalStateException("A writer has already been allocated");
    osCalled = true;
    return tx != null ? tx.getOutputStream() : super.getOutputStream();
  }

  /**
   * Signals the end of a cache entry.
   *
   * @param hnd
   *          the handle to end
   */
  void endEntry(CacheHandle hnd) {
    if (out != null)
      out.flush();
  }

  /**
   * Signals that the page display is finished and flushes the buffer.
   *
   * @return the cached transaction for this page
   */
  CacheTransaction endOutput() {
    try {
      if (out != null) {
        out.flush();
        out.close();
        out = null;
      } else if (tx != null) {
        tx.getOutputStream().flush();
        tx.getOutputStream().close();
        CachedOutputStream cacheOs = tx.getOutputStream();
        OutputStream clientOs = super.getOutputStream();
        ByteArrayInputStream cacheIs = new ByteArrayInputStream(cacheOs.getContent());
        IOUtils.copy(cacheIs, clientOs);
        clientOs.flush();
        clientOs.close();
      } else {
        super.getOutputStream().flush();
        super.getOutputStream().close();
      }
    } catch (IOException e) {
      if (RequestUtils.isCausedByClient(e))
        logger.debug("Can't write cached response back to client: " + e.getMessage());
      else
        logger.error("Unknown error while writing cached response back to client", e);
    }
    return tx;
  }

  /**
   * Invalidate the output. Tells the cache writer to stop adding output to the
   * cache.
   */
  void invalidate() {
    if (tx != null)
      tx.invalidate();
  }

  /**
   * Returns <code>true</code> if the response has been invalidated.
   *
   * @return <code>true</code> if the response has been invalidated
   */
  public boolean isValid() {
    return tx != null ? tx.isValid() : false;
  }

  /**
   * Returns the active cache transaction.
   *
   * @return the transaction
   */
  public CacheTransaction getTransaction() {
    return tx;
  }

  /**
   * @see javax.servlet.ServletResponse#setContentType(String)
   */
  @Override
  public void setContentType(String type) {
    super.setContentType(type);
    contentType = type;
    if (tx != null) {
      tx.getHeaders().setHeader("Content-Type", contentType);
    }
  }

  /**
   * {@inheritDoc}
   *
   * @see javax.servlet.ServletResponseWrapper#flushBuffer()
   */
  @Override
  public void flushBuffer() throws IOException {
    if (isCommitted())
      return;

    // Make sure to flush any print writer so its content is
    // written to the cached output stream
    if (tx != null && out != null)
      out.flush();

    // Finally initiate writing the content back to the client
    super.flushBuffer();
  }

  /**
   * @see javax.servlet.http.HttpServletResponse#addHeader(java.lang.String,
   *      java.lang.String)
   */
  @Override
  public void addHeader(String name, String value) {
    super.addHeader(name, value);
    if (tx != null)
      tx.getHeaders().addHeader(name, value);
  }

  /**
   * @see javax.servlet.http.HttpServletResponse#setHeader(java.lang.String,
   *      java.lang.String)
   */
  @Override
  public void setHeader(String name, String value) {
    super.setHeader(name, value);
    if (tx != null)
      tx.getHeaders().setHeader(name, value);
  }

  /**
   * @see javax.servlet.http.HttpServletResponse#addDateHeader(java.lang.String,
   *      long)
   */
  @Override
  public void addDateHeader(String name, long date) {
    super.addDateHeader(name, date);
    if (tx != null)
      tx.getHeaders().addHeader(name, formatDate(date));
  }

  /**
   * @see javax.servlet.http.HttpServletResponse#addIntHeader(java.lang.String,
   *      int)
   */
  @Override
  public void addIntHeader(String name, int value) {
    super.addIntHeader(name, value);
    tx.getHeaders().addHeader(name, Integer.toString(value));
  }

  /**
   * @see javax.servlet.http.HttpServletResponse#setDateHeader(java.lang.String,
   *      long)
   */
  @Override
  public void setDateHeader(String name, long date) {
    super.setDateHeader(name, date);
    if (tx != null)
      tx.getHeaders().setHeader(name, formatDate(date));
  }

  /**
   * @see javax.servlet.http.HttpServletResponse#setIntHeader(java.lang.String,
   *      int)
   */
  @Override
  public void setIntHeader(String name, int value) {
    super.setIntHeader(name, value);
    if (tx != null)
      tx.getHeaders().setHeader(name, Integer.toString(value));
  }

  /**
   * Format the date for an HTTP header. The resulting date will match the
   * following example:
   *
   * <pre>
   * EEE, dd MMM yyyy HH:mm:ss 'GMT'
   * </pre>
   *
   * @param date
   *          the date to format
   * @return the formatted date
   */
  private String formatDate(long date) {
    if (format == null) {
      format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
      format.setTimeZone(TimeZone.getTimeZone("GMT"));
    }
    return format.format(new Date(date));
  }

}
TOP

Related Classes of ch.entwine.weblounge.cache.impl.CacheableHttpServletResponse

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.