Revision: 646
http://stripes.svn.sourceforge.net/stripes/?rev=646&view=rev
Author: bengunter
Date: 2007-12-05 21:13:14 -0800 (Wed, 05 Dec 2007)
Log Message:
-----------
STS-449: Add annotation to control client-side caching. The new @HttpCache
annotation allows one to turn caching on or off and to expire a document after
a number of seconds.
Modified Paths:
--------------
trunk/stripes/src/net/sourceforge/stripes/config/DefaultConfiguration.java
Added Paths:
-----------
trunk/stripes/src/net/sourceforge/stripes/action/HttpCache.java
trunk/stripes/src/net/sourceforge/stripes/controller/HttpCacheInterceptor.java
Added: trunk/stripes/src/net/sourceforge/stripes/action/HttpCache.java
===================================================================
--- trunk/stripes/src/net/sourceforge/stripes/action/HttpCache.java
(rev 0)
+++ trunk/stripes/src/net/sourceforge/stripes/action/HttpCache.java
2007-12-06 05:13:14 UTC (rev 646)
@@ -0,0 +1,62 @@
+/* Copyright 2007 Ben Gunter
+ *
+ * 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 net.sourceforge.stripes.action;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ * This annotation can be applied to an event handler method or to an [EMAIL
PROTECTED] ActionBean} class to
+ * suggest to the HTTP client how it should cache the response. Classes will
inherit this annotation
+ * from their superclass. Method-level annotations override class-level
annotations. This means, for
+ * example, that applying [EMAIL PROTECTED] @HttpCache(allow=false)} to an
[EMAIL PROTECTED] ActionBean} class turns off
+ * client-side caching for all events except those that are annotated with
+ * [EMAIL PROTECTED] @HttpCache(allow=true)}.
+ * </p>
+ * <p>
+ * Some examples:
+ * <ul>
+ * <li>[EMAIL PROTECTED] @HttpCache} - Same behavior as if the annotation were
not present. No headers are
+ * set.</li>
+ * <li>[EMAIL PROTECTED] @HttpCache(allow=true)} - Same as above.</li>
+ * <li>[EMAIL PROTECTED] @HttpCache(allow=false)} - Set headers to disable
caching and immediately expire the
+ * document.</li>
+ * <li>[EMAIL PROTECTED] @HttpCache(expires=3600)} - Caching is allowed. The
document expires in 10 minutes.</li>
+ * </ul>
+ * </p>
+ *
+ * @author Ben Gunter
+ * @since Stripes 1.5
+ */
[EMAIL PROTECTED](RetentionPolicy.RUNTIME)
[EMAIL PROTECTED]( { ElementType.METHOD, ElementType.TYPE })
[EMAIL PROTECTED]
[EMAIL PROTECTED]
+public @interface HttpCache {
+ /** Indicates whether the response should be cached by the client. */
+ boolean allow() default true;
+
+ /**
+ * The number of seconds into the future that the response should expire.
If [EMAIL PROTECTED] #allow()} is
+ * false, then this value is ignored and zero is used. If [EMAIL
PROTECTED] #allow()} is true and this
+ * value is less than zero, then no Expires header is sent.
+ */
+ int expires() default 0;
+}
Modified:
trunk/stripes/src/net/sourceforge/stripes/config/DefaultConfiguration.java
===================================================================
--- trunk/stripes/src/net/sourceforge/stripes/config/DefaultConfiguration.java
2007-12-04 17:18:08 UTC (rev 645)
+++ trunk/stripes/src/net/sourceforge/stripes/config/DefaultConfiguration.java
2007-12-06 05:13:14 UTC (rev 646)
@@ -28,6 +28,7 @@
import net.sourceforge.stripes.controller.BeforeAfterMethodInterceptor;
import net.sourceforge.stripes.controller.DefaultActionBeanContextFactory;
import net.sourceforge.stripes.controller.DefaultActionBeanPropertyBinder;
+import net.sourceforge.stripes.controller.HttpCacheInterceptor;
import net.sourceforge.stripes.controller.Interceptor;
import net.sourceforge.stripes.controller.Intercepts;
import net.sourceforge.stripes.controller.LifecycleStage;
@@ -398,6 +399,7 @@
protected Map<LifecycleStage, Collection<Interceptor>>
initCoreInterceptors() {
Map<LifecycleStage, Collection<Interceptor>> interceptors = new
HashMap<LifecycleStage, Collection<Interceptor>>();
addInterceptor(interceptors, new BeforeAfterMethodInterceptor());
+ addInterceptor(interceptors, new HttpCacheInterceptor());
return interceptors;
}
Added:
trunk/stripes/src/net/sourceforge/stripes/controller/HttpCacheInterceptor.java
===================================================================
---
trunk/stripes/src/net/sourceforge/stripes/controller/HttpCacheInterceptor.java
(rev 0)
+++
trunk/stripes/src/net/sourceforge/stripes/controller/HttpCacheInterceptor.java
2007-12-06 05:13:14 UTC (rev 646)
@@ -0,0 +1,169 @@
+/* Copyright 2007 Ben Gunter
+ *
+ * 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 net.sourceforge.stripes.controller;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletResponse;
+
+import net.sourceforge.stripes.action.ActionBean;
+import net.sourceforge.stripes.action.ActionBeanContext;
+import net.sourceforge.stripes.action.HttpCache;
+import net.sourceforge.stripes.action.Resolution;
+import net.sourceforge.stripes.config.Configuration;
+import net.sourceforge.stripes.util.Log;
+
+/**
+ * Looks for an [EMAIL PROTECTED] HttpCache} annotation on the event handler
method, the [EMAIL PROTECTED] ActionBean}
+ * class or the [EMAIL PROTECTED] ActionBean}'s superclasses. If an [EMAIL
PROTECTED] HttpCache} is found, then the
+ * appropriate response headers are set to control client-side caching.
+ *
+ * @author Ben Gunter
+ * @since Stripes 1.5
+ */
[EMAIL PROTECTED](LifecycleStage.ResolutionExecution)
+public class HttpCacheInterceptor implements Interceptor {
+ private static final class CacheKey {
+ private Method method;
+ private Class<?> beanClass;
+ private int hashCode;
+
+ /** Create a cache key for the given event handler method and [EMAIL
PROTECTED] ActionBean} class. */
+ public CacheKey(Method method, Class<? extends ActionBean> beanClass) {
+ this.method = method;
+ this.beanClass = beanClass;
+ this.hashCode = method.hashCode() * 37 + beanClass.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ CacheKey that = (CacheKey) obj;
+ return this.method.equals(that.method) &&
this.beanClass.equals(that.beanClass);
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ @Override
+ public String toString() {
+ return beanClass.getName() + "." + method.getName() + "()";
+ }
+ }
+
+ private static final Log logger =
Log.getInstance(HttpCacheInterceptor.class);
+
+ private Map<CacheKey, HttpCache> cache = new HashMap<CacheKey,
HttpCache>(128);
+
+ public Resolution intercept(ExecutionContext ctx) throws Exception {
+ final Configuration config = StripesFilter.getConfiguration();
+ final ActionResolver resolver = config.getActionResolver();
+ final ActionBeanContext context = ctx.getActionBeanContext();
+ final ActionBean actionBean = resolver.getActionBean(context);
+ final Class<? extends ActionBean> beanClass = actionBean.getClass();
+ final String eventName = resolver.getEventName(beanClass, context);
+
+ // Look up the event handler method
+ final Method handler;
+ if (eventName != null) {
+ handler = resolver.getHandler(beanClass, eventName);
+ }
+ else {
+ handler = resolver.getDefaultHandler(beanClass);
+ if (handler != null) {
+ context.setEventName(resolver.getHandledEvent(handler));
+ }
+ }
+
+ if (handler != null) {
+ // if caching is disabled, then set the appropriate response
headers
+ logger.debug("Looking for ", HttpCache.class.getSimpleName(), " on
", beanClass
+ .getName(), ".", handler.getName(), "()");
+ HttpCache annotation = getAnnotation(handler, beanClass);
+ if (annotation != null) {
+ HttpServletResponse response = context.getResponse();
+ if (annotation.allow()) {
+ long expires = annotation.expires();
+ if (expires >= 0) {
+ logger.debug("Response expires in ", expires, "
seconds");
+ expires = expires * 1000 + System.currentTimeMillis();
+ response.setDateHeader("Expires", expires);
+ }
+ }
+ else {
+ logger.debug("Disabling client-side caching for response");
+ response.setDateHeader("Expires", 0);
+ response.setHeader("Cache-control", "no-cache");
+ response.setHeader("Pragma", "no-cache");
+ }
+ }
+ }
+ else {
+ logger.warn("No handler method found for ActionBean [",
beanClass.getName(),
+ "] and eventName [ ", eventName, "]");
+ }
+
+ return ctx.proceed();
+ }
+
+ /**
+ * Look for a [EMAIL PROTECTED] HttpCache} annotation on the method first
and then on the class and its
+ * superclasses.
+ *
+ * @param method an event handler method
+ * @param beanClass the class to inspect for annotations if none is found
on the method
+ * @return The first [EMAIL PROTECTED] HttpCache} annotation found. If
none is found then null.
+ */
+ protected HttpCache getAnnotation(Method method, Class<? extends
ActionBean> beanClass) {
+ // check cache first
+ CacheKey cacheKey = new CacheKey(method, beanClass);
+ if (cache.containsKey(cacheKey)) {
+ HttpCache annotation = cache.get(cacheKey);
+ return annotation;
+ }
+
+ // not found in cache so figure it out
+ HttpCache annotation = method.getAnnotation(HttpCache.class);
+ if (annotation == null) {
+ // search the method's class and its superclasses
+ Class<?> clazz = beanClass;
+ do {
+ annotation = clazz.getAnnotation(HttpCache.class);
+ clazz = clazz.getSuperclass();
+ } while (clazz != null && annotation == null);
+ }
+
+ // check for weirdness
+ if (annotation != null) {
+ logger.debug("Found ", HttpCache.class.getSimpleName(), " for ",
beanClass.getName(),
+ ".", method.getName(), "()");
+ if (annotation.allow() && annotation.expires() < 0) {
+ logger.warn(HttpCache.class.getSimpleName(), " for ",
beanClass.getName(), ".",
+ method.getName(), "() allows caching but expires in
the past");
+ }
+ else if (!annotation.allow() && annotation.expires() != 0) {
+ logger.warn(HttpCache.class.getSimpleName(), " for ",
beanClass.getName(), ".",
+ method.getName(), "() disables caching but explicitly
sets expires");
+ }
+ }
+
+ // cache and return it
+ cache.put(cacheKey, annotation);
+ return annotation;
+ }
+}
This was sent by the SourceForge.net collaborative development platform, the
world's largest Open Source development site.
-------------------------------------------------------------------------
SF.Net email is sponsored by: The Future of Linux Business White Paper
from Novell. From the desktop to the data center, Linux is going
mainstream. Let it simplify your IT future.
http://altfarm.mediaplex.com/ad/ck/8857-50307-18918-4
_______________________________________________
Stripes-development mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/stripes-development