/*
* 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.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map.Entry;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.util.collections.MultiMap;
import org.apache.wicket.util.io.StringBufferWriter;
import org.apache.wicket.util.string.AppendingStringBuffer;
/**
* Implementation of {@link HttpServletResponse} that saves the output in a string buffer. This is
* used in REDIRECT_TO_BUFFER render strategy to create the buffer of the output that can be held on
* to until the redirect part of the render strategy.
*
* @author jcompagner
*/
class BufferedHttpServletResponse implements HttpServletResponse
{
/** the print writer for the response */
private StringBufferWriter sbw = new StringBufferWriter();
private PrintWriter pw = new PrintWriter(sbw);
/** cookies list */
private List<Cookie> cookies;
/** status code */
private int status = -1;
/** headers map */
private MultiMap<String, Object> headers;
/** the real response for encoding the url */
private HttpServletResponse realResponse;
private String redirect;
private String contentType;
private byte[] byteBuffer;
private Locale locale;
private String encoding;
/**
* Constructor.
*
* @param realResponse
* The real response for encoding the url
*/
public BufferedHttpServletResponse(HttpServletResponse realResponse)
{
this.realResponse = realResponse;
}
/**
* @see javax.servlet.http.HttpServletResponse#addCookie(javax.servlet.http.Cookie)
*/
@Override
public void addCookie(Cookie cookie)
{
isOpen();
if (cookies == null)
{
cookies = new ArrayList<>(2);
}
cookies.add(cookie);
}
/**
* @see javax.servlet.http.HttpServletResponse#containsHeader(java.lang.String)
*/
@Override
public boolean containsHeader(String name)
{
isOpen();
if (headers == null)
{
return false;
}
return headers.containsKey(name);
}
/**
* @see javax.servlet.http.HttpServletResponse#encodeURL(java.lang.String)
*/
@Override
public String encodeURL(String url)
{
isOpen();
return realResponse.encodeURL(url);
}
/**
* @see javax.servlet.http.HttpServletResponse#encodeRedirectURL(java.lang.String)
*/
@Override
public String encodeRedirectURL(String url)
{
isOpen();
return realResponse.encodeRedirectURL(url);
}
/**
* @see javax.servlet.http.HttpServletResponse#encodeUrl(java.lang.String)
* @deprecated
*/
@Override
@Deprecated
public String encodeUrl(String url)
{
isOpen();
return realResponse.encodeURL(url);
}
/**
* @see javax.servlet.http.HttpServletResponse#encodeRedirectUrl(java.lang.String)
* @deprecated
*/
@Override
@Deprecated
public String encodeRedirectUrl(String url)
{
isOpen();
return realResponse.encodeRedirectURL(url);
}
/**
* @see javax.servlet.http.HttpServletResponse#sendError(int, java.lang.String)
*/
@Override
public void sendError(int sc, String msg) throws IOException
{
isOpen();
realResponse.sendError(sc, msg);
}
/**
* @see javax.servlet.http.HttpServletResponse#sendError(int)
*/
@Override
public void sendError(int sc) throws IOException
{
isOpen();
realResponse.sendError(sc);
}
/**
* @see javax.servlet.http.HttpServletResponse#sendRedirect(java.lang.String)
*/
@Override
public void sendRedirect(String location) throws IOException
{
isOpen();
redirect = location;
}
/**
* @return The redirect url
*/
public String getRedirectUrl()
{
isOpen();
return redirect;
}
private void testAndCreateHeaders()
{
isOpen();
if (headers == null)
{
headers = new MultiMap<>();
}
}
private void isOpen()
{
if (realResponse == null)
{
throw new WicketRuntimeException("the buffered servlet response already closed.");
}
}
/**
* @see javax.servlet.http.HttpServletResponse#setDateHeader(java.lang.String, long)
*/
@Override
public void setDateHeader(String name, long date)
{
testAndCreateHeaders();
headers.replaceValues(name, date);
}
/**
* @see javax.servlet.http.HttpServletResponse#addDateHeader(java.lang.String, long)
*/
@Override
public void addDateHeader(String name, long date)
{
testAndCreateHeaders();
headers.addValue(name, date);
}
/**
* @see javax.servlet.http.HttpServletResponse#setHeader(java.lang.String, java.lang.String)
*/
@Override
public void setHeader(String name, String value)
{
testAndCreateHeaders();
headers.replaceValues(name, value);
}
/**
* @see javax.servlet.http.HttpServletResponse#addHeader(java.lang.String, java.lang.String)
*/
@Override
public void addHeader(String name, String value)
{
testAndCreateHeaders();
headers.addValue(name, value);
}
/**
* @see javax.servlet.http.HttpServletResponse#setIntHeader(java.lang.String, int)
*/
@Override
public void setIntHeader(String name, int value)
{
testAndCreateHeaders();
headers.replaceValues(name, value);
}
/**
* @see javax.servlet.http.HttpServletResponse#addIntHeader(java.lang.String, int)
*/
@Override
public void addIntHeader(String name, int value)
{
testAndCreateHeaders();
headers.addValue(name, value);
}
/**
* @see javax.servlet.http.HttpServletResponse#setStatus(int)
*/
@Override
public void setStatus(int statusCode)
{
status = statusCode;
}
/**
* @see javax.servlet.http.HttpServletResponse#setStatus(int, java.lang.String)
* @deprecated use setStatus(int) instead
*/
@Override
@Deprecated
public void setStatus(int sc, String sm)
{
setStatus(sc);
}
/**
* @see javax.servlet.ServletResponse#getCharacterEncoding()
*/
@Override
public String getCharacterEncoding()
{
isOpen();
return encoding;
}
/**
* Set the character encoding to use for the output.
*
* @param encoding
*/
@Override
public void setCharacterEncoding(String encoding)
{
this.encoding = encoding;
}
/**
* @see javax.servlet.ServletResponse#getOutputStream()
*/
@Override
public ServletOutputStream getOutputStream() throws IOException
{
throw new UnsupportedOperationException("Cannot get output stream on BufferedResponse");
}
/**
* @see javax.servlet.ServletResponse#getWriter()
*/
@Override
public PrintWriter getWriter() throws IOException
{
isOpen();
return pw;
}
/**
* @see javax.servlet.ServletResponse#setContentLength(int)
*/
@Override
public void setContentLength(int len)
{
isOpen();
// ignored will be calculated when the buffer is really streamed.
}
@Override
public void setContentLengthLong(long len)
{
isOpen();
// ignored will be calculated when the buffer is really streamed.
}
/**
* @see javax.servlet.ServletResponse#setContentType(java.lang.String)
*/
@Override
public void setContentType(String type)
{
isOpen();
contentType = type;
}
/**
* @return The content type
*/
@Override
public String getContentType()
{
return contentType;
}
/**
* @see javax.servlet.ServletResponse#setBufferSize(int)
*/
@Override
public void setBufferSize(int size)
{
isOpen();
// ignored every thing will be buffered
}
/**
* @see javax.servlet.ServletResponse#getBufferSize()
*/
@Override
public int getBufferSize()
{
isOpen();
return Integer.MAX_VALUE;
}
/**
* @see javax.servlet.ServletResponse#flushBuffer()
*/
@Override
public void flushBuffer() throws IOException
{
isOpen();
}
/**
* @see javax.servlet.ServletResponse#resetBuffer()
*/
@Override
public void resetBuffer()
{
isOpen();
sbw.reset();
}
/**
* @see javax.servlet.ServletResponse#isCommitted()
*/
@Override
public boolean isCommitted()
{
return pw == null;
}
/**
* @see javax.servlet.ServletResponse#reset()
*/
@Override
public void reset()
{
resetBuffer();
headers = null;
cookies = null;
}
/**
* @see javax.servlet.ServletResponse#setLocale(java.util.Locale)
*/
@Override
public void setLocale(Locale loc)
{
isOpen();
locale = loc;
}
/**
* @see javax.servlet.ServletResponse#getLocale()
*/
@Override
public Locale getLocale()
{
isOpen();
if (locale == null)
{
return realResponse.getLocale();
}
return locale;
}
/**
* @return The length of the complete string buffer
*/
public int getContentLength()
{
isOpen();
return sbw.getStringBuffer().length();
}
/**
*
*/
public void close()
{
isOpen();
pw.close();
byteBuffer = convertToCharset(sbw.getStringBuffer(), getCharacterEncoding());
pw = null;
sbw = null;
realResponse = null;
}
/**
* Convert the string into the output encoding required
*
* @param output
*
* @param encoding
* The output encoding
* @return byte[] The encoded characters converted into bytes
*/
private static byte[] convertToCharset(final AppendingStringBuffer output, final String encoding)
{
if (encoding == null)
{
throw new WicketRuntimeException("Internal error: encoding must not be null");
}
final ByteArrayOutputStream baos = new ByteArrayOutputStream((int)(output.length() * 1.2));
final OutputStreamWriter osw;
final byte[] bytes;
try
{
osw = new OutputStreamWriter(baos, encoding);
osw.write(output.getValue(), 0, output.length());
osw.close();
bytes = baos.toByteArray();
}
catch (Exception ex)
{
throw new WicketRuntimeException("Can't convert response to charset: " + encoding, ex);
}
return bytes;
}
/**
* @param servletResponse
* @throws IOException
*/
public void writeTo(HttpServletResponse servletResponse) throws IOException
{
if (status != -1)
{
servletResponse.setStatus(status);
}
if (headers != null)
{
for (Entry<String, List<Object>> stringObjectEntry : headers.entrySet())
{
String name = stringObjectEntry.getKey();
List<Object> values = stringObjectEntry.getValue();
for (Object value : values)
{
addHeader(name, value, servletResponse);
}
}
}
if (cookies != null)
{
for (Cookie cookie : cookies)
{
servletResponse.addCookie(cookie);
}
}
if (locale != null)
{
servletResponse.setLocale(locale);
}
// got a buffered response; now write it
servletResponse.setContentLength(byteBuffer.length);
servletResponse.setContentType(contentType);
final OutputStream out = servletResponse.getOutputStream();
out.write(byteBuffer);
out.close();
}
/**
* @param name
* Name of the header to set
* @param value
* The value can be String/Long/Int
* @param servletResponse
* The response to set it to.
*/
private static void setHeader(String name, Object value, HttpServletResponse servletResponse)
{
if (value instanceof String)
{
servletResponse.setHeader(name, (String)value);
}
else if (value instanceof Long)
{
servletResponse.setDateHeader(name, (Long)value);
}
else if (value instanceof Integer)
{
servletResponse.setIntHeader(name, (Integer)value);
}
}
/**
* @param name
* Name of the header to set
* @param value
* The value can be String/Long/Int
* @param servletResponse
* The response to set it to.
*/
private static void addHeader(String name, Object value, HttpServletResponse servletResponse)
{
if (value instanceof String)
{
servletResponse.addHeader(name, (String)value);
}
else if (value instanceof Long)
{
servletResponse.addDateHeader(name, (Long)value);
}
else if (value instanceof Integer)
{
servletResponse.addIntHeader(name, (Integer)value);
}
}
/**
* @see javax.servlet.http.HttpServletResponse#setStatus(int)
* @return status
*/
public int getStatus()
{
return status;
}
/**
* @see javax.servlet.http.HttpServletRequest#getHeader(java.lang.String)
* @param name
* @return the first header with name
*/
public String getHeader(String name)
{
Object value = headers.getFirstValue(name);
if (value == null)
{
return null;
}
return value.toString();
}
/**
* @see javax.servlet.http.HttpServletRequest#getHeaders(java.lang.String)
* @param name
* @return all headers with name
*/
public Collection<String> getHeaders(String name)
{
List<Object> values = headers.get(name);
if (values == null)
{
return Collections.emptyList();
}
List<String> ret = new ArrayList<>(values.size());
for (Object value : values)
{
ret.add(value.toString());
}
return ret;
}
/**
* @see javax.servlet.http.HttpServletRequest#getHeaderNames()
* @return all header names
*/
public Collection<String> getHeaderNames()
{
return Collections.unmodifiableCollection(headers.keySet());
}
}