Struts 2 Tutorial – Interceptors

09/19/2008

Interceptors are my favorite aspect of Struts 2. They inspect and/or act on a user’s request. There are three main uses cases that I’ll discuss here: intercepting before the action, between the action and view, and after the view. At the end, I’ll show you how to add your brand spankin’ new interceptor to your struts.xml file so that it is called on each request.

Intercepting Before the Action:

package org.lumidant.tutorial.struts2.interceptor;

import java.util.Map;

import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ValidationAware;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;

public class AuthorizationInterceptor extends AbstractInterceptor {

	private static final String USER_KEY = "user";

	public String intercept(ActionInvocation invocation) throws Exception {
		Map session = invocation.getInvocationContext().getSession();
		if(session.get(USER_KEY) == null) {
			addActionError(invocation, "You must be authenticated to access this page");
			return Action.ERROR;
		}

		return invocation.invoke();
	}

	private void addActionError(ActionInvocation invocation, String message) {
		Object action = invocation.getAction();
		if(action instanceof ValidationAware) {
			((ValidationAware) action).addActionError(message);
		}
	}

}

You can see that we check to see if the user is authorized and present an error message if he/she is not logged in. We do this by adding an ActionError, which can be displayed on your view. If the user is logged in, then he/she proceeds normally.

Intercepting Between the Action and Generation of View:

package org.lumidant.tutorial.struts2.interceptor;

import java.util.Map;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.opensymphony.xwork2.interceptor.PreResultInterceptor;
import org.apache.struts2.ServletActionContext;

public class WirelessInterceptor extends AbstractInterceptor {

	private static final String RESULT_CODE_SUFFIX_WIRELESS = "Wireless";
	private static final String REQUEST_HEADER_ACCEPT = "Accept";
	private static final String ACCEPT_HEADER_WIRELESS = "vnd.wap";

	public String intercept(ActionInvocation invocation) throws Exception {

		invocation.addPreResultListener()(new PreResultListener() {
			public void beforeResult(ActionInvocation invocation, String resultCode) {

				// check if a wireless version of the page exists
				// by looking for a wireless action mapping in the struts.xml
				Map results = invocation.getProxy().getConfig().getResults();
				if(!results.containsKey(resultCode + RESULT_CODE_SUFFIX_WIRELESS)) {
					return;
				}

				// send to wireless version if wireless device is being used
				final String acceptHeader = ServletActionContext.getRequest().getHeader(REQUEST_HEADER_ACCEPT);
				if(acceptHeader != null && acceptHeader.toLowerCase().contains(ACCEPT_HEADER_WIRELESS)) {
					invocation.setResultCode(resultCode + RESULT_CODE_SUFFIX_WIRELESS);
				}
			}
		});

		return invocation.invoke();
	}
}

In this example, the action has already been taken, but we have not generated the view that the user will see yet. This allows us to send the user to a separate page if he/she is using a Blackberry. We check the Accept HTML header to see if the string “vnd.wap” is present and if so then we append “Wireless” to the result code. So if the action was going to send the user to the result “Success” then we will instead show them the result “SuccessWireless”. We could easily adopt this technique for a Google Android or an iPhone as well.

Intercepting after View Creation:

package org.lumidant.tutorial.struts2.interceptor;

import java.util.Map;

import org.hibernate.Transaction;
import org.marketcharts.data.dao.util.HibernateSessionFactory;

import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;

public class HibernateInterceptor extends AbstractInterceptor {

	public String intercept(ActionInvocation invocation) throws Exception {
		try {
			return invocation.invoke();
		} catch(Exception e) {
			Transaction tx = HibernateSessionFactory.getSession().getTransaction();
			if(tx != null && tx.isActive()) {
				tx.rollback();
			}
			return Action.ERROR;
		} finally {
			HibernateSessionFactory.getSession().close();
		}
	}

}

This example uses Hibernate, an Object Relational Mapper (ORM), which is library for easy database access. Hibernate needs an open session when the view is created (Open Session in View Pattern) because it lazily accesses the database. If the session is closed and we go to access the database, then Hibernate will throw an Exception. So this interceptor provides a method to close the session after the view has already been created and all the database calls have been made.

Adding an Interceptor to your struts.xml:

<package name="example" extends="struts-default">
  <interceptors>
    <!-- 
      One possible use of an interceptor is to send the user to special version of the .jsp
  	view if they are using a Blackberry as shown above.
    -->
    <interceptor name="wireless" class="com.lumidant.tutorial.struts2.interceptor.WirelessInterceptor"/>

    <interceptor-stack name="wirelessStack">
      <interceptor-ref name="exception" />
      <interceptor-ref name="servlet-config" />
      <interceptor-ref name="i18n" />
      <interceptor-ref name="chain" />
      <interceptor-ref name="params" />
      <interceptor-ref name="wireless" />
    </interceptor-stack>
  </interceptors>

  <default-interceptor-ref name="wirelessStack" />
</package>

In this example, we’ve change the default interceptor stack to be a custom stack which we’ve created. You can also extend existing stacks.

Be Sociable, Share!