In Struts, form’s must be bound to a specific action. Because of this, developers might create nearly identical Forms, say in JSP, in order to perform 2 distinct actions… Create vs Update.
Example:
<!-- CreateFoo.jsp -->
<html :form action="CreateFoo">
<html :text property="name"/>
<html :text property="description"/>
</html>
<!-- UpdateFoo.jsp -->
<html :form action="UpdateFoo">
<html :text property="name"/>
<html :text property="description"/>
</html>
In this scenario, the only thing that is different between the two JSP’s, is the Action that will be initiated when the form is submitted. It would be nice to have 1 form that could be used whether the system is trying to create OR update.
One approach to solving this problem would be to use Tiles and put the form fields into a separate file.
<!-- CreateFoo.jsp -->
<html :form action="CreateFoo">
<tiles :insert attribute="FooFields"/>
</html>
While this implementation would reduce some duplication, you would still end up with 3 JSP’s rather than just 1, or even 2 as in the previous example.
So, what do we do then? The trick is to re-think what you are trying to accomplish, and develop actions that are fine-grained enough to use in either scenario. In other words, when the user wants to CreateFoo or UpdateFoo, in each situation they want to EditFoo. So, instead of thinking about the 2 flows as atomic units of work, they can be broken down as:
CreateFoo = (1) Show FooForm (2) Edit FooData (3) Create Foo
UpdateFoo = (1) Show FooForm (2) Edit FooData (3) Update Foo
The 2 distinct flows show signs of commonality and we end up with 1 JSP that can be used in either the Create or Update flow.
Example:
<!-- EditFoo.jsp -->
<html :form action="EditFoo">
<html :text property="name"/>
<html :text property="description"/>
</html>
Now the question is… How do I incorporate the single ‘Edit’ page into an Update or Create Flow?
First of all, don’t try to define the page flows for the Create or Update flows outright, just access the Create or Update action mappings and let those actions delegate responsibility to other actions for the data they require.
Example:
<action path="/CreateFoo"
type="my.action.CreateFoo"
name="FooForm">
<forward name="success" path="/FooSummary.jsp"/>
<forward name="noFormData" path="/EditFoo.jsp"/>
</action>
<action path="/UpdateFoo"
type="my.action.UpdateFoo"
name="FooForm">
<forward name="success" path="/FooSummary.jsp"/>
<forward name="noFormData" path="/EditFoo.jsp"/>
</action>
<action path="/EditFoo"
type="my.action.EditForm"
input="/EditFoo.jsp"
name="FooForm">
<forward name="success" path="/Exit.do"/>
</action>
When the Create/Update flows are invoked, they will check to see if the FooForm contains any data. If not, those actions can return a forward that will cause the EditFoo.jsp to be displayed.
The only problem left is, to return control back to the ‘calling’ actions.
Note that the /EditFoo action mapping invoked /Exit.do upon successful completion (which is just the submission of the EditForm without any validation errors). So how do we tell Exit.do where to go?
Create a Stack in the session called ExitForwards. An action mapping would then add itself to the stack whenever it chains to a delegate flow, like when Create or Update flows branch off to the Edit flow.
An easy way to do this is to create a subclass of the Struts ActionForward called ExitForward.
package my.forward.ExitForward
public class ExitForward extends org.apache.struts.action.ActionForward
{}
Not one of your more complicated classes is it?
Then in your action mappings, just indicate that a forward is an ExitForward by simply specifying the ExitForward class type in the action mapping.
<action path="/CreateFoo"
type="my.action.CreateFoo"
name="FooForm">
<forward name="success" path="/FooSummary.jsp"/>
<forward name="noFormData" path="/EditFoo.jsp"
className="my.forward.ExitForward"/>
</action>
<action path="/UpdateFoo"
type="my.action.UpdateFoo"
name="FooForm">
<forward name="success" path="/FooSummary.jsp"/>
<forward name="noFormData" path="/EditFoo.jsp"
className="my.forward.ExitForward"/>
</action>
The only thing left to do is override the RequestProcessor to Push the current ActionMapping onto the ExitForwardStack (in the Session) whenever a forward of type ExitForward is returned.
//Override the RequestProcessor (or TilesRequestProcessor)
protected void processForwardConfig(HttpServletRequest request, HttpServletResponse response,
ForwardConfig forward) throws IOException, ServletException {
if (forward != null) {
if (forward instanceof ExitForward) {
Stack exitForwardStack = (Stack)request.getSession().getAttribute("ExitForwardStack");
ActionForward redirectForward = new ActionForward();
redirectForward.setRedirect(true);
redirectForward.setPath(this.processPath(request, response));
exitForwardStack.push(redirectForward);
}
}
super.processForwardConfig(request, response, forward);
}
Then just wire up your Exit.do mapping and Action class
<action path="/Exit"
type="my.action.GetExitForward"/>
public class GetExitForward extends Action{
public ActionForward Execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) throws Exception{
Stack exitForwardStack = (Stack)request.getSession().getAttribute("ExitForwardStack");
ActionForward forward = (ActionForward)exitForwardStack.pop();
return forward;
}
}
That’s all there is to it. With these modifications, you can transform your Struts application into a lean, mean, re-use machine. Applying these changes will allow you to re-use proven functionality in flows without having to create a lots of unnecessary action mappings and JSP’s.