Java Development News:

Struts action mappings: Devide Et Impera

By Michael Jouravlev

01 Dec 2004 | TheServerSide.com

This article discusses different combinations of a Struts action class and a form bean and how these combinations can be used.

Full action

This is arguably the most popular form of Struts action. It contains an action class and a form bean. The action mapping for this operation looks like this:

 <action path = "/fullAction" type = "com.acme.struts.MyAction" name = "myForm" input = "/WEB-INF/jsp/dataError.jsp"> <forward name="OK" path="/WEB-INF/jsp/viewResult.jsp"/> <forward name="ERROR" path="/WEB-INF/jsp/busError.jsp"/> </action>

 

 

 

 

Full action call sequence

  • Struts controller component receives a request.
  • Struts identifies the action mapping which is responsible for request processing.
  • Struts creates a new instance of the form bean, if it was not found in the scope or if the scope has "request" type. If the bean exists in the scope, it is reused.
  • If form bean defines reset() method, it is called (1)
  • Struts populates the fields with request arguments, using mutators (2)
  • Struts calls validate() method if "validate" attribute of action mapping is not set to "false" (3)
  • If validate() returns non-empty ActionErrors object, control is forwarded to an URI identified by "input" attribute of the action mapping (4a)
  • if validate() returns empty ActionErrors object or null, Struts calls execute() method of the action class (4b)
  • execute() method returns an ActionForward object, which is used by Struts to select the destination URI (5)

Consequences and usage tips

  • execute() method of an action class is not called if ActionForm.validate() returns non-empty ActionErrors object. Instead, control is directly routed to the URI defined in "input" attribute of action mapping.
  • "input" attribute of action mapping allows forwarding to an URI only. If you need to redirect, use Controller.inputForward property. This property was introduced in Struts 1.1. When it set to "true" it indicates that Action.input is not a URI, but a name of an Action.forward element.

Form-only action

This action combines a custom form bean and the standard ForwardAction class. This type of action can be used when an action has only two outcomes, one of which is an error.

 <action path = "/formOnlyAction" type = "org.apache.struts.actions.ForwardAction" name = "myForm" input = "/WEB-INF/jsp/error.jsp" parameter = "/WEB-INF/jsp/viewResult.jsp" />

 

 

 

 

Form-only call sequence

  • Struts creates a new instance of the form bean, if it was not found in the scope or if the scope has "request" type. If the bean exists in the scope, it is reused.
  • If the form bean defines reset() method, it is called (1)
  • Struts populates the fields with request arguments, using mutators (2)
  • Struts calls validate() method if "validate" attribute of action mapping is not set to "false" (3)
  • If validate() returns non-empty ActionErrors object, control is forwarded to an URI identified by "input" attribute of the action mapping (4a)
  • if validate() returns empty ActionErrors object or null, control is forwarded to an URI identified by "parameter" attribute of the action mapping (4b)

Consequences and usage tips

  • There is no action class to put business code in, so all custom code must be called from either reset() or validate() methods of the form bean.
  • validate() performs two functions: validating request arguments, and accessing the business layer.
  • Because action mapping does not contain "forward" elements, control can be only forwarded, but cannot be redirected.
  • One of the possible usages of a form-only action is response rendering. A form bean can receive a business object id with a request, then look up the business object in the business/persistence layer, fill in the form fields and forward to a JSP which would display the result.
  • It may be convenient to use "session" scope for form-only actions, thus implementing caching of output data.

Action class-only action

Struts does not require to declare a form bean for an action mapping. Hence the seemingly tautological action-only action.

 <action path = "/actionOnlyAction" type = "com.acme.struts.MyAction" input = "/WEB-INF/jsp/error.jsp"> <forward name="OK" path="/viewResult.do"/> <forward name="ERROR" path="/WEB-INF/jsp/error.jsp"/> </action>

 

 

 

Action-class only call sequence

  • execute() is called on the action class (1)
  • execute() returns ActionForward object; target URI must be defined in a "forward" element of action mapping (2)

Consequences and usage tips

  • This action does not declare a form bean, thus Struts passes null to execute() method instead of a form bean.
  • If the action receives request parameters, they must be retrieved from the request object manually. Action class-only action may be used if just a few or no parameters are passed with request, otherwise it is easier to use a form bean.
  • Control may be either forwarded or redirected to another action or JSP page. If you are forwarding control to a JSP, make sure that necessary form beans with output data exist in the scope.
  • Action-class only action cannot be called from HTML FORM, because Struts expects to find the form bean definition in an action mapping which serves the FORM. As a workaround, you can call an action-class only action from a link.

JSP-only action

This is the most compact, most error-prone and the least useful type of action. It can be used if it is forwarded to from another action, or if there are other means to obtain needed data.

 <action path = "/JSPOnlyAction" type = "org.apache.struts.actions.ForwardAction" parameter = "/WEB-INF/jsp/viewResult.jsp" />

 

 

 

 

The call chain

  • request is received by Struts controller component
  • Struts calls ForwardAction.execute()
  • ForwardAction.execute() forwards control to an action defined in "parameter" attribute of the action mapping.

Consequences and usage tips

  • This action does not use a form bean, thus it is not created, not initialized, not populated and not passed to an action class.
  • Though I called it JSP-only operation, it can forward to any URI including other action if needed.
  • Because a form bean is not instantiated during the course of this operation, JSP relies on a form bean which was created earlier and is in the scope of the action. Form bean could be created in another action which forwards control to this action, or it could be created with "session" or higher scope during processing of another request.
  • Usage of this action is very limited. For example, it can be used as a switching yard to forward to another action. After application is compiled and deployed, the course of operation can be switched in the config file using this action.

Two actions, one form

Struts allows to call one action from another, thus it is possible to implement a simple Chain of Responsibility.

 <action path = "/sourceAction" type = "com.acme.struts.Action1" name = "myForm" input = "/WEB-INF/jsp/error1.jsp"> <forward name="OK" path="/targetAction.do"/> </action> <action path = "/targetAction" type = "com.acme.struts.Action2" name = "myForm" input = "/WEB-INF/jsp/error2.jsp"> <forward name="OK" path="/WEB-INF/jsp/viewTarget.jsp"/> </action>

 

 

 

Two-action call sequence and usage tips

The call sequence does not differ from the full-action case. The problem is, that Struts calls reset() and validate() methods again for the target action, and it populates form bean fields as well. To use form bean as a transport/command object we must prevent overwriting of form bean fields.

The solution depends on how the target action is called. If the target action is forwarded to, we can use a token in the HttpRequest object to distinguish if an action is the first action in the chain.

  • Check for the token in the reset() method of the form. If token not found, then this action is the first in the chain. If token is found, do not reset form fields, because this is a chained action.
  • Plant a token into the request object. This must be done in reset() method, because it is always called unlike validate(). validate() may be not called if "validate" property is set to "false" in struts-config.xml file.
  • Verify in each mutator, that the action is first in chain. If not, do not allow changing the field value. This prevents Struts from modifying the form fields. •
  • check for token in validate() method. If not present, validate form fields, because this is the first action in chain.

You do not need to remove the token from the request object, the request will be disposed automatically.

If you use redirection to call the target action, you cannot use request token, because request object is recreated after redirection. The following solutions are possible:

  • Read action mapping name. This works only if you have strict rules defining when each action is called. If struts-config.xml is changed, the code would have to be updated.
  • Store a token on the server in the object with session or higher scope. The token should be promptly removed when the last action in chain finishes.
  • Stick a token into the target URI of the response object, it would be passed to a target action as an HTTP request parameter after redirect completes. This solution takes some additional effort, but the result worth it.

Important: use the same form scope in both actions for predictable behavior.

Two actions, two forms

As long as we already have two actions, why not to have two different forms.

 <action path = "/inputAction" type = "com.acme.struts.Action1" name = "inputForm" input = "/WEB-INF/jsp/error1.jsp"> <forward name="OK" path="/outputAction.do" redirect="true"/> </action> <action path = "/outputAction" type = "com.acme.struts.Action2" name = "outputForm" input = "/WEB-INF/jsp/error2.jsp"> <forward name="OK" path="/WEB-INF/jsp/viewTarget.jsp"/> </action>

 

 

 

 

>

Two-action, two form usage tips

The call sequence does not differ from the previous case. But because now we have two different form types and different instances, we can make the code much cleaner.

This design can be used to handle input and output of web applications. The source action receives the request, the source form validates the input data. If input is valid, the control is redirected to output action. Struts would want to populate form bean fields again, but here is the trick: you can either define fields with different names, or even better, you can define only accessors for the output form bean. Struts uses accessors and mutators to operate on form bean fields, so without mutators it would not be able to modify the fields of the output form.

The control flow looks like this:

  • Struts calls reset() on the input form bean (1)
  • Struts populates the fields of input form bean using mutators (2). Input form bean does not even have to define the accessors.
  • truts calls validate() on input form bean, which validates request data (3)
  • If validate() returns non-empty ActionErrors object, control is forwarded to an error page identified by "input" attribute of the action mapping (4a)
  • if validate() returns empty ActionErrors object or null, Struts calls execute() method of the input action class (4b)
  • execute() updates business and persistent objects.
  • execute() creates new ActionForward instance with modified URI of the target action, adding to it the ID of an object which must be displayed.
  • if execute() returns "OK", Struts redirects to the output action (5)
  • Struts calls reset() on the output form bean (6)
  • Struts tries to populate the fields of the output form bean, but because there are no mutators defined, they are not changed (7)
  • validate() has nothing to validate in the output form, so it returns null (8)
  • execute() method of the action class locates business object using ID passed with the request, fills out the fields of the output form with business data and returns "OK" (9b)
  • Struts displays the result page (10)

Because I fill out the output form manually with the persistent data, I do not need to preserve values of the input form bean. Thus, I do not need to use forwarding, and I can employ redirection instead. This way, input action and output action become less tied to each other.

A very nice side effect of this solution is that output page can be easily reloaded without processing the request in input action again. This helps to prevent double submission of input data.

More on double submit problem and how it can be defeated see in my other article: Redirect After Post

About the Author

Michael holds an MS in Computer Science from Moscow Aviation Institute (technical university), Moscow, Russia. He has more than 10 years of experience developing applications for MS-DOS, Windows and Java platform. he has devoted the last 5 years to server-side Java applications. Curently he is employed as a software engineer at International Lottery and Totalizator, Inc.