Monday, May 10, 2010

Struts 2 Package Extended

When we first started using struts 2 we wanted to keep parts of the application clean by using separate packages for different parts of the app. However we found that we were copying interceptors all over the place because we wanted to use the same functionality in the separate packages. I know the struts gurus out there are saying "duh! extend the package". Well my biggest gripe with struts is the documentation says things like "You can extend the package" or they will give you a snipplet of xml without any context.

How to extend your package:


Our struts.xml looks like this now.
<?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>
<!--<constant name="struts.enable.DynamicMethodInvocation" value="false" />-->
<!--<constant name="struts.devMode" value="true" />-->

<include file="admin-config.xml" />
<include file="reports-config.xml" />


<package name="mybiz-struts-default" extends="struts-default" abstract="true">
<interceptors>
<!--  <!– SessionTimeout interceptor will redirect to SessionTimeout action if problem is detected –>-->
<interceptor name="SessionTimeout" class="com.mybiz.interceptors.CheckTimeout"/>
<!--<!– CookieRedirect interceptor will redirect to CheckCookies action if problem is detected –>-->
<interceptor name="CookieRedirect" class="com.mybiz.interceptors.CookieRedirect"/>
<interceptor-stack name="PrepareSessionStack">
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="SessionTimeout"/>
<interceptor-ref name="CookieRedirect"/>
<interceptor-ref ... other interceptors .../>
</interceptor-stack>
</interceptors>
<global-results>
<result name="cookieRedirect">../ErrorUIMessages.jsp</result>
<result name="error">../ErrorUIMessages.jsp</result>
<result .... other results ....>
<result name="errorPage">../error/Error.jsp</result>
</global-results>
</package>
</struts>

There are a few parts that I would like to highlight.
  1. mybiz-struts-default
  2. CookieRedirect interceptor
  3. global-results
  4. Creating your own interceptor stack

mybiz-struts-default


This package extends the struts-default package. All of our other packages extends mybiz-struts-default. This way we have one definition that can be used by all our packages instead of copying and pasting the definition everywhere.
Here is an example of a package for one of the apps reports-config.xml:
<?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="mybiz" namespace="/mybiz" extends="mybiz-struts-default">
<action name="CheckCookies" class="com.mybiz.actions.core.CheckCookiesEnabled">
<result name="cookiedisabled">../ErrorUIMessages.jsp</result>
</action>
<action name="SessionTimeout" class="com.mybiz.actions.core.ActionTimeout">
<result>../ErrorUIMessages.jsp</result>
</action>
<action name="action1" class="com.mybiz.actions.core.Action1">
<interceptor-ref name="PrepareSessionStack"/>
<result>..action1.jsp</result>
</action>
<.... more actions ....>
</package>
</struts>

Now, I have gone and complained about code snipplets and then gone and given you code snipplets. Hopefully you can make sense of it.

CookieRedirect interceptor


Most modern applications don't function very well without cookies enabled. Tomcat uses the JSESSIONID cookie to keep track of the session. The documentation claims that if cookies are disabled tomcat switch to url-rewrite appending the session id on the end of every url. I have not been able to get this to work in tomcat 5.5. Especially adding a layer of apache and clustering servers it just gets really complicated.
So we decided that the user must really have cookies enabled. The only way to determine if cookies are enabled is to set a cookie and check for it on a subsequent request. I decide that the first request should have the JSESSIONID cookie set so I would redirect and check for the cookie. If the cookie is there I redirect back to the page they requested. If the cookie is not there they get sent to a page that explains why we need cookies and how to enable them.

global-results


Above I was explaining how I solved the cookies enabled issue. Well in an interceptor if the cookie redirect check had not happened I return "cookieRedirect" but the actual request has been redirected to the checkaction. Well this caused the stacktrace to print out an error unless every action had a result of "cookieRedirect" defined. I didn't want to make my team define this on every action and have to explain why all the time.
Global results solved this problem for me. We define a result in global-results section with value "cookieRedirect".

Creating your own interceptor stack


Creating your own interceptor stack can be very powerful. You can add default behavior like forced login on every request. However, remember to include default-stack in your interceptor stack. If you forget to do this much of the default behavior you expect to work doesn't work. Like setting the values of request parameters in your action pojo. This oversight could take an afternoon to figure out.

No comments: