Use optional forwarding to extend Actions

Ted Husted

 

 

Many times you will find that two Actions are very similar but need one small behavior to change. One good way to handle this is to subclass one Action from the other and change the behavior that way. Though, in the case of an Action, the behavior may buried in the perform (or execute) method. It may also not really seem worth a hotspot method of its own.

DEFINITION  hotspot - also called flexible points or extension points, hotspots are locations where code may added to customize a framework. Hotspots (the hotspot subsystem) describe the different characteristics of each application that can supported by the framework. In essence, Hotspots represent the problems that a framework solves. Many object-orientated frameworks consist of a kernel subsystem and a hotspot subsystem. [Braga et al]

Meanwhile, the method signature is locked, and adding another parameter to change the behavior is not trivial.

For an Action, another way to create a hotspot is through the ActionMappings. From an architectural perspective, the ActionMappings are a red-hot  extension point in the framework. Virtually every Action in an application is designed to be extended through the ActionMappings -- most often though use of the ActionForwards provided by the mappings.

The vast majority of Actions include "success" or "failure" forwards or something very much like them. The Action is often coded to use these as static parameters. Part of the Action's API contract is that it expects such forwards to exist either locally or globally. If they don't -- white screen: the browser returns a null response.

But we're writing dynamic applications here, and the forwards can treated as dynamic parameters too.

One common circumstance is checking for the cancel button. Some Actions need to do this, others do not. We could cut-and-paste the code around, but that's not the best road to reuse.

In practice, if the cancel button is pressed, the Action needs someplace to go anyway. So, what if the Action were to check to see if it had a "cancel" forward? If so, it can check for the cancel button, and return the forward if the button was pressed. If there is not a cancel forward, or the cancel button was not pressed, the Action can just go about its business.

Here's some code that does just that:

forward = mapping.findForward("cancel");
if ((forward!=null) && (isCancelled(request))) {
 // Post token error message
 ActionErrors errors = new ActionErrors();
 errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("error.cancel"));
 saveErrors(request,errors);
  return (forward);
}

First, it checks to see if a "cancel" ActionForward has been defined. If an appropriate ActionForward has been defined (!=null) and the request was in fact cancelled, a message is posted and the "cancel" ActionForward is returned, ending the method.

If you put something like this in a base Action for your application, when you define a "cancel" forward that leads to another action, you may need to define it as a redirect.

<forward
 name="cancel"
 redirect="true"
 path="/do/Menu"/>

The redirect will clear the cancel state. If the other action is checking for cancel too, this will keep you from falling into a loop. Of course, if the other Action doesn't check for cancel, then using redirect isn't important. But code like this gets the most reuse when it's provided through a base Action that your other Actions subclass.

Another good example is whether to use a transaction token with an operation. The token code is not complex, but it would nice if we did not have to copy and paste it in wherever we needed it. It would nicer still if we could look at the configuration file and tell whether a Action wanted a token or not.

// Check for missing token 
forward = mapping.findForward("getToken");
if ((null!=forward) && (!isTokenValid(request))) { 
 // Post token error message 
 ActionErrors errors = new ActionErrors();
 errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("error.token"));
 saveErrors(request,errors); 
 return (forward); 
} 
if (null!=forward) { 
 // reset to guard against duplicate request 
 resetToken(request);
}

So, if the ActionMapping for an Action using this code includes a "token" ActionForward,

<forward name="getToken" path="/pages/error.jsp"/>

and the token is missing or invalid, it will forward to the "getToken" page instead of processing the rest of the Action. If the was a "token" forward, but it was valid, the code resets the token so it can't used again.

To close the loop, we can include a "setToken" forward to tell an Action to create a new token.


// Check for save token directive (do this last) 
forward = mapping.findForward("setToken"); 
if (null!=forward) saveToken(request);

Though, this is really a kludge, since the ActionForward URI would never used, and so we are misusing its role in the framework. A better approach would to extend ActionMappings to tell the Action whether to create a token or not,

if (myMapping.setToken()) saveToken(request);

but that's more surgery than we can squeeze into a tip =:0)

HTH, Ted.

-----

Struts Tips  are based on excerpts from the book Struts in Action. The tips released weekly on the MVC-Programmers List. To subscribe, visit BaseBean Engineering.

About Ted. Ted Husted is an active Struts Committer and co-author of Struts in Action and Professional JSP Site Design. Ted also manages the JGuru Struts FAQ and moderates the Struts mailing list.

 

 

 

0 Comments  (click to add your comment)
Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

About | Sitemap | Contact