/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.wicket.protocol.http;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.DateFormat;
import java.util.*;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
/**
* Mock servlet response. Implements all of the methods from the standard
* HttpServletResponse class plus helper methods to aid viewing the generated response.
*
* @author Chris Turner
*/
public class SwarmMockHttpServletResponse implements HttpServletResponse
{
private static final int MODE_BINARY = 1;
private static final int MODE_NONE = 0;
private static final int MODE_TEXT = 2;
private ByteArrayOutputStream byteStream;
private String characterEncoding = "UTF-8";
private int code = HttpServletResponse.SC_OK;
private final List<Cookie> cookies = new ArrayList<Cookie>();
private String errorMessage = null;
private final Map<String, List<String>> headers = new LinkedHashMap<String, List<String>>();
private Locale locale = null;
private int mode = MODE_NONE;
private PrintWriter printWriter;
private String redirectLocation = null;
private ServletOutputStream servletStream;
private int status = HttpServletResponse.SC_OK;
private StringWriter stringWriter;
private final SwarmMockHttpServletRequest servletRequest;
/**
* Create the response object.
*
* @param servletRequest
*/
public SwarmMockHttpServletResponse(SwarmMockHttpServletRequest servletRequest)
{
this.servletRequest = servletRequest;
initialize();
}
/**
* Add a cookie to the response.
*
* @param cookie
* The cookie to add
*/
public void addCookie(final Cookie cookie)
{
cookies.add(cookie);
}
/**
* Add a date header.
*
* @param name
* The header value
* @param l
* The long value
*/
public void addDateHeader(String name, long l)
{
DateFormat df = DateFormat.getDateInstance(DateFormat.FULL);
addHeader(name, df.format(new Date(l)));
}
/**
* Add the given header value, including an additional entry if one already exists.
*
* @param name
* The name for the header
* @param value
* The value for the header
*/
public void addHeader(final String name, final String value)
{
List<String> list = headers.get(name);
if (list == null)
{
list = new ArrayList<String>(1);
headers.put(name, list);
}
list.add(value);
}
/**
* Add an int header value.
*
* @param name
* The header name
* @param i
* The value
*/
public void addIntHeader(final String name, final int i)
{
addHeader(name, "" + i);
}
/**
* Check if the response contains the given header name.
*
* @param name
* The name to check
* @return Whether header in response or not
*/
public boolean containsHeader(final String name)
{
return headers.containsKey(name);
}
/**
* Encode the redirectLocation URL. Does no changes as this test implementation uses
* cookie based url tracking.
*
* @param url
* The url to encode
* @return The encoded url
*/
@Deprecated
public String encodeRedirectUrl(final String url)
{
return url;
}
/**
* Encode the redirectLocation URL. Does no changes as this test implementation uses
* cookie based url tracking.
*
* @param url
* The url to encode
* @return The encoded url
*/
public String encodeRedirectURL(final String url)
{
return url;
}
/**
* Encode the URL. Does no changes as this test implementation uses cookie based url
* tracking.
*
* @param url
* The url to encode
* @return The encoded url
*/
@Deprecated
public String encodeUrl(final String url)
{
return url;
}
/**
* Encode the URL. Does no changes as this test implementation uses cookie based url
* tracking.
*
* @param url
* The url to encode
* @return The encoded url
*/
public String encodeURL(final String url)
{
return url;
}
/**
* Flush the buffer.
*
* @throws IOException
*/
public void flushBuffer() throws IOException
{
}
/**
* Get the binary content that was written to the servlet stream.
*
* @return The binary content
*/
public byte[] getBinaryContent()
{
return byteStream.toByteArray();
}
/**
* Return the current buffer size
*
* @return The buffer size
*/
public int getBufferSize()
{
if (mode == MODE_NONE)
{
return 0;
}
else if (mode == MODE_BINARY)
{
return byteStream.size();
}
else
{
return stringWriter.getBuffer().length();
}
}
/**
* Get the character encoding of the response.
*
* @return The character encoding
*/
public String getCharacterEncoding()
{
return characterEncoding;
}
/**
* Get the response code for this request.
*
* @return The response code
*/
public int getCode()
{
return code;
}
/**
* Get all of the cookies that have been added to the response.
*
* @return The collection of cookies
*/
public Collection<Cookie> getCookies()
{
return cookies;
}
/**
* Get the text document that was written as part of this response.
*
* @return The document
*/
public String getDocument()
{
if (mode == MODE_BINARY)
{
return new String(byteStream.toByteArray());
}
else
{
return stringWriter.getBuffer().toString();
}
}
/**
* Get the error message.
*
* @return The error message, or null if no message
*/
public String getErrorMessage()
{
return errorMessage;
}
/**
* Return the value of the given named header.
*
* @param name
* The header name
* @return The value, or null
*/
public String getHeader(final String name)
{
List<String> l = headers.get(name);
if (l == null || l.size() < 1)
{
return null;
}
else
{
return l.get(0);
}
}
/**
* Get the names of all of the headers.
*
* @return The header names
*/
public Set<String> getHeaderNames()
{
return headers.keySet();
}
/**
* Get the encoded locale
*
* @return The locale
*/
public Locale getLocale()
{
return locale;
}
/**
* Get the output stream for writing binary data from the servlet.
*
* @return The binary output stream.
*/
public ServletOutputStream getOutputStream()
{
if (mode == MODE_TEXT)
{
throw new IllegalArgumentException("Can't write binary after already selecting text");
}
mode = MODE_BINARY;
return servletStream;
}
/**
* Get the location that was redirected to.
*
* @return The redirect location, or null if not a redirect
*/
public String getRedirectLocation()
{
return redirectLocation;
}
/**
* Get the status code.
*
* @return The status code
*/
public int getStatus()
{
return status;
}
/**
* Get the print writer for writing text output for this response.
*
* @return The writer
* @throws IOException
* Not used
*/
public PrintWriter getWriter() throws IOException
{
if (mode == MODE_BINARY)
{
throw new IllegalArgumentException("Can't write text after already selecting binary");
}
mode = MODE_TEXT;
return printWriter;
}
/**
* Reset the response ready for reuse.
*/
public void initialize()
{
cookies.clear();
headers.clear();
code = HttpServletResponse.SC_OK;
errorMessage = null;
redirectLocation = null;
status = HttpServletResponse.SC_OK;
characterEncoding = "UTF-8";
locale = null;
byteStream = new ByteArrayOutputStream();
servletStream = new ServletOutputStream()
{
@Override
public void write(int b)
{
byteStream.write(b);
}
};
stringWriter = new StringWriter();
printWriter = new PrintWriter(stringWriter)
{
@Override
public void close()
{
// Do nothing
}
@Override
public void flush()
{
// Do nothing
}
};
mode = MODE_NONE;
}
/**
* Always returns false.
*
* @return Always false
*/
public boolean isCommitted()
{
return false;
}
/**
* Return whether the servlet returned an error code or not.
*
* @return Whether an error occurred or not
*/
public boolean isError()
{
return (code != HttpServletResponse.SC_OK);
}
/**
* Check whether the response was redirected or not.
*
* @return Whether the state was redirected or not
*/
public boolean isRedirect()
{
return (redirectLocation != null);
}
/**
* Delegate to initialize method.
*/
public void reset()
{
initialize();
}
/**
* Clears the buffer.
*/
public void resetBuffer()
{
if (mode == MODE_BINARY)
{
byteStream.reset();
}
else if (mode == MODE_TEXT)
{
stringWriter.getBuffer().delete(0, stringWriter.getBuffer().length());
}
}
/**
* Send an error code. This implementation just sets the internal error state
* information.
*
* @param errorCode
* The code
* @throws IOException
* Not used
*/
public void sendError(final int errorCode) throws IOException
{
this.code = errorCode;
errorMessage = null;
}
/**
* Send an error code. This implementation just sets the internal error state
* information.
*
* @param errorCode
* The error code
* @param msg
* The error message
* @throws IOException
* Not used
*/
public void sendError(final int errorCode, final String msg) throws IOException
{
this.code = errorCode;
errorMessage = msg;
}
/**
* @see org.apache.wicket.Request#getURL()
*/
private String getURL()
{
/*
* Servlet 2.3 specification : Servlet Path: The path section that directly
* corresponds to the mapping which activated this request. This path starts with
* a "/" character except in the case where the request is matched with the "/*"
* pattern, in which case it is the empty string. PathInfo: The part of the
* request path that is not part of the Context Path or the Servlet Path. It is
* either null if there is no extra path, or is a string with a leading "/".
*/
String url = servletRequest.getServletPath();
final String pathInfo = servletRequest.getPathInfo();
if (pathInfo != null)
{
url += pathInfo;
}
final String queryString = servletRequest.getQueryString();
if (queryString != null)
{
url += ("?" + queryString);
}
// If url is non-empty it will start with '/', which we should lose
if (url.length() > 0 && url.charAt(0) == '/')
{
// Remove leading '/'
url = url.substring(1);
}
return url;
}
/**
* Indicate sending of a redirectLocation to a particular named resource. This
* implementation just keeps hold of the redirectLocation info and makes it available
* for query.
*
* @param location
* The location to redirectLocation to
* @throws IOException
* Not used
*/
public void sendRedirect(String location) throws IOException
{
// If the location starts with ../
if (location.startsWith("../"))
{
// Test if the current url has a / in it. (a mount)
String url = getURL();
int index = url.lastIndexOf("/");
if (index != -1)
{
// Then we have to recalculate what the real redirect is for the
// next request
// which is just getContext() + getServletPath() + "/" +
// location;
url = url.substring(0, index + 1) + location;
url = RequestUtils.removeDoubleDots(url);
// stril the servlet path again from it.
index = url.indexOf("/");
location = url.substring(index + 1);
}
}
redirectLocation = location;
}
/**
* Method ignored.
*
* @param size
* The size
*/
public void setBufferSize(final int size)
{
}
/**
* Set the character encoding.
*
* @param characterEncoding
* The character encoding
*/
public void setCharacterEncoding(final String characterEncoding)
{
this.characterEncoding = characterEncoding;
}
/**
* Set the content length.
*
* @param length
* The length
*/
public void setContentLength(final int length)
{
setIntHeader("Content-Length", length);
}
/**
* Set the content type.
*
* @param type
* The content type
*/
public void setContentType(final String type)
{
setHeader("Content-Type", type);
}
public String getContentType()
{
return getHeader("Content-Type");
}
/**
* Set a date header.
*
* @param name
* The header name
* @param l
* The long value
*/
public void setDateHeader(final String name, final long l)
{
setHeader(name, formatDate(l));
}
/**
* @param l
* @return
*/
public static String formatDate(long l)
{
StringBuffer _dateBuffer = new StringBuffer(32);
Calendar _calendar = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
_calendar.setTimeInMillis(l);
formatDate(_dateBuffer, _calendar, false);
return _dateBuffer.toString();
}
/* BEGIN: This code comes from Jetty 6.1.1 */
private static String[] DAYS = {"Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
private static String[] MONTHS =
{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan"};
/**
* Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" or
* "EEE, dd-MMM-yy HH:mm:ss 'GMT'"for cookies
*
* @param buf
* @param calendar
* @param cookie
*/
public static void formatDate(StringBuffer buf, Calendar calendar, boolean cookie)
{
// "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
// "EEE, dd-MMM-yy HH:mm:ss 'GMT'", cookie
int day_of_week = calendar.get(Calendar.DAY_OF_WEEK);
int day_of_month = calendar.get(Calendar.DAY_OF_MONTH);
int month = calendar.get(Calendar.MONTH);
int year = calendar.get(Calendar.YEAR);
int century = year / 100;
year = year % 100;
int epoch = (int) ((calendar.getTimeInMillis() / 1000) % (60 * 60 * 24));
int seconds = epoch % 60;
epoch = epoch / 60;
int minutes = epoch % 60;
int hours = epoch / 60;
buf.append(DAYS[day_of_week]);
buf.append(',');
buf.append(' ');
append2digits(buf, day_of_month);
if (cookie)
{
buf.append('-');
buf.append(MONTHS[month]);
buf.append('-');
append2digits(buf, year);
}
else
{
buf.append(' ');
buf.append(MONTHS[month]);
buf.append(' ');
append2digits(buf, century);
append2digits(buf, year);
}
buf.append(' ');
append2digits(buf, hours);
buf.append(':');
append2digits(buf, minutes);
buf.append(':');
append2digits(buf, seconds);
buf.append(" GMT");
}
/**
* @param buf
* @param i
*/
public static void append2digits(StringBuffer buf, int i)
{
if (i < 100)
{
buf.append((char) (i / 10 + '0'));
buf.append((char) (i % 10 + '0'));
}
}
/* END: This code comes from Jetty 6.1.1 */
/**
* Set the given header value.
*
* @param name
* The name for the header
* @param value
* The value for the header
*/
public void setHeader(final String name, final String value)
{
List<String> l = new ArrayList<String>(1);
l.add(value);
headers.put(name, l);
}
/**
* Set an int header value.
*
* @param name
* The header name
* @param i
* The value
*/
public void setIntHeader(final String name, final int i)
{
setHeader(name, "" + i);
}
/**
* Set the locale in the response header.
*
* @param locale
* The locale
*/
public void setLocale(final Locale locale)
{
this.locale = locale;
}
/**
* Set the status for this response.
*
* @param status
* The status
*/
public void setStatus(final int status)
{
this.status = status;
}
/**
* Set the status for this response.
*
* @param status
* The status
* @param msg
* The message
* @deprecated
*/
@Deprecated
public void setStatus(final int status, final String msg)
{
setStatus(status);
}
}