Java tutorial
/* * 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)); } }