/* * 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()); } }