/*
* 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.testing;
import ch.entwine.weblounge.common.impl.request.DelegatingServletOutputStream;
import ch.entwine.weblounge.common.impl.request.HeaderValueCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
/**
* Mock implementation of the Servlet 2.4 API
* {@link javax.servlet.http.HttpServletResponse} interface.
*/
public class MockHttpServletResponse implements HttpServletResponse {
/** Logging facility */
private static final Logger logger = LoggerFactory.getLogger(MockHttpServletResponse.class);
/** Default http server port */
public static final int DEFAULT_SERVER_PORT = 80;
/** Determines the beginning of a character set definition */
private static final String CHARSET_PREFIX = "charset=";
/** Determines whether access to the output stream is allowed */
private boolean outputStreamAccessAllowed = true;
/** Determines whether writing to the output stream is allowed */
private boolean writerAccessAllowed = true;
/** The response character encoding */
private String characterEncoding = null;
/** The response */
private final ByteArrayOutputStream response = new ByteArrayOutputStream();
/** The servlet output stream */
private final ServletOutputStream outputStream = new ResponseServletOutputStream(this.response);
/** Response writer */
private PrintWriter writer = null;
/** The content length */
private int contentLength = 0;
/** The content mime type */
private String contentType = null;
/** Size of the output buffer */
private int bufferSize = 4096;
/** Determines whether the response has already been committed */
private boolean committed = false;
/** The response locale */
private Locale locale = Locale.getDefault();
/** The list of cookies */
private final List<Cookie> cookies = new ArrayList<Cookie>();
/** The response headers */
private final Map<String, HeaderValueCollection> headers = new HashMap<String, HeaderValueCollection>();
/** The response status, default to {@link HttpServletResponse#SC_OK} */
private int status = HttpServletResponse.SC_OK;
/** The error message */
private String errorMessage = null;
/** The redirect url, used in conjunction with the mock request dispatcher */
private String redirectedUrl = null;
/** The forward url, used in conjunction with the mock request dispatcher */
private String forwardedUrl = null;
/** The include url, used in conjunction with the mock request dispatcher */
private String includedUrl = null;
/**
* Creates a new mock servlet response with a default character encoding of
* <code>utf-8</code>.
*/
public MockHttpServletResponse() {
characterEncoding = "utf-8";
}
/**
* Set whether {@link #getOutputStream()} access is allowed.
* <p>
* Default is true.
*/
public void setOutputStreamAccessAllowed(boolean outputStreamAccessAllowed) {
this.outputStreamAccessAllowed = outputStreamAccessAllowed;
}
/**
* Return whether {@link #getOutputStream()} access is allowed.
*/
public boolean isOutputStreamAccessAllowed() {
return this.outputStreamAccessAllowed;
}
/**
* Set whether {@link #getWriter()} access is allowed.
* <p>
* Default is true.
*/
public void setWriterAccessAllowed(boolean writerAccessAllowed) {
this.writerAccessAllowed = writerAccessAllowed;
}
/**
* Return whether {@link #getOutputStream()} access is allowed.
*/
public boolean isWriterAccessAllowed() {
return this.writerAccessAllowed;
}
/**
* {@inheritDoc}
*
* @see javax.servlet.ServletResponse#setCharacterEncoding(java.lang.String)
*/
public void setCharacterEncoding(String characterEncoding) {
this.characterEncoding = characterEncoding;
}
/**
* {@inheritDoc}
*
* @see javax.servlet.ServletResponse#getCharacterEncoding()
*/
public String getCharacterEncoding() {
return this.characterEncoding;
}
/**
* {@inheritDoc}
*
* @see javax.servlet.ServletResponse#getOutputStream()
*/
public ServletOutputStream getOutputStream() {
if (!this.outputStreamAccessAllowed) {
throw new IllegalStateException("OutputStream access not allowed");
}
return this.outputStream;
}
/**
* {@inheritDoc}
*
* @see javax.servlet.ServletResponse#getWriter()
*/
public PrintWriter getWriter() throws UnsupportedEncodingException {
if (!this.writerAccessAllowed) {
throw new IllegalStateException("Writer access not allowed");
}
if (this.writer == null) {
Writer targetWriter = (this.characterEncoding != null ? new OutputStreamWriter(this.response, this.characterEncoding) : new OutputStreamWriter(this.response));
this.writer = new ResponsePrintWriter(targetWriter);
}
return this.writer;
}
/**
* Returns access to the response as a <code>byte</code> array.
*
* @return the response body as a <code>byte</code> array
*/
public byte[] getContentAsByteArray() {
flushBuffer();
return this.response.toByteArray();
}
/**
* Returns access to the response as a <code>String</code>.
*
* @return the response body as a string
* @throws UnsupportedEncodingException
* if the platform does not support the response encoding
*/
public String getContentAsString() throws UnsupportedEncodingException {
flushBuffer();
return (this.characterEncoding != null) ? this.response.toString(this.characterEncoding) : this.response.toString();
}
/**
* {@inheritDoc}
*
* @see javax.servlet.ServletResponse#setContentLength(int)
*/
public void setContentLength(int contentLength) {
this.contentLength = contentLength;
}
/**
* Returns the value that was set as the content length.
*
* @return the content length
*/
public int getContentLength() {
return this.contentLength;
}
/**
* {@inheritDoc}
*
* @see javax.servlet.ServletResponse#setContentType(java.lang.String)
*/
public void setContentType(String contentType) {
this.contentType = contentType;
if (contentType != null) {
int charsetIndex = contentType.toLowerCase().indexOf(CHARSET_PREFIX);
if (charsetIndex != -1) {
String encoding = contentType.substring(charsetIndex + CHARSET_PREFIX.length());
setCharacterEncoding(encoding);
}
}
}
/**
* {@inheritDoc}
*
* @see javax.servlet.ServletResponse#getContentType()
*/
public String getContentType() {
return this.contentType;
}
/**
* {@inheritDoc}
*
* @see javax.servlet.ServletResponse#setBufferSize(int)
*/
public void setBufferSize(int bufferSize) {
this.bufferSize = bufferSize;
}
/**
* {@inheritDoc}
*
* @see javax.servlet.ServletResponse#getBufferSize()
*/
public int getBufferSize() {
return this.bufferSize;
}
/**
* {@inheritDoc}
*
* @see javax.servlet.ServletResponse#flushBuffer()
*/
public void flushBuffer() {
setCommitted(true);
}
/**
* {@inheritDoc}
*
* @see javax.servlet.ServletResponse#resetBuffer()
*/
public void resetBuffer() {
if (isCommitted()) {
throw new IllegalStateException("Cannot reset buffer - response is already committed");
}
this.response.reset();
}
/**
* Switches the implementation to committing the response once the buffer size
* has been exceeded.
*/
void setCommittedIfBufferSizeExceeded() {
int bufSize = getBufferSize();
if (bufSize > 0 && this.response.size() > bufSize) {
setCommitted(true);
}
}
/**
* Marks the response as being committed.
*
* @param committed
* <code>true</code> to mark the response as being committed
*/
public void setCommitted(boolean committed) {
this.committed = committed;
}
/**
* {@inheritDoc}
*
* @see javax.servlet.ServletResponse#isCommitted()
*/
public boolean isCommitted() {
return this.committed;
}
/**
* {@inheritDoc}
*
* @see javax.servlet.ServletResponse#reset()
*/
public void reset() {
resetBuffer();
this.characterEncoding = null;
this.contentLength = 0;
this.contentType = null;
this.locale = null;
this.cookies.clear();
this.headers.clear();
this.status = HttpServletResponse.SC_OK;
this.errorMessage = null;
}
/**
* {@inheritDoc}
*
* @see javax.servlet.ServletResponse#setLocale(java.util.Locale)
*/
public void setLocale(Locale locale) {
this.locale = locale;
}
/**
* {@inheritDoc}
*
* @see javax.servlet.ServletResponse#getLocale()
*/
public Locale getLocale() {
return this.locale;
}
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServletResponse#addCookie(javax.servlet.http.Cookie)
*/
public void addCookie(Cookie cookie) {
if (cookie == null)
throw new IllegalArgumentException("Cookie must not be null");
this.cookies.add(cookie);
}
/**
* Returns the cookies that have been set on the response.
*
* @return the cookies
*/
public Cookie[] getCookies() {
return this.cookies.toArray(new Cookie[this.cookies.size()]);
}
/**
* Returns the cookie with name <code>name</code> or <code>null</code> if no
* such cookie was defined.
*
* @param name
* the cookie name
* @return the cookie
*/
public Cookie getCookie(String name) {
if (name == null)
throw new IllegalArgumentException("Cookie name must not be null");
for (Iterator<Cookie> it = this.cookies.iterator(); it.hasNext();) {
Cookie cookie = it.next();
if (name.equals(cookie.getName())) {
return cookie;
}
}
return null;
}
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServletResponse#containsHeader(java.lang.String)
*/
public boolean containsHeader(String name) {
return (HeaderValueCollection.getByName(this.headers, name) != null);
}
/**
* Return the names of all specified headers as a Set of Strings.
*
* @return the <code>Set of header name Strings, or an empty Set if none
*/
public Set<String> getHeaderNames() {
return this.headers.keySet();
}
/**
* Return the primary value for the given header, if any.
* <p>
* Will return the first value in case of multiple values.
*
* @param name
* the name of the header
* @return the associated header value, or <code>null if none
*/
public Object getHeader(String name) {
HeaderValueCollection header = HeaderValueCollection.getByName(this.headers, name);
return (header != null ? header.getValue() : null);
}
/**
* Return all values for the given header as a List of value objects.
*
* @param name
* the name of the header
* @return the associated header values, or an empty List if none
*/
public List<Object> getHeaderValues(String name) {
HeaderValueCollection header = HeaderValueCollection.getByName(this.headers, name);
return (header != null ? header.getValues() : new ArrayList<Object>());
}
/**
* The default implementation returns the given URL String as-is.
* <p>
* Can be overridden in subclasses, appending a session id or the like.
*/
public String encodeURL(String url) {
return url;
}
/**
* The default implementation delegates to {@link #encodeURL}, returning the
* given URL String as-is.
* <p>
* Can be overridden in subclasses, appending a session id or the like in a
* redirect-specific fashion. For general URL encoding rules, override the
* common {@link #encodeURL} method instead, appyling to redirect URLs as well
* as to general URLs.
*/
public String encodeRedirectURL(String url) {
return encodeURL(url);
}
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServletResponse#encodeUrl(java.lang.String)
*/
public String encodeUrl(String url) {
return encodeURL(url);
}
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServletResponse#encodeRedirectUrl(java.lang.String)
*/
public String encodeRedirectUrl(String url) {
return encodeRedirectURL(url);
}
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServletResponse#sendError(int,
* java.lang.String)
*/
public void sendError(int status, String errorMessage) throws IOException {
if (isCommitted()) {
logger.debug("Unable to send error " + status + ": response is already committed");
return;
}
this.status = status;
this.errorMessage = errorMessage;
setCommitted(true);
}
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServletResponse#sendError(int)
*/
public void sendError(int status) throws IOException {
if (isCommitted()) {
logger.debug("Unable to send error " + status + ": response is already committed");
return;
}
this.status = status;
setCommitted(true);
}
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServletResponse#sendRedirect(java.lang.String)
*/
public void sendRedirect(String url) throws IOException {
if (isCommitted()) {
logger.warn("Unable to send redirect: response is already committed");
return;
}
if (url == null)
throw new IllegalArgumentException("Redirect URL must not be null");
this.redirectedUrl = url;
setCommitted(true);
}
/**
* Returns the redirected url.
*
* @return the url
*/
public String getRedirectedUrl() {
return this.redirectedUrl;
}
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServletResponse#setDateHeader(java.lang.String,
* long)
*/
public void setDateHeader(String name, long value) {
setHeaderValue(name, new Long(value));
}
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServletResponse#addDateHeader(java.lang.String,
* long)
*/
public void addDateHeader(String name, long value) {
addHeaderValue(name, new Long(value));
}
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServletResponse#setHeader(java.lang.String,
* java.lang.String)
*/
public void setHeader(String name, String value) {
setHeaderValue(name, value);
}
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServletResponse#addHeader(java.lang.String,
* java.lang.String)
*/
public void addHeader(String name, String value) {
addHeaderValue(name, value);
}
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServletResponse#setIntHeader(java.lang.String,
* int)
*/
public void setIntHeader(String name, int value) {
setHeaderValue(name, new Integer(value));
}
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServletResponse#addIntHeader(java.lang.String,
* int)
*/
public void addIntHeader(String name, int value) {
addHeaderValue(name, new Integer(value));
}
/**
* Sets the value of the response header identified by <code>name</code>.
*
* @param name
* the the header name
* @param value
* the header value
*/
private void setHeaderValue(String name, Object value) {
doAddHeaderValue(name, value, true);
}
/**
* Adds a value to the list of header values.
*
* @param name
* the header name
* @param value
* the additional header value
*/
private void addHeaderValue(String name, Object value) {
doAddHeaderValue(name, value, false);
}
/**
* Adds the given value to the response header, optionally replacing any
* header values that were there previously.
*
* @param name
* the header name
* @param value
* the header value
* @param replace
* <code>true</code> to replace existing headers
*/
private void doAddHeaderValue(String name, Object value, boolean replace) {
HeaderValueCollection header = HeaderValueCollection.getByName(this.headers, name);
if (value == null)
throw new IllegalArgumentException("Header value must not be null");
if (header == null) {
header = new HeaderValueCollection();
this.headers.put(name, header);
}
if (replace) {
header.setValue(value);
} else {
header.addValue(value);
}
}
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServletResponse#setStatus(int)
*/
public void setStatus(int status) {
this.status = status;
}
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServletResponse#setStatus(int,
* java.lang.String)
*/
public void setStatus(int status, String errorMessage) {
this.status = status;
this.errorMessage = errorMessage;
}
/**
* Returns the response state.
*
* @return the response state
*/
public int getStatus() {
return this.status;
}
/**
* Returns the error message or <code>null</code> if no header has been
* reported so far.
*
* @return the error message
*/
public String getErrorMessage() {
return this.errorMessage;
}
/**
* Sets the forwarded url for the mock request dispatcher.
*
* @param forwardedUrl
* the forwarded url
*/
public void setForwardedUrl(String forwardedUrl) {
this.forwardedUrl = forwardedUrl;
}
/**
* Returns the forwarded url or <code>null</code> if no url has been set so
* far.
*
* @return the forwarded url
*/
public String getForwardedUrl() {
return this.forwardedUrl;
}
/**
* Sets the included url for the mock request dispatcher.
*
* @param includedUrl
* the include url
*/
public void setIncludedUrl(String includedUrl) {
this.includedUrl = includedUrl;
}
/**
* Returns the included url or <code>null</code> if no url has been set so
* far.
*
* @return the included url
*/
public String getIncludedUrl() {
return this.includedUrl;
}
/**
* Inner class that adapts the ServletOutputStream to mark the response as
* committed once the buffer size is exceeded.
*/
private class ResponseServletOutputStream extends DelegatingServletOutputStream {
public ResponseServletOutputStream(OutputStream out) {
super(out);
}
@Override
public void write(int b) throws IOException {
super.write(b);
super.flush();
setCommittedIfBufferSizeExceeded();
}
@Override
public void flush() throws IOException {
super.flush();
setCommitted(true);
}
}
/**
* Inner class that adapts the PrintWriter to mark the response as committed
* once the buffer size is exceeded.
*/
private final class ResponsePrintWriter extends PrintWriter {
public ResponsePrintWriter(Writer out) {
super(out, true);
}
/**
* {@inheritDoc}
*
* @see java.io.PrintWriter#write(char[], int, int)
*/
@Override
public void write(char[] buf, int off, int len) {
super.write(buf, off, len);
super.flush();
setCommittedIfBufferSizeExceeded();
}
/**
* {@inheritDoc}
*
* @see java.io.PrintWriter#write(java.lang.String, int, int)
*/
@Override
public void write(String s, int off, int len) {
super.write(s, off, len);
super.flush();
setCommittedIfBufferSizeExceeded();
}
/**
* {@inheritDoc}
*
* @see java.io.PrintWriter#write(int)
*/
@Override
public void write(int c) {
super.write(c);
super.flush();
setCommittedIfBufferSizeExceeded();
}
/**
* {@inheritDoc}
*
* @see java.io.PrintWriter#flush()
*/
@Override
public void flush() {
super.flush();
setCommitted(true);
}
}
}