Monday, November 12, 2007

Note: I still can't get code to display very well on blogger. Sorry for the formatting issues. If you need to the source code is linked at the bottom of this article if it makes it easier to follow along.

I've been using Stripes for both large and small applications for about a year. While I am of the camp that there is no golden hammer I've not run across a project I am involved with where Stripes doesn't make sense. I've been wanting to write more articles on Stripes. While the documentation on the site is great for folks to get started it is lacking in the area of best practices and general tutorials on different aspects of the framework. One of my favaorite things about Stripes (and there are many) is how easy it is to extend the framework to suit my needs and most of the time I don't even need to dig into the source. A lot of tiimes that power comes in the form of an Interceptor.

Interceptors are not a new concept. I believe my first experince with them in web applications was when working with WebWork several years ago. Stripes has a couple of Interceptors already:

SpringInterceptor - Allows you to inject Spring Beans into controllers using the @SpringBean method level annotation.

BeforeAfterMethodInterceptor - Allows you to execute methods before and/or after specific Lifecycle Stages using @Before and @After method level annotations (AOP like behavior)

Note: Both the Interceptors mentioned above work in combination with annotations but not all have to.

In this article I want to show how to create an interceptor and we'll also create an annotation that will be used with the interceptor. I'll identify a problem that Stripes by itself doesn't solve and show how to implement the solution with a simple Interceptor. At times you may see mention of concepts that you won't be familiar with if not familiar with Stripes. While I'll try and explain these a bit as I go I may link to the Stripes documentation on the subject so as not to repeat information. I also want to keep this article as concise and to the point as possible.

The Problem

Ajax is everywhere and one common problem I've run into in the past is dealing with the browser caching the response from an ajax request. This seems to happen more often than not in Internet Explorer. What we want is to prevent this as much as possible when needed.

Typical Solutions

Probably the most common talked about solution is appending a random number on the end of the request. This tricks the browser into thinking it is a different response. Wikipedia's entry on XmlHttpRequest offers a different and slightly more complex solution which you can read about here: http://en.wikipedia.org/wiki/Xmlhttprequest#Caching. The problem with that is most of us are using a JavaScript library like jQuery or Prototype and that would cause us to go hacking inside their code. Not ideal. And another approache is to add header information to the response. In java it goes something like this:

HttpServletResponse response = context.getResponse();

response.setDateHeader("Expires", 0);

response.setHeader("Cache-control", "no-cache");

response.setHeader("Pragma", "no-cache");

Finding a centralized place for that code is the key. One solution on the Stripes mailing list was having a disableCaching() method in an ActionBean base class. When you wanted this to execute you would do something like this:

@Before(stages = LifecycleStage.ResolutionExecution)

@Override

protected void disableCaching() {

super.disableCaching();

}

And while that works it is a bit more verbose than need be.

The Interceptor Solution

What we want to do is take the above code and centralize it in an Interceptor that executes on the LifecycleStage.ResolutionExecution stage however we want to be able to control when the code gets executed. To do this we need two things:

@NoCache annotation - We'll use this on methods to denote we want no caching. We'll also allow a boolean argument to indicate whether it should be on or off. This is important if we want to override a class level annotation on one or more methods.

NoCacheInterceptor - We'll use this to check for the @NoCache annotation and shove the no cache header directives into the response.

The @NoCache annotation is pretty straightfoward:

@Retention(RetentionPolicy.RUNTIME)

@Target( {ElementType.METHOD, ElementType.TYPE})

@Documented

public @interface NoCache

{

boolean value() default true;

}

Save that in a file called NoCache.java in whatever package you desire.

Next is the interceptor which requires a bit more discussion. A Stripes interceptor implements an interface called Interceptor. Interceptor requires one method be implemented; public Resolution intercept(ExecutionContext ctx) throws Exception. Stripes will call this method on any configured interceptor during the specified LifecycleStage. Whoa, how do we specify the LifecycleStage? Whoa!!! What's with all this LifecycleStage talk?

Segway..Stripes has five Lifecycle Stages. Instead of repeating what is already well documented I'll let you read about them at your lesiure here.

And we're back...So we need to create this Interceptor and tell it when to execute the intercept method. We do that by specifying an annotation at the class level:

The Stripes Configuration class holds all the configuration information passed into Stripes from the web.xml. This includes all configured Interceptors.

The ActionResolver will allows us to get which Method was requested from the ActionBean during the request.

The ActionBeanContext is used to help us get an instance of the requested ActionBean

The requested ActionBean.

We need the actual Class.

The eventName or requested method to call.

The next thing we need to do is get the Method or handler if the eventName doesn't exist. The reason for this is because if you request a URL and there is no eventName specified Stripes will look for a default handler which is a method denoted by the @DefaultHandler annotation. And then if this doesn't exist we need to throw an error and inform the user.

Good Practice: All ActionBeans should have a default handler in case an eventName was not given.

Don't worry too much about understanding every bit of that. Just know that we need the handler and we need to throw an error if one doesn't exist. The next thing we need to do is actually look for the @NoCache annotation. If we find it we set the no cache header junk and tell the ExecutionContext to proceed with the request.

To make sure that we are as performant as possible Stripes caches interceptor instances. Because of this we can also cache data within the interceptor. In this case once a @NoCache annotation has been found we want to cache this fact so the next time this interceptor is run with this beanClass we can just check the cache and not have to go through the annotation reflection checks. We first want to check the handler (Method) being called because it override class level annotations. If no annotation is found then we check for a class annotation.

So basically we look for an annotation on the method. If we find it we return its value. Otherwise, we check the class. We also want to check all super classes incase the user is extending a Base ActionBean of sorts.

Good Practice: Creating a BaseActionBean and having all your ActionBeans extend that base class will save a lot of boilerplace code and make developing with Stripes a lot simpler.

So once we either find an annotation or not we want to cache it and then return what we found:

cache.put(cacheKey, disabled);return disabled;

The last thing for code is the CacheKey class. We want to make sure that we store unique keys when checking for cache. That way we always know we have the correct cache for the correct beanClass that was requested. Here's the class. I just make it an inner class of the interceptor.

5 comments:

Great article! One tiny point -- the logic you have in place for finding the event handler is not always necessary at most lifecycle stages, especially the ones that occur after EventHandlerResolution, as Stripes has already found the event handling method. To get that method:

Method eventHandler = executionContext.getHandler();

This will return the default handler if there is one, or the specific one if @HandlesEvent is there..

Buy Guild Wars 2 Gold takes on for the Leading Category team Several weeks and the England country wide group. He is Chelsea's top aim scorer with One hundred thirty objectives, the most by midfielder inside the club background! Using more than 190 objectives scored currently, Chad Lampard Jr will be the highest scoring midfielder within The european union Homepage, along with is constantly on the make their mark?Wlodzimierz along with Ebi SmolarekWlodzimierz Smolarek, Poland very best striker, enjoyed an important function in the country third spot position within the The early 80's Arena PC Game.