In a project where we are using struts2 we wanted to create two custom interceptors. One to start a Transaction at the start of the interceptor stack and one to close that Transaction at the end of the interceptor stack. This would cause all (read only) database calls for the prepare method and all the conversion and validation to run in one transaction.
Instead of heaving two, very similar, interceptors to start and stop transactions, it’s more convenient to have only one with a parameter to switch start and stop mode. But hold on… the Struts2 docs state clearly that interceptors must have no state. Is setting a parameter on an interceptor, that is stored in a member variable not just that: state?
And if there is only one interceptor, that will surely fall over when used simultaneously in two or more request, each with different parameters to the interceptor. (Remember that you can override interceptor parameters per stack, or even per action, think of the “excludeMethods” parameter of the validate interceptor). This cannot be a design flaw, or what?
Unable to find an answer quickly in my Struts2 book or with Google, I put Struts2 to the test. For the first time I tried running Eclipse europa (on the MacBook Pro) with the Web Tools Platform and I was pleasantly surprised at its first time ease of use. Just go to the Eclipse update feature and select WTP. And I had installed Tomcat 6 already, so pointing to the installation directory for the server configuration was enough to get started.
Next, I downloaded the struts2-blank-2.0.11.1.war and imported this into the server configuration. Setting a breakpoint in the example code worked just fine. Just browsed to
http://localhost:8080/struts2-blank-2.0.11/index.html
and bingo: Eclipse stopped at the breakpoint.
Now for the test. First I changed the example.xml to include a new Interceptor:
green
red
/example/HelloWorld.jsp
orange
/example/HelloWorld.jsp
blue
Next, implementation of the interceptor:
package example;
import java.util.logging.Logger;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;
public class ThreadSafeInterceptor implements Interceptor {
private static final long serialVersionUID = 1964558869343105305L;
private Logger log = Logger.getLogger("threadsafe");
private String toggle;
public void destroy() {
log.info("Object: " + this);
}
public void init() {
log.info("Object: " + this);
}
public String intercept(ActionInvocation ai) throws Exception {
log.entering(this.toString() , "intercept()");
log.info("Object: " + this + " toggle before: " + toggle);
String result = ai.invoke();
log.info("Object: " + this + " toggle after: " + toggle);
return result;
}
public String getToggle() {
return toggle;
}
public void setToggle(String toggle) {
log.info("Object: " + this + " setToggle("+toggle+")");
this.toggle = toggle;
}
}
If there was only one interceptor, and the parameters would be set to change color, it must show up that the interceptor gets confused while running!
Here is what the console showed:
WARNING: Settings: Could not parse struts.locale setting, substituting default VM locale
Apr 22, 2008 10:04:40 PM example.ThreadSafeInterceptor setToggle
INFO: Object: example.ThreadSafeInterceptor@89c698 setToggle(green)
Apr 22, 2008 10:04:40 PM example.ThreadSafeInterceptor init
INFO: Object: example.ThreadSafeInterceptor@89c698
Apr 22, 2008 10:04:40 PM example.ThreadSafeInterceptor setToggle
INFO: Object: example.ThreadSafeInterceptor@4d3241 setToggle(red)
Apr 22, 2008 10:04:40 PM example.ThreadSafeInterceptor init
INFO: Object: example.ThreadSafeInterceptor@4d3241
Apr 22, 2008 10:04:40 PM example.ThreadSafeInterceptor setToggle
INFO: Object: example.ThreadSafeInterceptor@e5a19e setToggle(orange)
Apr 22, 2008 10:04:40 PM example.ThreadSafeInterceptor init
INFO: Object: example.ThreadSafeInterceptor@e5a19e
Apr 22, 2008 10:04:40 PM example.ThreadSafeInterceptor setToggle
INFO: Object: example.ThreadSafeInterceptor@d5cdab setToggle(blue)
Apr 22, 2008 10:04:40 PM example.ThreadSafeInterceptor init
INFO: Object: example.ThreadSafeInterceptor@d5cdab
Apr 22, 2008 10:04:40 PM com.opensymphony.xwork2.util.ObjectTypeDeterminerFactory
Ahh… at startup actually three instances are created! One for each interceptor-ref! So each one holds just one set of parameters, one state. Also note that first the toggle parameter is set and then the init is called. So you could potentially use the parameter value in the init method. Hmm, does it make sense to define the private toggle field as final as well, so it cannot change?
Next in the log, when pointing the browser to
http://localhost:8080/struts2-blank-2.0.11/example/HelloWorld.action
:
Apr 22, 2008 10:07:36 PM example.ThreadSafeInterceptor intercept
INFO: Object: example.ThreadSafeInterceptor@e5a19e toggle before: orange
Apr 22, 2008 10:07:36 PM com.opensymphony.xwork2.validator.ActionValidatorManagerFactory
INFO: Detected AnnotationActionValidatorManager, initializing it...
Apr 22, 2008 10:07:36 PM example.ThreadSafeInterceptor intercept
INFO: Object: example.ThreadSafeInterceptor@4d3241 toggle before: red
Apr 22, 2008 10:07:37 PM example.ThreadSafeInterceptor intercept
INFO: Object: example.ThreadSafeInterceptor@4d3241 toggle after: red
Apr 22, 2008 10:07:37 PM example.ThreadSafeInterceptor intercept
INFO: Object: example.ThreadSafeInterceptor@e5a19e toggle after: orange
Orange, red, red, orange… looks good, the green interceptor is hidden from this stack! Note that only the first occurence of the two interceptors in the stack is overridden. Mind this when using the “param-prepare-param” stack with possible parameter overrides for the param interceptor!
Calling this url:
http://localhost:8080/struts2-blank-2.0.11/example/HelloWorldBlue.action
shows:
Apr 22, 2008 10:08:10 PM example.ThreadSafeInterceptor intercept
INFO: Object: example.ThreadSafeInterceptor@d5cdab toggle before: blue
Apr 22, 2008 10:08:10 PM example.ThreadSafeInterceptor intercept
INFO: Object: example.ThreadSafeInterceptor@4d3241 toggle before: red
Apr 22, 2008 10:08:10 PM example.ThreadSafeInterceptor intercept
INFO: Object: example.ThreadSafeInterceptor@4d3241 toggle after: red
Apr 22, 2008 10:08:10 PM example.ThreadSafeInterceptor intercept
INFO: Object: example.ThreadSafeInterceptor@d5cdab toggle after: blue
As expected it uses the blue instance of the ThreadSafeInterceptor.
Adding two param fields to see if both interceptors can be “replaced” with new ones for one action fails. Using a second black param entry as follows:
/example/HelloWorld.jsp
blue
black
shows:
Apr 22, 2008 10:51:06 PM example.ThreadSafeInterceptor intercept
INFO: Object: example.ThreadSafeInterceptor@9d963f toggle before: black
Apr 22, 2008 10:51:06 PM com.opensymphony.xwork2.validator.ActionValidatorManagerFactory
INFO: Detected AnnotationActionValidatorManager, initializing it...
Apr 22, 2008 10:51:06 PM example.ThreadSafeInterceptor intercept
INFO: Object: example.ThreadSafeInterceptor@1b5077 toggle before: red
Apr 22, 2008 10:51:07 PM example.ThreadSafeInterceptor intercept
INFO: Object: example.ThreadSafeInterceptor@1b5077 toggle after: red
Apr 22, 2008 10:51:07 PM example.ThreadSafeInterceptor intercept
INFO: Object: example.ThreadSafeInterceptor@9d963f toggle after: black
The blue one does not even get created! Bummer… Well, we might be stretching the framework a bit, but it’s good to know the interceptors are really thread safe. And it brings the question: is it smart to use two of the same interceptors in one stack?
Time for one more try, lets make a second interceptor entry for the same class with a different name: threadsafe2.
green
red
Now we can use:
/example/HelloWorld.jsp
blue
black
And this just works! The console writes out: blue, black, black, blue. Great!