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.

Wednesday 1 August 2007

Working with cookies in Struts 2 - Part 2

Continuing from my earlier post, let's see how to add cookies to a HTTP response in an action class. Here also, I could figure out two ways by which it can be achieved -
  1. Use ServletActionContext to get HTTP response and set a cookie on it in the action

  2. Make the action implement ServletResponseAware interface and use the HTTP response object made available by the framework

Let's say we have an action class HelloWorld that needs to add a cookie named flash in the response.
Using the first option is pretty straightforward, here is how the code looks like -



public class HelloWorld extends ActionSupport {

private String username;

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

public String execute() throws Exception {
ServletActionContext.getResponse().addCookie(new Cookie("flash", getUsername()));
return SUCCESS;
}
}

To use the second option, the HelloWorld action needs to implement ServletResponseAware interface. This interface defines a single method,
void setServletResponse(HttpServletResponse response), which the framework, ServletConfigInterceptor to be precise, uses to carry out interface injection into the action at runtime. Here is how the code looks like -



public class HelloWorld extends ActionSupport implements ServletResponseAware {

private String username;
private HttpServletResponse response;

public String getUsername() {
return username;
}

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

public void setServletResponse(HttpServletResponse response) {
this.response = response;
}

public String execute() throws Exception {
response.addCookie(new Cookie("flash", getUsername()));
return SUCCESS;
}
}

So far, so good. These options will allow us to do the needful. Still, I'm a little uncomfortable using any of these. Reason? Well, both options will tie up the actions to servlet environment, which , amongst others, will make unit testing difficult. Do we have a better option? Yes, in my opinion, we can use the interceptors feature of the Struts 2 framework to our advantage, to abstract this functionality into a separate interceptor and decouple the actions from Servlet API. More on it in the next post. Thanks for reading and stay tuned.



Saturday 28 July 2007

Working with cookies in Struts 2

I have been working with Struts for a very long time now, in fact, I still clearly remember using perform method in the action classes :^) . And then, a few days ago, I got an opportunity to try my hand at Struts 2, the reincarnation of Struts, and at a first glance, it looks impressive, to say the least.

Coming to the subject, when I googled for how to work with cookies in struts 2, I could only find a couple of references, viz. struts-users mail archive link and CookieInterceptor javadoc . So, I'll take this opportunity to share whatever little I have picked up.

Let's divide this into two parts -
- Receiving cookies in an action
- Adding cookies to a response in an action

First, let's see how to receive cookies in an action. I could figure out two options by which it can be achieved. Well, there could possibly be other options available as well, but I'm sure you'll excuse me, as this post is based on my extremely limited knowledge of struts 2 that I could manage to gather in past couple of days. Please feel free to enlighten me if you know more about it. Both options need the CookieInterceptor to be configured for the action that needs to receive cookies. Here are the two options -

Option 1 - Filter cookies in the CookieInterceptor configuration and add properties with the cookie names to the action class
Option 2 - Make the action implement CookiesAware interface and receive a map that contains cookies received

Let's say we have an action class Greeter that expects a cookie named flash in the incoming request. Here is the action configuration for the CookieInterceptor. The param tag under interceptor-ref tag indicates the cookie name that is expected by the action.

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

To Use the first option, a field named flash with getters/setters needs to be added to Greeter action. The CookieInterceptor does it's magic at runtime to make the cookie available into the action. Here is the code snippet for Greeter action -


public class Greeter extends ActionSupport {
private String message;
private String flash;

public String getFlash() {
return flash;
}

public void setFlash(String flash) {
this.flash = flash;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

public String execute() throws Exception {
setMessage("Hello " + getFlash());
return SUCCESS;
}
}

To use the second option, the Greeter action needs to implement CookiesAware interface. CookiesAware interface defines one method void setCookiesMap(Map cookies), which the CookiesInterceptor uses to carry out interface injection into the action at runtime.


public class Greeter extends ActionSupport implements CookiesAware {
private Map cookiesMap;

public Map getCookiesMap() {
return cookiesMap;
}

public void setCookiesMap(Map cookiesMap) {
this.cookiesMap = cookiesMap;
}

public String execute() throws Exception {
// Do whatever you want to with the cookies map
return SUCCESS;
}
}

Well, that covers the "Receiving cookies into action" part. Onto the "Adding cookies to a response" in the next post. Stay tuned.