diff --git a/src/java/grails/plugin/cache/SerializableByteArrayOutputStream.java b/src/java/grails/plugin/cache/SerializableByteArrayOutputStream.java new file mode 100644 index 00000000..b9e95451 --- /dev/null +++ b/src/java/grails/plugin/cache/SerializableByteArrayOutputStream.java @@ -0,0 +1,109 @@ +/* Copyright 2012 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; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +/** + * A Serializable version of java.io.ByteArrayOutputStream. + * + * @author Burt Beckwith + */ +public class SerializableByteArrayOutputStream extends SerializableOutputStream { + + private static final long serialVersionUID = 1; + + protected byte[] buf; + protected int count; + + public SerializableByteArrayOutputStream() { + this(32); + } + + public SerializableByteArrayOutputStream(int size) { + if (size < 0) { + throw new IllegalArgumentException("Negative initial size: " + size); + } + buf = new byte[size]; + } + + @Override + public synchronized void write(int b) { + int newcount = count + 1; + if (newcount > buf.length) { + buf = copyOf(Math.max(buf.length << 1, newcount)); + } + buf[count] = (byte)b; + count = newcount; + } + + @Override + public synchronized void write(byte[] b, int off, int len) { + if ((off < 0) || (off > b.length) || (len < 0) || + ((off + len) > b.length) || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } + + if (len == 0) { + return; + } + + int newcount = count + len; + if (newcount > buf.length) { + buf = copyOf(Math.max(buf.length << 1, newcount)); + } + System.arraycopy(b, off, buf, count, len); + count = newcount; + } + + public synchronized void writeTo(OutputStream out) throws IOException { + out.write(buf, 0, count); + } + + public synchronized void reset() { + count = 0; + } + + public synchronized byte[] toByteArray() { + return copyOf(count); + } + + // ByteArrayOutputStream uses Arrays.copyOf which is only in Java 6, that's inlined here. + protected byte[] copyOf(int newLength) { + byte[] copy = new byte[newLength]; + System.arraycopy(buf, 0, copy, 0, Math.min(buf.length, newLength)); + return copy; + } + + public synchronized int size() { + return count; + } + + @Override + public synchronized String toString() { + return new String(buf, 0, count); + } + + public synchronized String toString(String charsetName) throws UnsupportedEncodingException { + return new String(buf, 0, count, charsetName); + } + + @Override + public void close() throws IOException { + // no-op + } +} diff --git a/src/java/grails/plugin/cache/SerializableOutputStream.java b/src/java/grails/plugin/cache/SerializableOutputStream.java new file mode 100644 index 00000000..bfde1225 --- /dev/null +++ b/src/java/grails/plugin/cache/SerializableOutputStream.java @@ -0,0 +1,28 @@ +/* Copyright 2012 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; + +import java.io.OutputStream; +import java.io.Serializable; + +/** + * Abstract base class for serializable OutputStream classes. + * + * @author Burt Beckwith + */ +@SuppressWarnings("serial") +public abstract class SerializableOutputStream extends OutputStream implements Serializable { + // no methods +} diff --git a/src/java/grails/plugin/cache/web/GenericResponseWrapper.java b/src/java/grails/plugin/cache/web/GenericResponseWrapper.java index 807bd745..e26cf5bf 100644 --- a/src/java/grails/plugin/cache/web/GenericResponseWrapper.java +++ b/src/java/grails/plugin/cache/web/GenericResponseWrapper.java @@ -14,11 +14,11 @@ */ package grails.plugin.cache.web; +import grails.plugin.cache.SerializableOutputStream; import grails.plugin.cache.web.Header.Type; import grails.plugin.cache.web.filter.FilterServletOutputStream; import java.io.IOException; -import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Serializable; @@ -34,7 +34,6 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; -import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** @@ -54,8 +53,6 @@ public class GenericResponseWrapper extends HttpServletResponseWrapper implement private static final long serialVersionUID = 1; - private final Logger log = LoggerFactory.getLogger(getClass()); - private int statusCode = SC_OK; private int contentLength; private String contentType; @@ -63,13 +60,13 @@ public class GenericResponseWrapper extends HttpServletResponseWrapper implement String.CASE_INSENSITIVE_ORDER); private final List cookies = new ArrayList(); private ServletOutputStream out; - private PrintWriter writer; + private transient PrintWriter writer; private boolean disableFlushBuffer = true; /** * Creates a GenericResponseWrapper */ - public GenericResponseWrapper(final HttpServletResponse response, final OutputStream outputStream) { + public GenericResponseWrapper(final HttpServletResponse response, final SerializableOutputStream outputStream) { super(response); out = new FilterServletOutputStream(outputStream); } @@ -128,7 +125,7 @@ public void sendRedirect(String string) throws IOException { @Override public void setStatus(final int code, final String msg) { statusCode = code; - log.warn("Discarding message because this method is deprecated."); + LoggerFactory.getLogger(getClass()).warn("Discarding message because this method is deprecated."); super.setStatus(code); } diff --git a/src/java/grails/plugin/cache/web/HttpDateFormatter.java b/src/java/grails/plugin/cache/web/HttpDateFormatter.java index 958f2d09..9406c53e 100644 --- a/src/java/grails/plugin/cache/web/HttpDateFormatter.java +++ b/src/java/grails/plugin/cache/web/HttpDateFormatter.java @@ -14,13 +14,13 @@ */ package grails.plugin.cache.web; +import java.io.Serializable; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.TimeZone; -import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** @@ -52,9 +52,9 @@ * @author Greg Luck * @author Burt Beckwith */ -public class HttpDateFormatter { +public class HttpDateFormatter implements Serializable { - protected final Logger log = LoggerFactory.getLogger(getClass()); + private static final long serialVersionUID = 1; protected final SimpleDateFormat httpDateFormat; @@ -92,7 +92,8 @@ public synchronized Date parseDateFromHttpDate(String date) { return httpDateFormat.parse(date); } catch (ParseException e) { - log.debug("ParseException on date {}. 1/1/1970 will be returned", date); + LoggerFactory.getLogger(getClass()).debug( + "ParseException on date {}. 1/1/1970 will be returned", date); return new Date(0); } } diff --git a/src/java/grails/plugin/cache/web/PageInfo.java b/src/java/grails/plugin/cache/web/PageInfo.java index bbf50d2f..c14a4d9c 100644 --- a/src/java/grails/plugin/cache/web/PageInfo.java +++ b/src/java/grails/plugin/cache/web/PageInfo.java @@ -34,11 +34,17 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import net.sf.cglib.proxy.Callback; + +import org.codehaus.groovy.grails.plugins.web.api.ControllersApi; +import org.codehaus.groovy.grails.web.servlet.GrailsFlashScope; import org.codehaus.groovy.grails.web.servlet.HttpHeaders; -import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.aop.PointcutAdvisor; +import org.springframework.aop.TargetSource; import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import org.springframework.web.servlet.FlashMap; /** * A Serializable representation of a {@link HttpServletResponse}. @@ -59,12 +65,10 @@ public class PageInfo implements Serializable { protected static final int GZIP_MAGIC_NUMBER_BYTE_2 = -117; protected static final long ONE_YEAR_IN_SECONDS = 60 * 60 * 24 * 365; - private final Logger log = LoggerFactory.getLogger(getClass()); - private final HttpDateFormatter httpDateFormatter = new HttpDateFormatter(); private final List> responseHeaders = new ArrayList>(); private final List serializableCookies = new ArrayList(); - private Map requestAttributes; + private Map requestAttributes; private String contentType; private byte[] gzippedBody; private byte[] ungzippedBody; @@ -90,7 +94,7 @@ public class PageInfo implements Serializable { public PageInfo(final int statusCode, final String contentType, final byte[] body, boolean storeGzipped, long timeToLiveSeconds, final Collection> headers, @SuppressWarnings("unused") final Collection cookies, - Map requestAttributes) throws AlreadyGzippedException { + Map requestAttributes) throws AlreadyGzippedException { if (headers != null) { responseHeaders.addAll(headers); @@ -101,7 +105,8 @@ public PageInfo(final int statusCode, final String contentType, final byte[] bod this.contentType = contentType; this.storeGzipped = storeGzipped; this.statusCode = statusCode; - this.requestAttributes = requestAttributes; + setCacheableRequestAttributes(requestAttributes); + // bug 2630970 // extractCookies(cookies); @@ -122,7 +127,7 @@ public PageInfo(final int statusCode, final String contentType, final byte[] bod } } catch (IOException e) { - log.error("Error ungzipping gzipped body", e); + LoggerFactory.getLogger(getClass()).error("Error ungzipping gzipped body", e); } } @@ -306,7 +311,7 @@ public long getTimeToLiveSeconds() { return timeToLiveSeconds; } - public Map getRequestAttributes() { + public Map getRequestAttributes() { return Collections.unmodifiableMap(requestAttributes); } @@ -388,4 +393,36 @@ public Map getCacheDirectives() { } return directives; } + + private void setCacheableRequestAttributes(Map attributes) { + requestAttributes = new HashMap(); + + for (Map.Entry entry : attributes.entrySet()) { + Serializable value = entry.getValue(); + + if (value instanceof GrailsFlashScope) { + continue; + } + if (value instanceof FlashMap) { + continue; + } + if (value instanceof HttpServletResponse) { + continue; + } + if (value instanceof ControllersApi) { + continue; + } + if (value instanceof PointcutAdvisor || value instanceof PointcutAdvisor[]) { + continue; + } + if (value instanceof Callback || value instanceof Callback[]) { + continue; + } + if (value instanceof TargetSource) { + continue; + } + + requestAttributes.put(entry.getKey(), value); + } + } } diff --git a/src/java/grails/plugin/cache/web/filter/FilterServletOutputStream.java b/src/java/grails/plugin/cache/web/filter/FilterServletOutputStream.java index 08dc5782..0770687e 100644 --- a/src/java/grails/plugin/cache/web/filter/FilterServletOutputStream.java +++ b/src/java/grails/plugin/cache/web/filter/FilterServletOutputStream.java @@ -14,8 +14,10 @@ */ package grails.plugin.cache.web.filter; +import grails.plugin.cache.SerializableOutputStream; + import java.io.IOException; -import java.io.OutputStream; +import java.io.Serializable; import javax.servlet.ServletOutputStream; @@ -27,11 +29,13 @@ * @author Greg Luck * @author Burt Beckwith */ -public class FilterServletOutputStream extends ServletOutputStream { +public class FilterServletOutputStream extends ServletOutputStream implements Serializable { + + private static final long serialVersionUID = 1; - protected OutputStream stream; + protected SerializableOutputStream stream; - public FilterServletOutputStream(final OutputStream stream) { + public FilterServletOutputStream(final SerializableOutputStream stream) { this.stream = stream; } diff --git a/src/java/grails/plugin/cache/web/filter/PageFragmentCachingFilter.java b/src/java/grails/plugin/cache/web/filter/PageFragmentCachingFilter.java index d1ffbc4f..8ebf14ce 100644 --- a/src/java/grails/plugin/cache/web/filter/PageFragmentCachingFilter.java +++ b/src/java/grails/plugin/cache/web/filter/PageFragmentCachingFilter.java @@ -14,6 +14,7 @@ */ package grails.plugin.cache.web.filter; +import grails.plugin.cache.SerializableByteArrayOutputStream; import grails.plugin.cache.Timer; import grails.plugin.cache.web.ContentCacheParameters; import grails.plugin.cache.web.GenericResponseWrapper; @@ -21,7 +22,6 @@ import grails.plugin.cache.web.PageInfo; import grails.plugin.cache.web.SerializableCookie; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Method; @@ -313,7 +313,7 @@ private void releaseCacheLocks(Map> op private PageInfo buildPage(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { // Invoke the next entity in the chain - ByteArrayOutputStream out = new ByteArrayOutputStream(); + SerializableByteArrayOutputStream out = new SerializableByteArrayOutputStream(); GenericResponseWrapper wrapper = new GenericResponseWrapper(response, out); Map cacheableRequestAttributes = new HashMap(); @@ -786,8 +786,8 @@ private static class CacheStatus { final boolean updateRequired; final ValueWrapper valueWrapper; - CacheStatus(Map cUpdates, boolean updateRequired, ValueWrapper valueWrapper) { - this.updates = cUpdates; + CacheStatus(Map updates, boolean updateRequired, ValueWrapper valueWrapper) { + this.updates = updates; this.updateRequired = updateRequired; this.valueWrapper = valueWrapper; }