| /* |
| * 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.mock; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.text.DateFormat; |
| import java.util.ArrayList; |
| import java.util.Calendar; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.GregorianCalendar; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Set; |
| import java.util.TimeZone; |
| |
| import jakarta.servlet.ServletOutputStream; |
| import jakarta.servlet.WriteListener; |
| import jakarta.servlet.http.Cookie; |
| import jakarta.servlet.http.HttpServletResponse; |
| |
| import org.apache.wicket.protocol.http.IMetaDataBufferingWebResponse; |
| import org.apache.wicket.request.http.WebResponse; |
| import org.apache.wicket.util.value.ValueMap; |
| |
| |
| /** |
| * 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 MockHttpServletResponse implements HttpServletResponse, IMetaDataBufferingWebResponse |
| { |
| 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 final List<Cookie> cookies = new ArrayList<Cookie>(); |
| |
| private String errorMessage = null; |
| |
| private final ValueMap headers = new ValueMap(); |
| |
| 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; |
| |
| /** |
| * Create the response object. |
| * |
| * @param servletRequest |
| */ |
| public MockHttpServletResponse(MockHttpServletRequest servletRequest) |
| { |
| initialize(); |
| } |
| |
| /** |
| * Add a cookie to the response. |
| * |
| * @param cookie |
| * The cookie to add |
| */ |
| @Override |
| public void addCookie(final Cookie cookie) |
| { |
| // remove any potential duplicates |
| // see http://www.ietf.org/rfc/rfc2109.txt, p.4.3.3 |
| Iterator<Cookie> iterator = cookies.iterator(); |
| while (iterator.hasNext()) |
| { |
| Cookie old = iterator.next(); |
| if (Cookies.isEqual(cookie, old)) |
| { |
| iterator.remove(); |
| } |
| } |
| cookies.add(cookie); |
| } |
| |
| /** |
| * Add a date header. |
| * |
| * @param name |
| * The header value |
| * @param l |
| * The long value |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| @SuppressWarnings("unchecked") |
| public void addHeader(final String name, final String value) |
| { |
| List<String> list = (List<String>)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 |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| public String encodeURL(final String url) |
| { |
| return url; |
| } |
| |
| /** |
| * Flush the buffer. |
| * |
| * @throws IOException |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| public String getCharacterEncoding() |
| { |
| return characterEncoding; |
| } |
| |
| |
| /** |
| * Get all of the cookies that have been added to the response. |
| * |
| * @return The collection of cookies |
| */ |
| public List<Cookie> getCookies() |
| { |
| List<Cookie> copies = new ArrayList<Cookie>(); |
| for (Cookie cookie : cookies) |
| { |
| copies.add(Cookies.copyOf(cookie)); |
| } |
| return copies; |
| } |
| |
| /** |
| * 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 |
| */ |
| @Override |
| @SuppressWarnings("unchecked") |
| public String getHeader(final String name) |
| { |
| List<String> l = (List<String>)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 |
| */ |
| @Override |
| public Set<String> getHeaderNames() |
| { |
| return headers.keySet(); |
| } |
| |
| /** |
| * Get the encoded locale |
| * |
| * @return The locale |
| */ |
| @Override |
| public Locale getLocale() |
| { |
| return locale; |
| } |
| |
| /** |
| * Get the output stream for writing binary data from the servlet. |
| * |
| * @return The binary output stream. |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| public int getStatus() |
| { |
| return status; |
| } |
| |
| /** |
| * Get the print writer for writing text output for this response. |
| * |
| * @return The writer |
| * @throws IOException |
| * Not used |
| */ |
| @Override |
| 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(); |
| errorMessage = null; |
| redirectLocation = null; |
| status = HttpServletResponse.SC_OK; |
| characterEncoding = "UTF-8"; |
| locale = null; |
| |
| byteStream = new ByteArrayOutputStream(); |
| servletStream = new ServletOutputStream() |
| { |
| @Override |
| public boolean isReady() |
| { |
| return true; |
| } |
| |
| @Override |
| public void setWriteListener(WriteListener writeListener) |
| { |
| } |
| |
| @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 |
| */ |
| @Override |
| 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 (status != 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. |
| */ |
| @Override |
| public void reset() |
| { |
| initialize(); |
| } |
| |
| /** |
| * Clears the buffer. |
| */ |
| @Override |
| 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 code |
| * The code |
| * @throws IOException |
| * Not used |
| */ |
| @Override |
| public void sendError(final int code) throws IOException |
| { |
| status = code; |
| errorMessage = null; |
| } |
| |
| /** |
| * Send an error code. This implementation just sets the internal error state information. |
| * |
| * @param code |
| * The error code |
| * @param msg |
| * The error message |
| * @throws IOException |
| * Not used |
| */ |
| @Override |
| public void sendError(final int code, final String msg) throws IOException |
| { |
| status = code; |
| errorMessage = msg; |
| } |
| |
| /** |
| * 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 |
| */ |
| @Override |
| public void sendRedirect(String location) throws IOException |
| { |
| redirectLocation = location; |
| status = HttpServletResponse.SC_FOUND; |
| } |
| |
| /** |
| * Method ignored. |
| * |
| * @param size |
| * The size |
| */ |
| @Override |
| public void setBufferSize(final int size) |
| { |
| } |
| |
| /** |
| * Set the character encoding. |
| * |
| * @param characterEncoding |
| * The character encoding |
| */ |
| @Override |
| public void setCharacterEncoding(final String characterEncoding) |
| { |
| this.characterEncoding = characterEncoding; |
| } |
| |
| /** |
| * Set the content length. |
| * |
| * @param length |
| * The length |
| */ |
| @Override |
| public void setContentLength(final int length) |
| { |
| setIntHeader("Content-Length", length); |
| } |
| |
| @Override |
| public void setContentLengthLong(long len) |
| { |
| setContentLength((int) len); |
| } |
| |
| /** |
| * Set the content type. |
| * |
| * @param type |
| * The content type |
| */ |
| @Override |
| public void setContentType(final String type) |
| { |
| setHeader("Content-Type", type); |
| } |
| |
| /** |
| * @return value of content-type header |
| */ |
| @Override |
| public String getContentType() |
| { |
| return getHeader("Content-Type"); |
| } |
| |
| /** |
| * Set a date header. |
| * |
| * @param name |
| * The header name |
| * @param l |
| * The long value |
| */ |
| @Override |
| public void setDateHeader(final String name, final long l) |
| { |
| setHeader(name, formatDate(l)); |
| } |
| |
| /** |
| * @param l |
| * @return formatted date |
| */ |
| public static String formatDate(long l) |
| { |
| StringBuilder _dateBuffer = new StringBuilder(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(StringBuilder 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(StringBuilder 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 |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| public void setIntHeader(final String name, final int i) |
| { |
| setHeader(name, "" + i); |
| } |
| |
| /** |
| * Set the locale in the response header. |
| * |
| * @param locale |
| * The locale |
| */ |
| @Override |
| public void setLocale(final Locale locale) |
| { |
| this.locale = locale; |
| } |
| |
| /** |
| * Set the status for this response. |
| * |
| * @param status |
| * The status |
| */ |
| @Override |
| public void setStatus(final int status) |
| { |
| this.status = status; |
| } |
| |
| /** |
| * Set the status for this response. |
| * |
| * @param status |
| * The status |
| * @param msg |
| * The message |
| * @deprecated |
| */ |
| @Override |
| @Deprecated |
| public void setStatus(final int status, final String msg) |
| { |
| setStatus(status); |
| } |
| |
| /** |
| * @return binary response |
| */ |
| public String getBinaryResponse() |
| { |
| String ctheader = getHeader("Content-Length"); |
| if (ctheader == null) |
| { |
| return getDocument(); |
| } |
| else |
| { |
| return getDocument().substring(0, Integer.valueOf(ctheader)); |
| } |
| } |
| |
| /** |
| * @param name |
| * @return headers with given name |
| */ |
| @Override |
| public Collection<String> getHeaders(String name) |
| { |
| return Collections.singletonList(headers.get(name).toString()); |
| } |
| |
| @Override |
| public void writeMetaData(WebResponse webResponse) |
| { |
| for (Cookie cookie : cookies) |
| { |
| webResponse.addCookie(cookie); |
| } |
| for (String name : headers.keySet()) |
| { |
| webResponse.setHeader(name, headers.get(name).toString()); |
| } |
| webResponse.setStatus(status); |
| } |
| } |