ch.entwine.weblounge.common.impl.request.WebloungeResponseImpl.java Source code

Java tutorial

Introduction

Here is the source code for ch.entwine.weblounge.common.impl.request.WebloungeResponseImpl.java

Source

/*
 *  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.common.impl.request;

import ch.entwine.weblounge.common.content.page.HTMLHeadElement;
import ch.entwine.weblounge.common.impl.util.config.ConfigurationUtils;
import ch.entwine.weblounge.common.request.CacheHandle;
import ch.entwine.weblounge.common.request.CacheTag;
import ch.entwine.weblounge.common.request.ResponseCache;
import ch.entwine.weblounge.common.request.WebloungeRequest;
import ch.entwine.weblounge.common.request.WebloungeResponse;

import org.apache.commons.io.IOUtils;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

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

/**
 * Default implementation of the <code>WebloungeResponse</code>.
 */
public class WebloungeResponseImpl extends HttpServletResponseWrapper implements WebloungeResponse {

    /** Flag for invalidated responses that should not be cached */
    private boolean isValid = true;

    /** True if an error has been reported */
    private boolean hasError = false;

    /** Flag to indicate whether the buffered response has been submitted */
    private boolean submitted = false;

    /** Response status */
    private int responseStatus = SC_OK;

    /** Associated HTTP request object */
    private WeakReference<WebloungeRequest> request = null;

    /** The cache service */
    private WeakReference<ResponseCache> cache = null;

    /** The response's cache handle */
    private WeakReference<CacheHandle> cacheHandle = null;

    private List<HTMLHeadElement> htmlHeaders = null;

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

    /** Holds the special writer that copies the output to the buffer first */
    private PrintWriter out = null;

    /** A buffer that can hold the output stream prior to writing it back */
    private CachedOutputStream os = null;

    /** The response's content modification date */
    private Date modificationDate = new Date(0);

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

    /**
     * Creates a new <code>HttpServletResponse</code> wrapper around the original
     * response object.
     * 
     * @param response
     *          the response
     */
    public WebloungeResponseImpl(HttpServletResponse response) {
        super(response);
    }

    /**
     * {@inheritDoc}
     * 
     * @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;

        String contentType = getContentType();
        if (contentType != null && contentType.startsWith("text")) {
            os = new CachedOutputStream();
            return os;
        } else {
            return super.getOutputStream();
        }
    }

    /**
     * Returns the modified writer that enables the caching of the response.
     * 
     * @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);
        }

        // Install the writer
        try {
            String contentType = getContentType();
            if (contentType != null && contentType.startsWith("text")) {
                os = new CachedOutputStream();
                out = new PrintWriter(new OutputStreamWriter(os, encoding));
            } else {
                out = new PrintWriter(new OutputStreamWriter(super.getOutputStream(), encoding));
            }
        } catch (UnsupportedEncodingException e) {
            throw new IOException(e.getMessage());
        }

        // Return the new writer
        return out;
    }

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

        if (!submitted)
            submitResponseBuffer();

        // Send the response back to the client
        super.flushBuffer();
    }

    /**
     * {@inheritDoc}
     * 
     * @see javax.servlet.http.HttpServletResponseWrapper#sendRedirect(java.lang.String)
     */
    @Override
    public void sendRedirect(String location) throws IOException {
        super.sendRedirect(location);
        responseStatus = HttpServletResponse.SC_MOVED_PERMANENTLY;
    }

    /**
     * {@inheritDoc}
     * 
     * @see javax.servlet.http.HttpServletResponseWrapper#sendError(int,
     *      java.lang.String)
     */
    @Override
    public void sendError(int error, String msg) throws IOException {
        hasError = true;
        responseStatus = error;
        super.sendError(error, msg);
    }

    /**
     * {@inheritDoc}
     * 
     * @see javax.servlet.http.HttpServletResponseWrapper#sendError(int)
     */
    @Override
    public void sendError(int error) throws IOException {
        sendError(error, null);
    }

    /**
     * Returns <code>true</code> if an error code has been sent back to the
     * client.
     * 
     * @return <code>true</code> if an error code has been sent to the client
     */
    public boolean hasError() {
        return hasError;
    }

    /**
     * {@inheritDoc}
     * 
     * @see javax.servlet.http.HttpServletResponseWrapper#setStatus(int)
     */
    @Override
    public void setStatus(int sc) {
        super.setStatus(sc);
        responseStatus = sc;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.request.WebloungeResponse#getStatus()
     */
    public int getStatus() {
        return responseStatus;
    }

    /**
     * Sets the associated request object.
     * 
     * @param request
     *          the request
     */
    public void setRequest(WebloungeRequest request) {
        this.request = new WeakReference<WebloungeRequest>(request);
    }

    /**
     * Sets the service that is used to cache responses to clients.
     * 
     * @param cache
     *          the cache
     */
    public void setResponseCache(ResponseCache cache) {
        this.cache = new WeakReference<ResponseCache>(cache);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.Taggable#addTag(java.lang.String,
     *      java.lang.String)
     */
    public boolean addTag(String name, String value) {
        boolean result = false;
        if (cacheHandle != null && cacheHandle.get() != null) {
            result = cacheHandle.get().addTag(name, value);
        }
        return result;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.Taggable#addTags(java.util.Collection)
     */
    public boolean addTags(Collection<CacheTag> tags) {
        boolean result = false;
        if (cacheHandle != null && cacheHandle.get() != null) {
            result = cacheHandle.get().addTags(tags);
        }
        return result;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.Taggable#addTag(ch.entwine.weblounge.common.content.Tag)
     */
    public boolean addTag(CacheTag tag) {
        boolean result = false;
        if (cacheHandle != null && cacheHandle.get() != null) {
            result = cacheHandle.get().addTag(tag);
        }
        return result;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.Taggable#clearTags()
     */
    public void clearTags() {
        if (cacheHandle != null && cacheHandle.get() != null) {
            cacheHandle.get().clearTags();
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.Taggable#containsTag(ch.entwine.weblounge.common.content.Tag)
     */
    public boolean containsTag(CacheTag tag) {
        if (cacheHandle != null && cacheHandle.get() != null) {
            return cacheHandle.get().containsTag(tag);
        }
        return false;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.Taggable#containsTag(java.lang.String)
     */
    public boolean containsTag(String name) {
        if (cacheHandle != null && cacheHandle.get() != null) {
            return cacheHandle.get().containsTag(name);
        }
        return false;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.Taggable#containsTag(java.lang.String,
     *      java.lang.String)
     */
    public boolean containsTag(String name, String value) {
        if (cacheHandle != null && cacheHandle.get() != null) {
            return cacheHandle.get().containsTag(name, value);
        }
        return false;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.Taggable#isTagged()
     */
    public boolean isTagged() {
        if (cacheHandle != null && cacheHandle.get() != null) {
            return cacheHandle.get().isTagged();
        }
        return false;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.Taggable#removeTag(ch.entwine.weblounge.common.content.Tag)
     */
    public boolean removeTag(CacheTag tag) {
        if (cacheHandle != null && cacheHandle.get() != null) {
            return cacheHandle.get().removeTag(tag);
        }
        return false;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.Taggable#removeTags(java.lang.String)
     */
    public boolean removeTags(String name) {
        if (cacheHandle != null && cacheHandle.get() != null) {
            return cacheHandle.get().removeTags(name);
        }
        return false;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.Taggable#removeTag(java.lang.String,
     *      java.lang.String)
     */
    public boolean removeTag(String name, String value) {
        if (cacheHandle != null && cacheHandle.get() != null) {
            return cacheHandle.get().removeTag(name, value);
        }
        return false;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.Taggable#tags()
     */
    public Iterator<CacheTag> tags() {
        if (cacheHandle != null && cacheHandle.get() != null) {
            return cacheHandle.get().tags();
        }
        return new ArrayList<CacheTag>().iterator();
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.Taggable#getTags()
     */
    public CacheTag[] getTags() {
        if (cacheHandle != null && cacheHandle.get() != null) {
            return cacheHandle.get().getTags();
        }
        return new CacheTag[] {};
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.request.WebloungeResponse#startResponse(ch.entwine.weblounge.common.request.CacheTag[],
     *      long, long)
     */
    public boolean startResponse(CacheTag[] tags, long expirationTime, long revalidationTime)
            throws IllegalStateException {
        if (!isValid || cache == null)
            return false;
        ResponseCache cache = this.cache.get();
        if (cache == null)
            return false;
        if (cacheHandle != null)
            throw new IllegalStateException("The response is already being cached");

        // Is the response in the cache?
        CacheHandle hdl = cache.startResponse(tags, request.get(), this, expirationTime, revalidationTime);
        if (hdl == null)
            return true;

        // It's not, meaning we need to do the processing ourselves
        cacheHandle = new WeakReference<CacheHandle>(hdl);
        return false;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.request.WebloungeResponse#endResponse()
     */
    public void endResponse() throws IllegalStateException {
        try {

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

            // Copy the response buffer to the cached response
            if (!submitted)
                submitResponseBuffer();

            // See if there is an active cache transaction
            if (cache == null)
                return;
            ResponseCache cache = this.cache.get();
            if (cache == null)
                return;
            if (cacheHandle == null || cacheHandle.get() == null)
                return;

            // End the response and have the output sent back to the client
            cache.endResponse(this);

        } catch (IOException e) {
            // The client closed the connection
        } finally {
            cacheHandle = null;
        }
    }

    /**
     * Submits the buffered response to the wrapped response's output stream. This
     * method returns gracefully if the response has already been submitted.
     * 
     * @throws IOException
     *           if submitting fails
     */
    private void submitResponseBuffer() throws IOException {

        // Has content been added?
        if (os == null)
            return;

        // Has the content been submitted already?
        if (submitted)
            return;

        // Is there an output stream that we can copy to?
        OutputStream clientOS = super.getOutputStream();
        if (clientOS == null)
            return;

        // Check if there are HTML header includes
        String response = new String(os.getContent(), DEFAULT_ENCODING);
        StringBuffer headersHTML = new StringBuffer();
        if (htmlHeaders != null) {
            for (HTMLHeadElement e : htmlHeaders) {
                String header = ConfigurationUtils.processTemplate(e.toHtml(), request.get().getSite(),
                        request.get().getEnvironment());
                headersHTML.append(header).append('\n');
            }
        }

        // Replace the marker with the actual headers
        int headersPlaceholderLocation = response.indexOf(HTML_HEADER_MARKER);
        if (headersPlaceholderLocation >= 1) {
            StringBuffer updatedResponse = new StringBuffer(response.substring(0, headersPlaceholderLocation));
            updatedResponse.append(headersHTML.toString());
            updatedResponse.append(response.substring(headersPlaceholderLocation + HTML_HEADER_MARKER.length()));
            response = updatedResponse.toString();
        }

        setContentLength(response.getBytes().length);
        IOUtils.write(response, clientOS, DEFAULT_ENCODING);
        clientOS.flush();

        os = null;
        submitted = true;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.request.WebloungeResponse#setModificationDate(Date)
     */
    @Override
    public Date setModificationDate(Date modificationDate) {
        if (modificationDate == null)
            return this.modificationDate;
        this.modificationDate = modificationDate.after(this.modificationDate) ? modificationDate
                : this.modificationDate;
        if (cacheHandle == null)
            return this.modificationDate;
        CacheHandle hdl = cacheHandle.get();
        if (hdl == null)
            return this.modificationDate;
        return hdl.setModificationDate(modificationDate);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.request.WebloungeResponse#getModificationDate()
     */
    @Override
    public Date getModificationDate() {
        if (cacheHandle == null)
            return modificationDate.getTime() > 0 ? modificationDate : new Date();
        CacheHandle hdl = cacheHandle.get();
        if (hdl == null)
            return modificationDate.getTime() > 0 ? modificationDate : new Date();
        return hdl.getModificationDate();
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.request.WebloungeResponse#setClientRevalidationTime(long)
     */
    public void setClientRevalidationTime(long revalidationTime) {
        if (cacheHandle == null)
            return;
        CacheHandle hdl = cacheHandle.get();
        if (hdl == null)
            return;
        hdl.setClientRevalidationTime(Math.min(revalidationTime, hdl.getClientRevalidationTime()));
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.request.WebloungeResponse#getClientRevalidationTime()
     */
    public long getClientRevalidationTime() {
        if (cacheHandle == null)
            return 0;
        CacheHandle hdl = cacheHandle.get();
        if (hdl == null)
            return 0;
        return hdl.getClientRevalidationTime();
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.request.WebloungeResponse#setCacheExpirationTime(long)
     */
    public void setCacheExpirationTime(long expirationTime) {
        if (cacheHandle == null)
            return;
        CacheHandle hdl = cacheHandle.get();
        if (hdl == null)
            return;
        hdl.setCacheExpirationTime(Math.min(expirationTime, hdl.getCacheExpirationTime()));

        // The recheck time can't be longer than the valid time
        setClientRevalidationTime(expirationTime);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.request.WebloungeResponse#isCached()
     */
    public boolean isCached() {
        return cache != null && cache.get() != null;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.request.WebloungeResponse#getCacheExpirationTime()
     */
    public long getCacheExpirationTime() {
        if (cacheHandle == null)
            return 0;
        CacheHandle hdl = cacheHandle.get();
        if (hdl == null)
            return 0;
        return hdl.getCacheExpirationTime();
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.request.WebloungeResponse#invalidate()
     */
    public void invalidate() {
        isValid = false;
        if (cache != null) {
            ResponseCache c = cache.get();
            if (c != null) {
                c.invalidate(this);
            }
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.request.WebloungeResponse#isValid()
     */
    public boolean isValid() {
        return isValid;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.request.WebloungeResponse#addHTMLHeader(ch.entwine.weblounge.common.content.page.HTMLHeadElement)
     */
    @Override
    public void addHTMLHeader(HTMLHeadElement header) {
        if (htmlHeaders == null)
            htmlHeaders = new ArrayList<HTMLHeadElement>();
        if (!htmlHeaders.contains(header))
            htmlHeaders.add(header);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.request.WebloungeResponse#getHTMLHeaders()
     */
    @Override
    public HTMLHeadElement[] getHTMLHeaders() {
        if (htmlHeaders == null)
            return new HTMLHeadElement[] {};
        return htmlHeaders.toArray(new HTMLHeadElement[htmlHeaders.size()]);
    }

    /**
     * {@inheritDoc}
     * 
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        if (request == null || request.get() == null)
            return super.toString();
        return request.get().toString();
    }

}