Introduction
A common problem Struts developers encounter involves the situation where a developer places a list into request scope, only to find after a form fails validation, that the list is no longer in scope. This problem is commonly seen when a list is put into request scope in order to populate options for a select list on a form. If you are using automatic validation provided by Struts (setting validate=”true” in your Action mapping) and your form fails validation you are forwarded back to the original form and all request scoped attributes are lost. (You may be wondering how your request scoped ActionForm data manages to persist after validation fails. The reason is that the Struts RequestProcessor stuffs your ActionForm back into scope within the processActionForm() method.)
I will be proposing several solutions you can use to tackle this problem of losing your request scoped lists. The solution you decide to implement may depend on various factors - maintenance, performance, ease of development, etc. At the end of this article, I recommend the approach that I prefer to use in almost all cases. An Example Of The Problem
You create a form that lets you insert and update an Employee. You are required to capture the typical employee information – name, address, phone, etc. Additionally, the user will need to select a job code from a list of job codes.
In the example below I have created a ‘setUp’ dispatch method in an EmployeeDispatchAction that prepares the request with our list of job codes. The DispatchAction will also need an ‘update’ method that will execute if the form validates without any errors. The example shows a simple Service object with a static method that represents our business layer implementation which returns our collection of job codes. (In reality, it might be a factory or singleton, and of course our DAO would be hidden behind this layer.)
//class EmployeeDispatchAction – not showing any try/catch stuff
public ActionForward setUpForm(ActionMapping mapping, ...) throws Exception {
Collection jobCodes = Service.getJobCodes();
request.setAttribute(“jobCodes”, jobCodes);
return (mapping.findForward(UIconstants.TO_FORM));
}
public ActionForward update(ActionMapping mapping, ...) throws Exception {
//BeanUtils to copy Form to a ValueObject .. call service update
return (mapping.findForward(UIconstants.SUCCESS));
}
The mapping in the struts-config might look like:
<action path="/updateEmployee"
type="com.foobar.EmployeeDispatchAction"
name="employeeForm"
scope="request"
validate="true"
parameter="employeeAction"
input="/employeeForm.jsp"
>
<forward name="toForm" path="/employeeForm.jsp"/>
<forward name="success" path="/whateverSuccessPage.jsp"/>
</action>
Our JSP would have a form and would display the following select list:
<html:select property="jobCode">
<c:forEach items="${jobCodes}" var="code">
<html-el:option value="${code}" />
</c:forEach>
</html:select>
(You can shorten the above using the html:options tag.) So what’s the problem?
Imagine using the above example, the user submits the form but forgot to enter a required field. Since we set validate=”true” in our mapping, Struts takes care of calling the ActionForm’s validate() method when the form is submitted. If validation fails, the user will be forwarded back to the JSP form where we would typically display the validation error messages. Since we put our jobCodes collection in request scope, this list will not be available for populating our form’s select options when we are forwarded back to the JSP. Approaches to solving the problem
There are various ways we can make sure a list (or any other object) is always available for our JSP to use after validation fails. The different solutions described below have pros and cons which will vary depending on various application requirements. Some things to consider: Which solution is the quickest/easiest to implement? Which solution is the most flexible? Which solution is the easiest to maintain? Which solution requires the least amount of memory? Another factor to consider is consistency – a factor too often neglected for the sake of ‘just get a fix in as quickly as possible’. How many times have you been stuck working on someone else’s code and just after you figured out how a problem was worked out in one situation you find a totally different approach was used to tackle the same problem in another section of the application. Consistency throughout an application is extremely important. (I’d even argue that if you don’t have the time to refactor old code, then continue to use the less-than-eloquent, or maybe even slightly more complex, approach within an application simply for consistency’s sake – assuming of course you are not propagating some horrible performance or maintenance nightmare.)
Use the Form’s reset method
The reset method of an ActionForm is called before any form is loaded up into scope (even before validation takes place). The concept of this solution is that you can put anything you want into scope from within this method. This is generally a very poor solution – and could go from poor to horrible depending on what you want to do. From a maintenance standpoint you want your ActionForms as dumb and as non-business-layer dependent as possible. The purpose of the ActionForm is to capture user inputs on a page. They should not be used for anything more (other than possibly validation and resetting some form properties). A 100% ‘no-no’ would be calling some backend layer to get your collection while in the reset method. If you do this, you have now coupled your front end very tightly with the backend. What about a simple, hard-coded static list? For example, what if you are thinking to yourself, “All my page needs is to have a drop down select list of a few favorite cars for the user to select from. Since I know the car models, I’ll just stuff them into a list in request scope within the form’s reset method.” If you do this, what do you think will happen two months later when a different developer decides to replace the select list with a text box to allow the user type in the car name? Do you think the developer will remember to look in your form bean’s reset method to make sure you are not populating the list in request scope? The developer might even see the list of cars on many other pages and assume it is being populated in application scope and therefore won’t even both to look in the reset method. Remember the new code using the text box will not break if the reset is left alone, but now you’ve wasted memory populating the request with an unneeded list. The bottom line is do as little possible in the ActionForms (obviously you have to sometimes use the reset method for dealing with things like checkboxes and possibly initializing some collection or array fields).
Use the Form’s validate method
This technique has the same drawbacks as using the reset method mentioned above. It is actually made worse by the fact that you are still going to have to make sure the page is populated with your list in another place besides the validate method since the first time the page is displayed you still need the list in scope. Now you end up having to maintain populating the list in two places – in the ActionForm and somewhere in your Action.
Extend the Request Processor
I only mention this option since I have heard of people creating their own RequestProcessor in order to deal with problems that they think can’t be addressed by the standard Struts framework. I recommend you do not mess with the RequestProcessor unless you have some serious reason to change the behavior of Struts. You normally do not have any need to extend this class and you certainly would not want to extend it to deal with touching your business layer objects to generate lists to put in request scope.
Make the collection a property of your ActionForm
Since your ActionForm is always stuffed back into appropriate scope before validation takes place, you could make certain your page had access to the list by making the list a property of your ActionForm and making sure you set that form field with the list before you forward to the JSP. This technique will work but it goes against the principal stated previously that the ActionForm should only contain user fields you want to capture. In case someone is jumping to this section without having read the problem statement, I am not saying do not use collections or arrays as properties in your ActionForm, I am referring to cases where you set them as form fields for the sole purpose of holding data for the resulting JSP (such as select options). Stuffing this kind of stuff into your ActionForm just bloats the ActionForm and makes it confusing to tell at glance what information you are capturing from the user. For example, if you were to see the property – List automobiles - you would assume the user is going to be submitting a list of automobiles when, in relation to what we are discussing, this list of automobiles might be being used to display options for a select list. This creates confusion and difficult to maintain code. You want to be able to look at an ActionForm and see only the inputs you plan on capturing from the user.
Put the list in Application scope
If your lists will not change that often, and you can afford restarting the server when they do, using application scoped lists are great. You can create a servlet that executes on server start up that loads all your application scoped objects. In reality, though, it is rare that I find many cases that I can use application scoped lists. Usually the lists are updated too frequently or they are often dynamic - populated differently based on run time criteria such as the user logged in. Many argue for using application scoped lists as much as possible since they claim it avoids the performance hit which can occur every time the database is accessed to populate the list. This is true in some cases, however, if you are using a decent persistence mechanism (iBATIS, Hibernate, etc) you should not have to worry about this performance hit since these persistence frameworks can all be configured to cache data that rarely changes.
Put the list in Session scope
This solution has the advantage of being very easy - just throw the list in session scope in your action method that gets called before you hit the form. Maintenance is relatively easy also – you only have to make sure you populate it in session scope in one place. One major drawback to this approach is if you are tight on memory you may have to watch the number and size of objects you place into the session. Others also have a philosophical problem with sticking stuff in the session that does not need to truly persist. For example, user information of course makes sense to store in the session since you may have to pull that out in numerous places, but a list that is simply used to supply options for a drop down list on one page in a huge application does not need to persist throughout the whole user’s session. Another drawback to using session scope for the list is that when validation fails and you are retuned back to the page, you are using a stale list. In theory the list of options available may have changed since the time the user was on the form and when they hit submit. When you are ‘sure’ memory is not going to be a problem, and you can deal with the possibility of using stale lists, then the session does make for a quick and easy solution.
Manually call validate and use Request Scope
Not many people seem to be using this approach, yet I have found it to be the best overall solution. Here is how it works:
Do not have Struts call validate for you automatically (make sure you do not have validate=”true” set in your action mapping). We are going to call form.validate(..) manually. Create a method somewhere to set up your request with any lists or other objects that you need to have in scope. In our initial example we would use this method to put our jobCodes list into request scope. I was intentionally ‘vague’ using the word ‘somewhere’ to describe where you would create this method. If you are using a DispatchAction you might simply have a private setUp(..) method in this Action. If you are creating separate Action classes for all of your different related actions you might want to make this a separate class, or put it in a base Action class that your Actions can extend from. I like DispatchActions, and since the DispatchAction is dealing with all of the form’s related CRUD stuff (Create, retrieve, update, delete), it makes sense to implement this setUp method directly in the DispatchAction. Next, all we need to do is manually call our form’s validate method, and then, based on whether ActionErrors return or not, we decide what to do. If ActionErrors returns null or empty we know validation was successful and we can continue our processing and forward on to success. If ActionErrors is not null and not empty, we had validation errors and we need to call our setUp method to repopulate any lists then we forward back to the JSP form. So looking at our initial example code, we could change it to look like this:
//class EmployeeDispatchAction – not showing any try/catch stuff
private void setUp( HttpServletRequest request ) {
Collection jobCodes = Service.getJobCodes();
request.setAttribute(“jobCodes”, jobCodes);
}
public ActionForward setUpForm(ActionMapping mapping, ...) throws Exception {
setUp( request );
return (mapping.findForward(UIconstants.TO_FORM));
}
public ActionForward update(ActionMapping mapping, ...) throws Exception {
ActionErrors errors = form.validate( mapping, request );
if ( errors != null && !errors.isEmpty() ) {
saveErrors(request, errors);
setUp(request);
return (mapping.findForward(UIconstants.VALIDATION_FAILURE));
}
//Everything OK, continue on...
//BeanUtils to copy Form to a ValueObject .. call service update
return (mapping.findForward(UIconstants.SUCCESS));
}
Benefits to using this approach:
|