| /* Copyright 2012-2013 SpringSource. |
| * |
| * Licensed 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 grails.plugin.cache.web; |
| |
| import grails.plugin.cache.SerializableOutputStream; |
| import grails.plugin.cache.web.Header.Type; |
| import javassist.util.proxy.*; |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| import java.io.IOException; |
| import java.io.OutputStreamWriter; |
| import java.io.PrintWriter; |
| import java.io.Serializable; |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.TreeMap; |
| |
| import javax.servlet.ServletOutputStream; |
| import javax.servlet.http.Cookie; |
| import javax.servlet.http.HttpServletResponse; |
| import javax.servlet.http.HttpServletResponseWrapper; |
| |
| |
| /** |
| * Provides a wrapper for {@link javax.servlet.http.HttpServletResponseWrapper}. |
| * <p/> |
| * It is used to wrap the real Response so that we can modify it after that the |
| * target of the request has delivered its response. |
| * <p/> |
| * It uses the Wrapper pattern. |
| * |
| * Based on net.sf.ehcache.constructs.web.GenericResponseWrapper. |
| * |
| * @author Greg Luck |
| * @author Burt Beckwith |
| */ |
| @SuppressWarnings("deprecation") |
| public class GenericResponseWrapper extends HttpServletResponseWrapper implements Serializable { |
| |
| private static final long serialVersionUID = 1; |
| public static final Log LOG = LogFactory.getLog(GenericResponseWrapper.class); |
| |
| protected int statusCode = SC_OK; |
| protected int contentLength; |
| protected String contentType; |
| protected final Map<String, List<Serializable>> headersMap = new TreeMap<String, List<Serializable>>( |
| String.CASE_INSENSITIVE_ORDER); |
| protected final List<Cookie> cookies = new ArrayList<Cookie>(); |
| protected ServletOutputStream out; |
| protected transient PrintWriter writer; |
| protected boolean disableFlushBuffer = true; |
| |
| static { |
| ProxyFactory.classLoaderProvider = new ProxyFactory.ClassLoaderProvider() { |
| public ClassLoader get(ProxyFactory pf) { |
| return Thread.currentThread().getContextClassLoader(); |
| } |
| }; |
| } |
| |
| /** |
| * Creates a GenericResponseWrapper |
| */ |
| public GenericResponseWrapper(final HttpServletResponse response, final SerializableOutputStream outputStream) { |
| super(response); |
| |
| ProxyFactory factory = new ProxyFactory(); |
| |
| factory.setSuperclass(ServletOutputStream.class); |
| Class clazz = factory.createClass(); |
| |
| try { |
| this.out = (ServletOutputStream)clazz.newInstance(); |
| MethodHandler handler = new MethodHandler() { |
| |
| public Object invoke(Object o, Method method, Method forwarder, Object[] args) throws Throwable { |
| if("write".equals(method.getName())) { |
| switch(args.length) { |
| case 1: |
| Object arg = args[0]; |
| if(arg instanceof Integer) { |
| outputStream.write((Integer)arg); |
| } |
| else { |
| outputStream.write((byte[])arg); |
| } |
| case 3: |
| outputStream.write((byte[])args[0], (Integer)args[1], (Integer)args[2]); |
| } |
| return null; |
| } |
| else { |
| return forwarder.invoke(o, args); |
| } |
| } |
| }; |
| |
| ((ProxyObject) out).setHandler(handler); |
| } |
| catch(Exception e) { |
| throw new RuntimeException("Cannot create output stream proxy: " + e.getMessage(), e); |
| } |
| |
| |
| } |
| |
| @Override |
| public ServletOutputStream getOutputStream() { |
| return out; |
| } |
| |
| @Override |
| public void setStatus(final int code) { |
| statusCode = code; |
| super.setStatus(code); |
| } |
| |
| /** |
| * Send the error. If the response is not ok, most of the logic is bypassed |
| * and the error is sent raw Also, the content is not cached. |
| * |
| * @param code the status code |
| * @param string the error message |
| * @throws IOException |
| */ |
| @Override |
| public void sendError(int code, String string) throws IOException { |
| statusCode = code; |
| super.sendError(code, string); |
| } |
| |
| /** |
| * Send the error. If the response is not ok, most of the logic is bypassed |
| * and the error is sent raw Also, the content is not cached. |
| * |
| * @param code the status code |
| * @throws IOException |
| */ |
| @Override |
| public void sendError(int code) throws IOException { |
| statusCode = code; |
| super.sendError(code); |
| } |
| |
| /** |
| * Send the redirect. If the response is not ok, most of the logic is |
| * bypassed and the error is sent raw. Also, the content is not cached. |
| * |
| * @param string the URL to redirect to |
| * @throws IOException |
| */ |
| @Override |
| public void sendRedirect(String string) throws IOException { |
| statusCode = HttpServletResponse.SC_MOVED_TEMPORARILY; |
| super.sendRedirect(string); |
| } |
| |
| @Override |
| public void setStatus(final int code, final String msg) { |
| statusCode = code; |
| LOG.warn("Discarding message because this method is deprecated."); |
| super.setStatus(code); |
| } |
| |
| // don't add @Override since it's only a method as of Servlet 3.0 |
| public int getStatus() { |
| return statusCode; |
| } |
| |
| @Override |
| public void setContentLength(final int length) { |
| contentLength = length; |
| super.setContentLength(length); |
| } |
| |
| public int getContentLength() { |
| return contentLength; |
| } |
| |
| @Override |
| public void setContentType(final String type) { |
| contentType = type; |
| super.setContentType(type); |
| } |
| |
| @Override |
| public String getContentType() { |
| return contentType; |
| } |
| |
| @Override |
| public PrintWriter getWriter() throws IOException { |
| if (writer == null) { |
| writer = new PrintWriter(new OutputStreamWriter(out, getCharacterEncoding()), true); |
| } |
| return writer; |
| } |
| |
| @Override |
| public void addHeader(String name, String value) { |
| List<Serializable> values = headersMap.get(name); |
| if (values == null) { |
| values = new LinkedList<Serializable>(); |
| headersMap.put(name, values); |
| } |
| values.add(value); |
| |
| super.addHeader(name, value); |
| } |
| |
| @Override |
| public void setHeader(String name, String value) { |
| LinkedList<Serializable> values = new LinkedList<Serializable>(); |
| values.add(value); |
| headersMap.put(name, values); |
| |
| super.setHeader(name, value); |
| } |
| |
| @Override |
| public void addDateHeader(String name, long date) { |
| List<Serializable> values = headersMap.get(name); |
| if (values == null) { |
| values = new LinkedList<Serializable>(); |
| headersMap.put(name, values); |
| } |
| values.add(date); |
| |
| super.addDateHeader(name, date); |
| } |
| |
| @Override |
| public void setDateHeader(String name, long date) { |
| LinkedList<Serializable> values = new LinkedList<Serializable>(); |
| values.add(date); |
| headersMap.put(name, values); |
| |
| super.setDateHeader(name, date); |
| } |
| |
| @Override |
| public void addIntHeader(String name, int value) { |
| List<Serializable> values = headersMap.get(name); |
| if (values == null) { |
| values = new LinkedList<Serializable>(); |
| headersMap.put(name, values); |
| } |
| values.add(value); |
| |
| super.addIntHeader(name, value); |
| } |
| |
| @Override |
| public void setIntHeader(String name, int value) { |
| LinkedList<Serializable> values = new LinkedList<Serializable>(); |
| values.add(value); |
| headersMap.put(name, values); |
| |
| super.setIntHeader(name, value); |
| } |
| |
| public Collection<Header<? extends Serializable>> getAllHeaders() { |
| List<Header<? extends Serializable>> headers = new LinkedList<Header<? extends Serializable>>(); |
| |
| for (Map.Entry<String, List<Serializable>> headerEntry : headersMap.entrySet()) { |
| String name = headerEntry.getKey(); |
| for (Serializable value : headerEntry.getValue()) { |
| |
| // Null Check for value before doing value.getClass() |
| // FIX for: http://jira.grails.org/browse/GPCACHE-37 |
| if(value != null) { |
| |
| Type type = Header.Type.determineType(value.getClass()); |
| switch (type) { |
| case STRING: |
| headers.add(new Header<String>(name, (String)value)); |
| break; |
| case DATE: |
| headers.add(new Header<Long>(name, (Long)value)); |
| break; |
| case INT: |
| headers.add(new Header<Integer>(name, (Integer)value)); |
| break; |
| default: |
| throw new IllegalArgumentException("No mapping for Header.Type: " + type); |
| } |
| } |
| } |
| } |
| |
| return headers; |
| } |
| |
| @Override |
| public void addCookie(final Cookie cookie) { |
| cookies.add(cookie); |
| super.addCookie(cookie); |
| } |
| |
| public Collection<Cookie> getCookies() { |
| return cookies; |
| } |
| |
| @Override |
| public void flushBuffer() throws IOException { |
| flush(); |
| |
| // doing this might leads to response already committed exception when the |
| // PageInfo has not yet built but the buffer already flushed. Happens in WebLogic |
| // when a servlet forward to a JSP page and the forward method trigger a flush |
| // before it forwarded to the JSP disableFlushBuffer for that purpose is 'true' by default |
| // EHC-447 |
| if (!disableFlushBuffer) { |
| super.flushBuffer(); |
| } |
| } |
| |
| @Override |
| public void reset() { |
| super.reset(); |
| cookies.clear(); |
| headersMap.clear(); |
| statusCode = SC_OK; |
| contentType = null; |
| contentLength = 0; |
| } |
| |
| /** |
| * Flushes all the streams for this response. |
| */ |
| public void flush() throws IOException { |
| if (writer != null) { |
| writer.flush(); |
| } |
| out.flush(); |
| } |
| |
| /** |
| * Is the wrapped reponse's buffer flushing disabled? |
| * |
| * @return true if the wrapped reponse's buffer flushing disabled |
| */ |
| public boolean isDisableFlushBuffer() { |
| return disableFlushBuffer; |
| } |
| |
| /** |
| * Set if the wrapped reponse's buffer flushing should be disabled. |
| * |
| * @param disable true if the wrapped reponse's buffer flushing should be disabled |
| */ |
| public void setDisableFlushBuffer(boolean disable) { |
| disableFlushBuffer = disable; |
| } |
| } |