Friday 9 November 2007

Working with cookies in Struts 2 - Part 3

Picking up the hanging thread from my previous post, a thread that was left hanging for a little too longer as other concerns, a job switch being the principal one, kept me busy. Now things smoothly falling into place once again, let's conclude this series by taking a look at how Struts2 Interceptors can be utilized for adding Cookies to HTTP Response.


To begin with, let's remind ourselves how the Struts2 uses Interface Injection. To put it in one line, an interface is defined, an Action implements the interface and interceptors intercept the calls to the action at runtime to call the interface methods on it. Just to give one example, the built in ServletConfigInterceptor works with many "aware" interfaces such as SessionAware, ServletRequestAware etc. to provide access to Servlet-based objects.


Let's start by defining an interface named CookieProvider. It provides one method named getCookies() which returns a list of Cookies to be added to the HTTP Response. Any action that needs to add cookies to HTTP Response needs to implement it.
Here is the code for it -


package com.omkarpatil.interceptor;

import java.util.List;
import com.omkarpatil.util.CookieBean;

public interface CookieProvider {
List<CookieBean> getCookies();
}

Now let's define an action, HelloWorld, which implements this interface. Here is how it looks like -


public class HelloWorld extends ActionSupport implements CookieProvider {
private String username;

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String execute() throws Exception {
// Carry out the processing
return SUCCESS;
}

public List<CookieBean> getCookies() {
List<CookieBean> cookies = new ArrayList<CookieBean>();

CookieBean cookie = new CookieBean();
cookie.setCookieName("flash");
cookie.setCookieValue(getUsername());
//cookie.setPath("/firstapp");

cookies.add(cookie);

return cookies;
}
}

Let's take a look at implementation of getCookies() method. The CookieBean used here is a simple JavaBean that has same attributes as a HTTP cookie. What's the purpose of defining it when we could very well have used the HTTP Cookie? Well, it's a simple JavaBean independent of Servlet environment and that's the purpose of this post, to show how to make an Action independent of Servlet environment while adding a Cookie to a HTTP Response :^)
Here is the CookieBean code snippet -


package com.omkarpatil.util;

public class CookieBean {
private String cookieName; // Name of cookie
private String cookieValue; // value of cookie

private String comment; // describes cookie's use
private String domain; // domain that sees cookie
private int maxAge = -1; // cookies auto-expire
private String path; // URLs that see the cookie
private boolean secure; // use SSL
private int version = 0;
.
.
.
//and all the getters and setters for these attributes
}

Now that we have our Action ready, let's create an Interceptor, CookieProviderInterceptor, that uses the CookieProvider interface. Here is the code for it -


public class CookieProviderInterceptor extends AbstractInterceptor implements PreResultListener {

@Override
public String intercept(ActionInvocation invocation) throws Exception {
before(invocation);
return invocation.invoke();
}

private void before(ActionInvocation invocation) {
invocation.addPreResultListener(this);
}

private void addCookiesToResponse(Object action, HttpServletResponse response) {
if (action instanceof CookieProvider) {

List<CookieBean> cookies = ((CookieProvider) action).getCookies();

if (cookies != null) {
for (CookieBean cookiebean : cookies) {
Cookie cookie = new Cookie(cookiebean.getCookieName(), cookiebean.getCookieValue());

//Set any other attributes you would like to add
//A utility such as Commons BeanUtils can be used to copy between to objects
//cookie.setMaxAge(cookiebean.getMaxAge());
//cookie.setPath(cookiebean.getPath());

response.addCookie(cookie);
}
}
}
}

public void beforeResult(ActionInvocation invocation, String resultCode) {
ActionContext ac = invocation.getInvocationContext();
HttpServletResponse response = (HttpServletResponse) ac.get(StrutsStatics.HTTP_RESPONSE);
addCookiesToResponse(invocation.getAction(), response);
}
}

Before diving into the code of the interceptor, let's summarize the request lifecycle in Struts2.


  1. Accept Request - The web browser requests a resource

  2. Select Action - The Filter Dispatcher looks at the request and determines the appropriate Action

  3. Push Interceptors - The Interceptors apply common functionality to the request

  4. Invoke Action - The Action method executes

  5. Execute Result - Result is executed

  6. Pop Interceptors - The request passes back through the set of Interceptors after Result is executed

  7. Return Response - The response is returned to the web browser

Coming back to the Interceptor code, we need to fit our cookie into the Response after the Action has executed and before the Result is executed. PreResultListener allows us to do just that. CookieProviderInterceptor implements PreResultListener and provides implementation of the beforeResult method. In this method, we get the HttpServletResponse object from the ActionContext and add cookies to it. The CookieProviderInterceptor registers itself with the ActionInvocation in the before method, which gets a callback before the result is executed.


The last piece of this jigsaw puzzle is the configuration of the Interceptor.
Here is the struts config file -


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package name="default" extends="struts-default">
<interceptors>
<interceptor name="cookieprovider"
class="com.omkarpatil.interceptor.CookieProviderInterceptor">
</interceptor>
<interceptor-stack name="defaultAndCookiesProvider">
<interceptor-ref name="defaultStack" />
<interceptor-ref name="cookieprovider" />
</interceptor-stack>
</interceptors>

<action name="HelloWorld_*" method="{1}" class="com.omkarpatil.HelloWorld">
<result name="input">/Welcome.jsp</result>
<result type="redirect-action">
<param name="actionName">Greet</param>
</result>
<interceptor-ref name="defaultAndCookiesProvider" />
</action>

<action name="Greet" class="com.omkarpatil.Greeter">
<result>/greet.jsp</result>
<interceptor-ref name="cookie">
<param name="cookiesName">flash</param>
</interceptor-ref>
</action>
</package>
</struts>

That completes our mini-series about cookie handling in Struts2. Hope this is useful. Good luck and happy Strutting.


Note - Difference between packaged CookieInterceptor and CookieProviderInterceptor

The CookieInterceptor that comes packaged only works on the cookies for the inbound requests. It picks up cookies that are present in the incoming http request and then based on S2 configuration, injects them into S2 actions. It doesn't give you any facility to add cookies into an outgoing http response. The CookieProviderInterceptor, fills this gap and provides the facility to add cookies to an outgoing http response.