Writing and Configuring Struts2 Actions (Part 2)
July 11th, 2007 by Wes Wannemacher
In the last post, I presented an in-depth example of a very simple Struts2 Action. Although this may have been helpful to some, there is much more that can be done with actions. The important aspect of the last example is to understand that Struts2 Actions are simple POJOs. Having your UI built around simple objects allows you to create code that is much easier to maintain. Simple classes are easier to unit test, easier to enhance, and easier to debug. In this post, I will show you that this simplicity carries through even for advanced aspects of using Struts2 Actions.
Just like the previous posts, I am going to walk you through some examples that build upon your newly found Struts2 knowledge. The first example I want to talk about is wildcard mapping. It is considered a “best practice” to have all of your request processing to be handled through Struts2 Actions. If each element of your UI is handled by Struts2, you can cut down the possibility of 404s on your site by using the <s:url> tag. This sounds like a lot of work, creating an action class for each JSP on your site, but it is easy to create a simple “base” class and using a wildcard mapping. To make this a useful example, I am going to use the base action class to initialize a log4j logger.
BaseAction.java ->
package com.wantii.actions;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import com.opensymphony.xwork2.ActionSupport;
public class BaseAction extends ActionSupport {
private static final long serialVersionUID = 1L;
protected Logger log = LogManager.getLogger(this.getClass().getName());
@Override
public String execute() {
log.debug("in the BaseAction execute() method");
String returnValue = this.myExecute();
log.debug("myExecute returned - " + returnValue);
return returnValue;
}
public String myExecute() {
return SUCCESS;
}
}
The BaseAction class extends the ActionSupport so that any other classes that extend BaseAction will inherit all of the utility methods and constants that ActionSupport provides. The addition of the “log” variable is meant only for convenience. Now, any action classes extending BaseAction will have a log of their own with no effort. By creating a “myExecute()” method, and wrapping some logging around the call to it, my intention is to create another method that I will override in the classes that extend BaseAction. I was tempted to declare “myExecute” as abstract, thus forcing classes to override “myExecute,” but this class will be used directly by the wildcard mapping.
action_parts2.xml ->
<!-- other action mappings must be defined first -->
/WEB-INF/jsp/{1}.jsp
This mapping will create a link between all JSPs in the /WEB-INF/jsp directory to the BaseAction class. So, a request for http://yourserver/your-app/index.action will be dispatched to /WEB-INF/jsp/index.jsp. By taking advantage of this wildcard mapping feature, you give yourself the freedom to create JSPs that are handled by Struts2 without creating an individual action class for each one. You also gain a few extra advantages such as the ability to manage your link generation with the <s:url> tag. You may also be wondering why I have placed all of the JSPs I created in this and other posts in the /WEB-INF directory. You probably know that the application server will not allow direct access to the contents of the /WEB-INF directory. This is intentional. Although the Struts2 filter is mapped in the web.xml file to all requests, by hiding the JSPs in /WEB-INF, we are guaranteeing, explicitly, that all views will be generated by Struts2. You may think it is overkill, but it is really a matter of preference on my part. My concern is that I could see myself creating a JSP file that is accessed directly thinking that I would never need it to be handled by Struts2. Then, months later, I would find that I want to insert some business logic which would lead to extra work to make sure all links pointing to the JSP are updated. It is also possible that I may create a custom tag and use the tag in the JSP file and have the page break unexpectedly when I add Struts2 elements to the tag (forgetting that JSPs outside of Struts2 processing are using the tag). For completeness, here is an example index.jsp file.
index.jsp ->
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" %><%@ taglib prefix="s" uri="/struts-tags" %>Welcome! You Made It!
As you can see, there is no reason why this file couldn’t exist in the web-app without being handled by Struts2, but as I mentioned before, it is a matter of personal preference. The last thing to add about this example is that the order in which you create the action mappings in the struts configuration file does matter. If you were to leave this as the first mapping in the package, the Struts2 filter would use BaseAction to handle all requests. This is something that has come up from time to time on the struts-user mailing list. This is non-intuitive behavior for people who have worked with XML in the past. When you use wildcards in your configuration, the Struts2 filter does not look for the *best* match, it looks for the *first* match. This means you have to make sure that a wildcard mapping like this is always the last mapping in your package’s configuration.
Another feature of Struts2 that can be helpful in creating a professional-looking web-app is the availability of “global-results.” A “result” mapping in the “global-results” section of the struts.xml file (or your included xml configuration) will allow all actions to dispatch to a single JSP if necessary. One way to use the “global-results” would be to create a single JSP that will be displayed when an exception is thrown. To take advantage of this, I am going to make a few changes to the last example.
BaseAction ->
package com.wantii.actions;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import com.opensymphony.xwork2.ActionSupport;
public class BaseAction extends ActionSupport {
private static final long serialVersionUID = 1L;
public static final String EXCEPTION = "exception";
protected Logger log = LogManager.getLogger(this.getClass().getName());
@Override
public String execute() {
log.debug("in the BaseAction execute() method");
String returnValue ;
try {
returnValue = this.myExecute();
log.debug("myExecute returned - " + returnValue);
}
catch (Exception e) {
log.error("the myExecute method threw an Exception");
log.debug(e.getMessage());
return EXCEPTION;
}
return returnValue;
}
public String myExecute() {
return SUCCESS;
}
}
Now, within the BaseAction.execute() method, I am catching all exceptions. If an Exception is thrown, I perform some logging and return the String “exception.” By mapping the String “exception” in the “global-results” section, I dispatch the same view for all errors.
action_parts2.xml ->
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
/WEB-INF/jsp/exception.jsp
<!-- other action mappings must be defined first -->
/WEB-INF/jsp/{1}.jsp
The “result”s mapped in the “global-results” section are just like the “result”s mapped within an action’s configuration. This saves the time of adding the “exception” result to each action in my web-app. This error handling is nice, but Struts2 has a better facility for dealing with exceptions. Check http://struts.apache.org/2.x/docs/exception-configuration.html to see. Using the exception configuration that Struts2 provides allows you to be more granular. It allows you to specify different handling of different types of exceptions.
Now that I have covered some of the useful configuration elements available to you, I would like to move on to some of the useful functionality available when you are writing Struts2 Actions. Most of the functionality available to you while you are writing actions is available through Struts2 Interceptors. This is not as complicated as it sounds, but it is important to understand the link between Struts2 Interceptors and using Struts2 features when you are writing your actions. For instance, the first feature I am going to show you is how you can have access to the objects in the user’s session. As you will see, storing and reading objects in the session is rather easy. You simply implement an interface by adding one method. To make this work, Struts2 has an interceptor that uses introspection to discover whether or not the user’s session should be injected into the action (prior to calling the execute method). Obviously, for this to work, the appropriate interceptor must be configured to process the request.
Up to now, I have not configured a single interceptor in any of the configuration files, but there is a default set of interceptor stacks and a default stack that is configured for every package that extends struts-default. Much of the behavior that appears to be built-in to the Struts2 framework is really just a part of the way that Struts2 is configured out-of-the-box. The configuration file that works as a starting point is called struts-default.xml and it is located within the struts-core-2.x.jar file. I would suggest not changing this file, although I will look at it periodically to see what exactly is available. There are a few interceptor stacks defined in there and the defaultStack as it is defined is sufficient for now. I will cover interceptors in another article series.
Provided that you have not changed the struts.xml file in the struts-core jar file, and that your package extends the “struts-default” package, then one of the interceptors on your stack is called “servlet-config.” One of the tasks “servlet-config” handles is to check to see if your action is expecting to have access to the session object. To tell this interceptor you want access to the session, you implement the “SessionAware” interface. Here I will add access to the session object to the BaseAction.
BaseAction ->
package com.wantii.actions;
import java.util.Map;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.struts2.interceptor.SessionAware;
import com.opensymphony.xwork2.ActionSupport;
public class BaseAction extends ActionSupport implements SessionAware {
private static final long serialVersionUID = 1L;
private Map session ;
public static final String EXCEPTION = "exception";
protected Logger log = LogManager.getLogger(this.getClass().getName());
@Override
public String execute() {
log.debug("in the BaseAction execute() method");
String returnValue ;
try {
returnValue = this.myExecute();
log.debug("myExecute returned - " + returnValue);
}
catch (Exception e) {
log.error("the myExecute method threw an Exception");
log.debug(e.getMessage());
return EXCEPTION;
}
return returnValue;
}
public String myExecute() {
return SUCCESS;
}
public void setSession(Map session) {
this.session = session;
}
public Map getSession() {
return session;
}
}
If you have previous experience writing JSP/Servlet code, you probably noticed right away that the “session” is now a java.util.Map rather than an javax.servlet.http.HttpSession. So far, the BaseAction has no dependency on the JSP/Servlet API at all. Not only does this abstraction make interactions with the session much easier, but since there is no JSP/Servlet API dependency, unit testing is much easier as well. The SessionAware interface only includes the “setSession(Map session)” method, I added the getter so that the Struts2 tags can easily pick data out of the session. There is a trick to using the session in the Struts2 tags, you will want to test for your object’s existence before attempting to retrieve data from it. Here is a quick snippet that uses one of the Struts2’s logic tags to check for existence of the session and/or objects in the session before retrieving a property:
snippet ->
Hello
Returning customers <a href="http://www.wantii.com/wordpress/wp-admin/%3Cs:url%20namespace="></a>">Login.
By testing whether or not the values are available, you are allowing your code to gracefully accept future changes (such as possibly dropping the servlet-config interceptor from the stack). Another note on this snippet of code is that in the <s:if> tag, I reference “#session” rather than “session.” Since I have a getter in the action code, “session” would work. However, if I only specify “session,” then the tag will look for the “session” object on the value stack. By using “#session,” I am explicitly instructing the tag to look in the “context map.” OGNL’s “context map” has a few useful objects such as the “request,” “application” and “parameters,” I will cover OGNL further when I cover the Struts2 Tags in-depth. The purpose of using the “#session” rather than “session” is to de-couple the dependency between the action’s getter and the Struts2 Tags that might use values from the web-app’s session object.
Implementing “SessionAware” is useful for many obvious reasons, but it is not the only useful interface that can help you. For example, there is a “prepare” interceptor that will allow you to implement an interface so that a “prepare()” method is executed in your action before the “execute()” method is launched. There is also an “execAndWait” interceptor that is useful if your action code may run for an extended period of time. The “execAndWait” interceptor will send your user to an intermediate page, maybe with a “loading…” message or graphic, and from the intermediate page you would redirect back to the original action. When the original action is completed, the original action will be placed on the value stack and the appropriate view will be dispatched. You can find more information in the struts docs section on interceptors.
Before I move on to other topics, I would like to mention that given the flexibility of the Struts2 architecture, I would have built the example above slightly differently. Since a time may come where I may not want to use the “SessionAware” interface on an action, rather than implementing the “SessionAware” interface on the “BaseAction,” I would create another class that extends “BaseAction.” Then I can have the choice whether or not I want the session when I am mapping actions. Here is an example “BaseActionWithSession” class.
BaseActionWithSession ->
package com.wantii.actions;
import java.util.Map;
import org.apache.struts2.interceptor.SessionAware;
public class BaseActionWithSession extends BaseAction implements SessionAware {
private static final long serialVersionUID = 1L;
protected Map session;
public Map getSession() {
return session;
}
public void setSession(Map session) {
this.session = session;
}
}
Now, this class can be mapped the same way that “BaseAction” was mapped. Using inheritance on POJOs is easy and allows me to write simpler code.
So far, all of the views in my code have been generated through a “dispatcher.” Dispatching is the Struts2 default for view generation, but what if you want to generate a view that the user may “refresh?” If your result is dispatched to a JSP, your browser does not realize that the page presented is not the resource that the browser POSTed parameters to. For instance, imagine a chat page. Your users will type a message into an input box and submit the message. If you have programmed the chat using Struts2, you could have an action that receives the message, pushes the message onto the end of the collection of submitted messages, then dispatches the user back to the chat page so that the user will see his message and any others that have been submitted. This implementation could work, but what happens when a user wants to see the latest messages? Most likely he will press the “refresh” button in his browser. From the browser’s perspective, it does not understand that Struts2 is handling processing and it thinks that the currently loaded page (that the user is trying to refresh) is generated by POSTing the message. In this situation, modern browsers will pop up a dialog box asking the user whether they really want to POST data again. If the user allows data to be POSTed again, he/she will push another copy of the same message. If the user does not allow data to be POSTed again, the page will not refresh. No matter how you consider it, dispatching is the wrong solution for this type of problem.
There are many scenarios when dispatching is not going to be the best solution, so Struts2 offers other result types that you can use. To solve the chat room page problem I outlined above, I would use a “redirect-action” result type. There is also a “redirect” result, but as I mentioned at the start of this article, it is easy to have Struts2 Actions handle all requests. Here is a snippet that would take a “success” result and generate a view by redirecting the user to an action.
struts.xml ->
[SNIP] </pre> ShowMessages ${roomId} [SNIP]
This snippet also demonstrates the ability to use expressions in the struts.xml configuration. In this case, I am adding a parameter to the request that where the user will be redirected. The expression will look for a getRoomId() method on the AddMessage action's class. The URL generated would be http://yourserver/your-app/namespace/ShowMessages.action?roomId=X (where X is the value of roomId on the AddMessage action's instance).Before I wrap up this post, there is one more topic I want to cover. Although I spent a lot of time in the last post praising the built-in validation facilities, there are bound to be times that a complex business requirement causes you to write code that performs input data validation in the action code. Don't despair, this is bound to happen and the Struts2 creators assumed that the situation will come up. In fact, to test data and present error messages in the view is quite easy. The ActionSupport class provides a few utility methods that help, in particular addActionError(String message) and addFieldError(String field, String message). With the addFieldError(String,String) method, if you return "input" from your method, the error message specified will be displayed on the field as though that field had failed validation. The addActionError(String) method is treated a bit different, for the specified message to be displayed in the view, you have to use the <s:actionerror > tag in your view.This post (like a few others earlier) was a bit slim on the code examples. My goal was to quickly cover some of the useful configuration and programming techniques that are available and useful when working on a larger project. Now that I have covered enough of the Struts 2.x framework, I am going to move on to the Spring Framework. I would like to quickly cover Spring and Hibernate next so that I can follow-up with some posts discussing real-world projects. Let me know your thoughts so that I can know whether I am on track or not. Thanks again for reading!
This entry was posted on Wednesday, July 11th, 2007 at 8:24 pm and is filed under Java, Struts 2. You can follow any responses to this entry through the RSS 2.0 feed.
Spread the Word
del.icio.us Digg Furl Reddit StumbleUpon Technorati
You can leave a response, or trackback from your own site.
July 12th, 2007 at 8:03 am
Hi Wes,
This article was as interesting as the others.
I hope you’ll provide a light sample project that applies the concepts mentionned in your articles. A better sample that the ones provided with the S2 distribution should be great.
Details:
- Copyright’s year is 2006
- What is this “Best Deal Ads” ?
July 13th, 2007 at 1:06 pm
The copyright date was part of the template I was using, but this template has the right date. I should probably try to figure out a better way to handle it before 2008
.
The “Best Deal Ads” are from chitika.com, they aren’t contextual, so they can co-exist with the google contextual ads. I was hoping that the site would be self-sufficient (ad revenue covers hosting expense), but it doesn’t look like it. Oh well, I’ll just pay out of pocket.
July 17th, 2007 at 6:47 am
Great job done in all your Struts2 articles (I’ve red them all
).
They’ve been really helpful for me to get initiated in this world.
Many thanks and keep it on!!
Regards
July 18th, 2007 at 7:52 am
Thanks a lot for a quick Struts2 tutorial…Hope see many more…
July 19th, 2007 at 7:00 am
Great article. Can`t wait to read about wiring hibernate, spring and struts.
Thank you for your work.
September 10th, 2007 at 10:06 am
Great content, clear expressed ideas, a good read indeed! Keep on writing, after 2 years on Struts 1.x I’m eager to learn new JEE frameworks and Spring is the other one I’m interested.
If you have the time, let us know your thoughts about the lack of ActionForms (or some DTO) to represent Screen Data. Without giving too much thought about it, this seems to be a bad design because we can’t reuse those JavaBeans across multiple screens and to-and-from the Business layer…
September 10th, 2007 at 11:30 pm
Any chance of a tiles example in s2
October 5th, 2007 at 7:08 am
Great Experience while learning struts2 and pretty interesting Concepts and favourite is Interceptors in Struts2 so can you
give me some clarification about defining Interceptors,and how can we
invoke Interceptors before and after
action Invocation and finally what is diff b/w Struts2 Interceptors and Spring Interceptors
October 15th, 2007 at 5:15 am
Hi,
I am using struts2 sessionAware interface.
My problem is that whenever i open a new showModalDialog window it is creating new session.. how can i retrict from being a new session.
thnaks,
partha
November 14th, 2007 at 2:41 pm
Hi,
I need to clean few table, when user session gets expire.
Can i capture the session timeout event in Struts2?
December 5th, 2007 at 9:43 am
read alle the articles regardgin struts. like em. nice style of writing and drawing a fast and nice picutre of struts and the capabilitys. looking forward to spring and hibernate
thanks for the work
December 19th, 2007 at 9:09 am
greatly done!! After a lot of searching in the internet for struts 2 articles and howtos, I think your posts are the most complete ones and best explained!
now it’s time for spring & hibernate! let’s go!
thank you very much!
May 22nd, 2008 at 3:41 am
Hmm, you did a great job. I gain a lot of help of it. Thanx and carry on.
June 24th, 2008 at 1:26 am
Hi,
I need to clean few table, when user session gets expire.
June 24th, 2008 at 9:30 am
vikram, I would suggest taking a look here –
http://www.onjava.com/pub/a/onjava/2001/04/12/listeners.html
You might be able to incorporate a Struts DispatchListener, but a servlet app context listener seems a better fit to me.
-Wes
April 29th, 2009 at 10:42 pm
hope to see more full project example with user login authentication.
May 28th, 2009 at 6:12 pm
Any chance of a tiles example in s2
October 9th, 2009 at 7:32 am
Any chance of a tiles example in s2