ignore

Handling Navigation

In Struts 1.2, the mechanism for navigation involves the action returning an ActionForward instance from the Action's execute() method. Strecks action beans do not need to return an ActionForward, although this is one implementation choice available. Strecks provides a @NavigateForward tag, which gives flexibility in how an ActionForward is returned. It also provides hooks for pluggable navigation, opening up the possibility of providing tightly integrated support for different navigation or view rendering mechanisms. Strecks also provides additional navigation handling utilities supporting Redirect After Post, and the use of page classes.

Navigating using ActionForward

Strecks makes it particularly easy to handle what Craig McLanahan terms "outcome-based navigation", which is implemented in Struts using navigation mapping entries in struts-config.xml, retrieved using ActionMapping.findForward(result). In the action bean example below, ActionMapping.findForward() will automatically be called with the success passed in as the argument.

@Controller(name = BasicController.class)
public class ExampleOutcomeAction implements BasicAction
{
    public String execute()
    {
        //add processing here
        return "success";
    }
}

The same result can also be achieved with the @NavigateForward annotation:

@Controller(name = NavigableController.class)
public class ExampleOutcomeAction implements NavigableAction
{

    public void execute()
    {
        //add processing here
    }
    
    @NavigateForward
    public String getResult()
    {
        return "success";
    }
}

The difference between the two methods is in the choice of controller. The first, which uses BasicController, gives you convenience if you want to use outcome-based navigation and are happy for this to be built into the action bean's interface. The second uses NavigableController and gives you flexibility. The second example could be changed to handle navigation by returning an ActionForward from the action bean itself:

@Controller(name = NavigableController.class)
public class ExampleOutcomeAction implements NavigableAction
{
    public void execute()
    {
        //add processing here
    }
    
    @NavigateForward
    public ActionForward getResult()
    {
        return new ActionForward("/result.jsp");
    }
}

Redirect After Post

Strecks adds a few features which facilitate redirecting after handling posted form submissions. The typical behaviour in a Redirect After Post sequence is:

Our concern is for how state is passed from the action handling the post to the subsequent action. Two mechanisms can be used; state can either be passed via request parameters (via HTTP GET) or by adding state to the session. Use of the first mechanism prevents us from configuring the redirect in struts-config.xml, unless the parameters are static (i.e. independent of the specific request). The problem with the second mechanism is that we now need to remove any redirect specific state from the session once the redirect operation is complete.

Strecks provides the following artifacts to help provide a clean solution to these requirements:

  1. Redirect Parameters: these are objects which have redirect scope. They are stored in the session just before the redirect is issued. After the redirect has occurred, they are removed from the session and placed in the request; they do not pollute the session once the redirect is complete. Individual Redirect Parameters can be injected into the target action using the @InjectRedirectParameter annotation. An example is shown below:
    @Controller(name = BasicController.class)
    public class PostRedirectAction implements BasicAction
    {
        private String message;
    
        public String execute()
        {
             System.out.println("Message injected " + message);
            return "success";
        }
    
        @InjectRedirectParameter(name = InfrastructureKeys.STATUS_MESSAGE)
        public void setMessage(String message)
        {
            this.message = message;
        }
    }
    
  2. A Redirect Helper: this is a helper object which is designed to assist with issuing the redirect in the first place. RedirectHelper has, among others, the following methods:
    public void addRequestParameter(String name, Object value);
    public void addSessionParameter(String name, Object value);
    
    The method addRequestParameter() is called to add request parameters to the URL which is used for the redirect. addSessionParameter() is used to add a Redirect Parameter to the session. A RedirectHelper can be injected into an action bean using the @InjectRedirectHelper annotation.
  3. The @NavigateForward can be parameterised with an ActionRedirectNavigationHandler.class, as shown in the example below:
    @NavigateForward(handler = ActionRedirectNavigationHandler.class)
    public String nextAction()
    {
        return "post_redirect_action";
    }
    
    Using this method in your action bean will result in the construction of a redirect URL post_redirect_action.do?[query string], where the query string will be constructed using parameters added using RedirectHelper.addRequestParameter(). An example of the submitting form which uses the ActionRedirectNavigationHandler and the RedirectHelper is shown below:
    @Controller(name = NavigableLookupDispatchController.class)
    public class SimpleNavigableSubmitAction implements NavigableSubmitAction
    {
    
        private RedirectHelper helper;
    
        public void preBind()
        {
        }
    
        public void execute()
        {
            message = "Ran execute() method " + SimpleNavigableSubmitAction.class;
            helper.addSessionParameter("message", "Some Message");
            helper.addRequestParameter("timeStamp", 
                new Date(System.currentTimeMillis()).toString());
        }
    
        public void cancel()
        {
            //implementation not shown
        }
    
        @NavigateForward(handler = ActionRedirectNavigationHandler.class)
        public String nextAction()
        {
            return "post_redirect_action";
        }
    
        @InjectRedirectHelper
        public void setHelper(RedirectHelper helper)
        {
            this.helper = helper;
        }
    
    }
    

Page classes

Strecks explicitly supports the use of a page class. A page class in Strecks is a Java class which is directly coupled to a JSP. Its sole purpose is in assisting with the rendering of the page. Using a page class can make JSPs much more maintainable, because markup which would otherwise need to be generated using nested and conditional tags can be generated more simply in a Java class and accessed via simple JavaBean properties.

Page classes are implemented using the Page interface:

public interface Page
{
    String getPagePath();

    void setHttpServletResponse(HttpServletResponse response);
}

The method getPagePath() refers to the path to the JSP within the web application (e.g. /WEB-INF/jsps/mypage.jsp) and is used to enforce the coupling between the page and the JSP. setHttpServletResponse() is present so that the Page instance can be used for link generation, with the response object HttpServletResponse used for URL encoding. setHttpServletResponse() will be called automatically.

A typical page implementation will contain getter and setter methods for all the properties which need to be exposed to the JSP. The example below illustrates:

public class MessagePage implements Page
{
    private String message;

    public String getPagePath()
    {
        return "/message.jsp";
    }

    public void setHttpServletResponse(HttpServletResponse response)
    {
    }

    public String getMessage()
    {
        return message;
    }

    public void setMessage(String message)
    {
        this.message = message;
    }
}

Usually, the Page instance will contain additional methods designed to support page rendering, for example, formatting logic, minor transformations and link generation. These types of operations are quite cumbersome to implement using JSP tags, but very simple to implement (and test) using code.

The Page instance is instantiated and populated during execution of action bean methods, as shown in the example below. The @NavigateForward annotation can be used to allow the Page instance to be used directly for navigation.

@Controller(name = NavigableController.class)
public class ExamplePageAction implements NavigableAction
{
    private String message;

    private MessagePage page;

    public void execute()
    {
        message = "Navigation using @NavigateForward annotation in " 
				  + ExamplePageAction.class.getName();
        page = new MessagePage();
        page.setMessage("A message set specifically for the page");
    }

    @NavigateForward
    public Page getPage()
    {
        return page;
    }

    public String getMessage()
    {
        return message;
    }
}

A final step required is to choose a page context variable for exposing the page instance. This is done in the JSP using the Strecks pageBean tag. Once done, the page class methods can be invoked via JSTL expressions:

<strecks:pageBean var = "page"/>

<html:html xhtml="true">
<title></title>
<head></head>
<body>
<h1>Message Page</h1>
<p>This page simply outputs a message, produced by the action.
</p>
<p>The message from the page instance is: 
    <b><c:out value="${page.message}"/></b>
</p>
</body>
</html:html>

Using Alternative View Technologies

Controller actions all implement the interface ControllerAction, which defines the following method:

public ViewAdapter executeController(Object actionBean, ActionContext actionContext) throws Exception;

The fact that ViewAdapter is returned instead of ActionForward opens up the possibility of implementing alternative view rendering and navigation mechanisms. For handling ActionForward-based navigation, a Strecks controller will return ActionForwardViewAdapter, which simply wraps an ActionForward instance. Alternative view handling can be implemented by configuring the controller to return an instance of the RenderingViewAdapter interface, which has the following form:

public interface RenderingViewAdapter extends ViewAdapter
{
    public void render(ActionContext actionContext);
}

We can see how the ViewAdapter interface is used by looking at some code in ControllerProcessorDelegateImpl, which is responsible for calling the controller action's methods:

if (viewAdapter instanceof ActionForwardViewAdapter)
{
    ActionForwardViewAdapter va = (ActionForwardViewAdapter) viewAdapter;
    ActionForward actionForward = va.getActionForward();
    request.setAttribute(InfrastructureKeys.ACTION_FORWARD, actionForward);
    return actionForward;
}
else if (viewAdapter instanceof RenderingViewAdapter)
{
    RenderingViewAdapter va = (RenderingViewAdapter) viewAdapter;
    va.render(context);
}

If the controller action returns an ActionForwardViewAdapter, the enclosed ActionForward is returned. If RenderingViewAdapter is encountered, its render() method is called.

This mechanism is used to allow view rendering using Spring MVC's built-in mechanisms, as described in some detail in the Spring Integration section.

SourceForge.net logo java.net Member logo Copyright © 2005-2007 Realsolve Solutions