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.
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"); } }
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:
@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; } }
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.
@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; } }
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>
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.