State Management

This document has two major sections:

  1. #Smart State Management using the ActionBeanContext
  2. #Redirect-after-Post using the Flash Scope

Smart State Management

In this section we'll walk through a strategy for how to access HttpSession, Cookies and other HttpServletRequest/Response behaviours in a way that's type safe and keeps your ActionBean classes testable (woohoo!). The key to this is providing your own ActionBeanContext subclass.

Subclassing ActionBeanContext

The following is an example subclass that allows for type safe access to a User object stored in session:

ActionBeanContext subclass
public class MyAppActionBeanContext extends ActionBeanContext { 
	public void setUser(User user) { 
		getRequest().getSession().setAttribute("user", user); 
	} 

	public User getUser() { 
		return (User) getRequest().getSession().getAttribute("user"); 
    } 
} 

Now that you have your own ActionBeanContext you'll need to tell Stripes to use it. The easiest way is to drop your class into an extensions package (see Extensions). Alternatively, you can add the following initialization parameter to the Stripes Filter in your web.xml:

Configuring your ActionBeanContext
<init-param> 
	<param-name>ActionBeanContext.Class</param-name> 
	<param-value>com.myco.MyAppActionBeanContext</param-value> 
</init-param> 

Using your new ActionBeanContext

Now, the ActionBeanContext that gets set on your ActionBean will always be a MyAppActionBeanContext. You'll still have to cast the context object that you receive in your ActionBean, but thanks to a new feature in Java 1.5 called co-variant return types, you can at least do it once, and not litter your code with casts.

Using a co-variant return type in your ActionBean
public class MyActionBean implements ActionBean { 
	private MyAppActionBeanContext context; 

	/** Interface method from ActionBean. */ 
	public setContext(ActionBeanContext context) { 
		this.context = (MyAppActionBeanContext) context; 
	} 

	/** Interface method from ActionBean, using a co-variant return type! */ 
	public MyAppActionBeanContext getContext() { 
		return this.context; 
	} 

... 
} 

In Java 1.4 and earlier, the getContext() method would not compile because the interface specifies it must return ActionBeanContext, and the class specifies MyAppActionBeanContext. It'd fail even though MyAppActionBeanContext extends ActionBeanContext. In Java 1.5 this is allowed, and it helps make things a bit cleaner. And if you create your own BaseActionBean class you can put this in there and never have to see it again (smile)

Now that you have your own ActionBeanContext class you can

  • Add type safe methods to access things you need to store in session (hopefully not too many things)
  • Add type safe methods for getting and setting the values of cookies
  • Put all the strings/keys needed for the above operations in one place
  • Access methods and functionality of the request and response objects without coupling your ActionBeans to them

But wait, there's more. Because the ActionBeanContext is attached to your ActionBean, and your ActionBean is dropped into a request attribute, you now have two ways of accessing things stored in session. On a JSP:

<div>${user} == ${actionBean.context.user}</div> 

While this may seem trivial, there's one benefit. The first syntax ${user} assumes that you have placed the User object in to a JSP scope (e.g. session) under the attribute key "user". If you ever change that key, say to "__secretPlace_user", your JSPs will break. You're probably less likely to change the type safe methods on your ActionBeanContext, so the ${actionBean.context.user} might be safer. Not a huge benefit, but worth noting.

Enabling Testing

It's often difficult to test classes that live in the web tier. This is usually because they either need to interact with one of the Http* objects, or they are forced to through the API of the Servlet/Action/Whatever model being used. Unit testing of Stripes ActionBeans is already pretty simple (just instantiate, set context, set properties and go), but it falls down when your ActionBean really needs to interact with the request or the session. Use those and suddenly testing is difficult...you might have to have a set of mock Http* objects, etc.

With just a little bit more work, you can make your ActionBeans completely testable. Using our MyAppActionBeanContext example above, we can create an abstract class that our ActionBeanContext will extend, and which our ActionBeans will use. Let's refactor a bit:

MyAppActionBeanContext.java
public abstract class MyAppActionBeanContext extends ActionBeanContext { 
	public abstract void setUser(User user); 
	public abstract User getUser(); 
} 

Our "real" class would look much the same as before, but perhaps this time we'd name it differently:

MyAppActionBeanContextImpl.java
public class MyAppActionBeanContextImpl extends MyAppActionBeanContext { 
	public void setUser(User user) { 
		getRequest().getSession().setAttribute("user", user); 
	} 

	public User getUser() { 
		return (User) getRequest().getSession().getAttribute("user"); 
	} 
} 

And then, we create out test ActionBeanContext:

MyAppActionBeanContextTestImpl.java
public class MyAppActionBeanContextTestImpl extends MyAppActionBeanContext { 
	private User user; 

	public void setUser(User user) { this.user = user; } 

	public User getUser() {return this.user; } 
} 

Et voilà, we have a test rig for our ActionBeans that allows us to simulate the real environment with very little effort. You can imagine other ways of doing this. Perhaps the test version (MyAppActionBeanContextTestImpl in our example) could simply extend the "real" version (MyAppActionBeanContextImpl in our example) and override methods that access session or request. You could even go one further and have a protected helper method in your "real" context class called something like setInSession(String key, Object value), which in the real version calls getRequest().getSession().setAttribute(), but in the test version just puts the value in a local Map.

Redirect-after-Post

Redirect-after-Post is the name given to a fairly simple technique. The idea is that after a form POST has occurred and the server has done something significant (create a user, place a trade etc.) that it is safest to then redirect the user to another (or perhaps the same) page. By redirecting we ensure that if the user hits the refresh button they don't re-submit the form. If we were to have simply forwarded to a page after the POST, a refresh would re-post the form!

But there is a down side to redirecting. Anything stored in request attributes is lost when the redirect is issued (because the request for the page is not the same request). One option is to stuff things into session, but that's a bad idea(tm) because things will break when the user has more than one browser window open using your application.

The FlashScope

This is the problem that the FlashScope is designed to solve. In essence the flash scope is a scope much like request scope, session scope etc. Whereas the request scope is defined as existing for the lifetime of a request, the flash scope is defined as existing for the duration of this request and the subsequent request. Any item added to the flash scope will be made available as a request attribute during the current request, and during the next request.

An example of flash scope usage is non-error messages functionality in Stripes. The ActionBeanContext has a getMessages() method that returns a list to which messages may be added. This list is stored in flash scope so that the messages are available to the current request (should the ActionBean forward to a page) and in the next request (should the ActionBean redirect to a page). The code looks like this:

Example usage of FlashScope
public List<Message> getMessages(String key) { 
	FlashScope scope = FlashScope.getCurrent(getRequest(), true); 
	List<Message> messages = (List<Message>) scope.get(key); 

	if (messages == null) { 
		messages = new ArrayList<Message>(); 
		scope.put(key, messages); 
	} 

	return messages; 
} 

The true on the first line tells FlashScope to manufacture a FlashScope if one doesn't yet exist. After that the scope is used just like any other scope - as a container of key/value pairs.

Flashing ActionBeans

By default ActionBeans are not put in flash scope. This is partly for backwards compatibility reasons, and partly because it just isn't necessary to do it all the time! That doesn't mean it's hard to do though. If you're using the standard RedirectResolution it's as simple as chaining one more method call:

return new RedirectResolution("/some/page.jsp").flash(this); 

Even if you're redirecting some other way, it's still very easy:

// From within the ActionBean 
FlashScope.getCurrent(getContext().getRequest(), true).put(this); 

Note: Only do this if you're redirecting to a JSP. If you redirect to an ActionBean that forwards to a JSP, the flashed ActionBean will be overwritten with the ActionBean you redirected to.

Accessing the FlashScope in your JSP

Lets say you called this method in one of your ActionBeans:

public void addRecipe(HttpServletRequest request, String message) { 
	FlashScope fs = FlashScope.getCurrent(request, true); 
	List<String> messages = (List<String>) fs.get("recipes"); 

	if (messages == null) { 
		messages = new ArrayList<String>(); 
		fs.put("recipes", messages); 
	} 

	messages.add(message); 
} 

Then you redirected to another ActionBean which forwards to a JSP. This is how you access the FlashScope in your JSP:

<c:forEach var="recipe" items="${recipes}"> 
	<div class="recipe_name">${recipe.name}</div> 
</c:forEach> 

As you can see, when you put an object into the FlashScope, it can be accessed directly by its key in an EL statement.

How FlashScope works

The FlashScope works by temporarily storing instances of itself in Session, and removing them on subsequent requests. As a result, when a FlashScope is used, an additional parameter is appended to redirect URLs to tell Stripes which FlashScope to use. Because of this parameter two or more browser windows (or tabs) in the same session will never get confused and access each others' FlashScopes.

An obvious side affect of this strategy is that occasionally FlashScopes get orphaned in session. For this reason Stripes will examine all FlashScopes in existence for a session each time a request is received; each flash scope starts "aging" when the request that generated it is completed, and any FlashScope beyond a certain age (currently 2 minutes) is destroyed. Think of this like session expiration on a smaller scale.

Bringing it all Together

Hopefully the above sections on smart state management and redirect-after-post made sense. The FlashScope actually provides the single best example to date of why using the ActionBeanContext to manage all state related logic is a good idea.

The FlashScope was introduced in Stripes 1.2. Prior to 1.2 the list of non-error messages was stored in a request attribute. Now that the flash scope is available the non-error messages are stored there. Making the change was trivial because ActionBeans were completely isolated from the details of how the messages were stored and retrieved. Had ActionBeans been accessing request (or session) directly the change could not have been made without breaking existing code.