Introduction

Author: Kasper Graversen, 12/12/07
Keywords: Struts, Actions, Forwards
Abstract: on struts
subscribe to my RSS feed


Bookmark and Share


Introduction

An overview over the central classes of the Struts framework.
An overview over the central classes of the Struts framework.

Model

ActionForm

DynaActionForm

Lose type checking lose the validate method fewer classes, forms are specified as xml may introduce harder to find bugs remember, xml is not a programming language..

Form state conversion

Struts does not support you in the model aspect of your application. However, Struts encapsulate the model of the webapplication forms in subclasses of ActionForms or by the use of HashMaps (through DynaActionForm). The separation between the webapplication form state and the model state may often map 1:1. There are several best practices to handle this situation
  • Never have your business logic use the ActionForm entities! This will inevitably tie the application to the fact that one of its front-ends are webdriven!
  • Have in each ActionForm a convert() method which is able to transform the actionform instance into a databean needed in the other layers of the application
  • Explicitly create a converter class that has several overloaded converter methods public Bean convert(SomeActionForm)

Controller/Action

Action Mapping

Attribute Description
attribute The name under which the Action's ActionForm bean is bound (should it be different than the bean's specified name attribute). The attribute is often used when there is a need to use the same ActionForm bean instance at the same time. However, a better way to accomplish the same is to create two unique <form-bean> definitions that reference the same ActionForm object.
className The fully qualified class name of the ActionMapping implementation. If the class-Name attribute is omitted, the ActionMapping defined in the ActionServlet's mapping initialization parameter is used.
[exceptions] Specifies a collection of exception handlers that can be associated with a particular ActionMapping.
forward The context-relative path of the servlet or JSP resource that will process this request using a forward. This attribute is used if you do not want an Action to service the request to this path. The forward attribute is valid only if no include or type attribute is specified.
type The fully qualified class name of the Action class being described by this ActionMapping. The type attribute is valid only if no include or forward attribute is specified.
include The context-relative path of the servlet or JSP resource that will process this request using an include. This attribute is used if you do not want an Action to service the request to this path. The include attribute is valid only if no forward or type attribute is specified.
input The context-relative path of the input form to which control should be returned if ActionErrors are returned from the ActionForm or Action objects.
name The name of the ActionForm bean that is coupled with this action. It is not the classname of the ActionForm! It is the unique identifier set-up in the <form-bean> element.
path The context-relative path of the submitted request. The path must begin with a /.
[parameter] A generic configuration parameter enabling passing additional information to the Action instance.
roles Identifies the collection of user roles that can successfully request this Action mapping.
scope Names the scope of the form bean that is bound to the described Action.
unknown When true, this ActionMapping instance acts as the default <action-mapping> for the hosting application.
validate When true, causes the ActionForm.validate() method to be called on the form bean associated to the Action being described. If the validate attribute is set to false, then the ActionForm.validate() method is not called.
Table caption: Table explaining the attributes associated with the Action node of the structs-config.xml A typical action declaration for validating a form is
<form-bean name="formExample" type="com.yourcompany.struts.ExampleActionForm"/>
...
<action path="/urlExampleForm"                        <!-- url in the browser + ".do"
        name="formExample"                            <!-- name of registered form -->
        type="com.yourcompany.actions.ExampleAction"  <!-- the action dealing with valid forms -->
        input="/index.jsp"                            <!-- page/action to visit upon failure of invalid form validation -->
        validate="true"                               <!-- validate the form automatically before invoking the action -->
        scope="request">
</action>

Forward action

You should never use direct links to other pages. Not only can it break your Struts application (if multiple modules are in play), it is also a direct violation of the MVC architecture. The fundamental philosophy of MVC is that it is the controller that decides what to display on certain events. Thus instead of a direct link to a page, you must set up an action that forwards to that page. The advantage is that later you may find you want certain business logic to be executed upon visiting the page in question. This is possible with this setup. Additionally, using direct li It is not only bad design, but may lead to problems in your Structs application if you use direct linking. Firstly, at some point in the development it may be realized, that an event need Instead use a ForwardAction. To link home.do to index.jsp amend struts-config.xml in the following
  • Add a general forward rule <forward name="home" path="/home.do"/>
  • Add the action <action path="/home" forward="/index.jsp" />
  • Create links to that page using <html:link forward=”home”>Goto home</html:link>
Alternatively and a bit terser (however less recomended in literature for some reason yet unknown to me)
  • Add the action <action path="/home" parameter="/index.jsp" type="org.apache.struts.actions.ForwardAction" />
  • Create links to that page using <html:link page=”/home.do”>Goto home</html:link>

Include action

Instead of forwarding the request, this action includes the given ressource instead. As discussed in the forward action, you should never refer to jsp pages directly, rather views must be fetched from the controller/actions. In order to include another ressource do the following
  • Incorporate the ressource on your page by <jsp:include page="/App/legacy.do" />
  • Setup the action in struts-configuration.xml
    <action path="/legacy"
            parameter="/foo/LegacyServlet"
            type="org.apache.struts.actions.IncludeAction" />
If you find that you need this action, consider using Tiles or portlets instead of this kind of action.

DispatchAction

When an action class is to handle more than one kind of input, you should inherit the DispatcherAction rather than the Action class. This situation arises, when a form has more than one button, e.g. "edit", "delete" and "add" but you want to centralize the code for these actions in one class. To use this class take the following steps.
  • Create a form action implementing a method for each kind of action possible in the form. Be careful as not to override the execute() method. The execute method will be invoked by the struts framework and using reflection will determine on the basis of a request parameter which method to invoke. This explains the 4 arguments each of the action methods must take.
    class MemberAction extends DispatchAction {
    
        public ActionForward edit(ActionMapping mapping,
                                  ActionForm form,
                                  HttpServletRequest request,
                                  HttpServletResponse response) throws Exception {
            ...
            mapping.findForward("edit-success");
        }
    
        public ActionForward delete...
    ...
    }
    
  • Setup the action in the struct-configuration.xml and remember the magic takes place in the parameter attribute below
    <action path="/member-screen-app"
            input="/ListMembers.jsp"
            type="foo.MemberAction"
            parameter="theAction"
            scope="request">
           <forward name="edit-success" path="RejectAppSuccess.jsp" redirect="true"/>
           ...
    </action>
    
  • In your view setup submit buttons so the name of the button matches the name of the methods in your action
    <html:submit property="theAction">Delete</html:submit>
    <html:submit property="theAction">Edit</html:submit>
    
    In turn these will be translated into the url's such as ..foo.do?theAction=edit&id=1
If you want to use images or normal links, you have to use a LookupDispatcherAction instead.











	

View

Tiles

Installing

  • Ensure that a struts-tiles.jar is available in the WEB-INF/lib directory of your web application.
  • Add this init-param to the Action Servlet definition in web.xml:
     <init-param>
         <param-name>chainConfig</param-name>
         <param-value>org/apache/struts/tiles/chain-config.xml</param-value>
     </init-param>
    
  • Configure the Tiles Plugin to load your definitions in the bottom of your struts-config.xml (just after message-ressources)
    <plug-in className="org.apache.struts.tiles.TilesPlugin">
        <set-property property="definitions-config" value="/WEB-INF/tiles-defs.xml"/>
    </plug-in>
    
  • At the top of each JSP page that will use the Tiles custom tags, add a line that declares the Tiles custom tag library.
    <%@ taglib uri="http://struts.apache.org/tags-tiles" prefix="tiles" %>
    

Defining tiles and extending existing tiles

Best usage tiles

The best way to use tiles is
  • To set up a number of page definitions
  • Make them accessible using forwarding in an action <forward name="success" path=".simple.admin.main" /> i.e. in case of success or failures.
  • Make them accessible using them as inputs for actions <action path="/MyFormHandler" input=".simple.admin.main"..., i.e. when simple validation (within the actionformbean) fails and the form needs be redisplayed.
  • Notice you cannot set them up as global forwarders

Simple usage tiles

The simplest usage is to create a jsp page that tells at a certain point to insert the set of tiles defined by a certain name in the tiles-defs.xml. This is unpreferred, as such a page should not be needed at all. Presumably it can be useful for those rare but odd cases that may arise in any software project.
  • // somepage.jsp
    <%@ taglib uri="http://struts.apache.org/tags-tiles" prefix="tiles" %>
    <tiles:insert definition="templateDefinition" />
    
  • // tiles-defs.xml
    <?xml version="1.0" encoding="ISO-8859-1" ?>
    <!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 1.1//EN" "http://struts.apache.org/dtds/tiles-config_1_1.dtd">
    <tiles-definitions>
        <definition name="templateDefinition" path="/layouts/layout.jsp">
           <put name="title"  value="This is the title." type="string"/>
           <put name="header" value="/layouts/header.jsp" type="page"/>
           <put name="body"   value="/layouts/body.jsp" type="page"/>
        </definition>
    </tiles-definitions>
    
  • view page /somepage.jsp

Using CSS

Unless you have all your .css files in the root folder (which generally is a bad idea), the you need to specify a relative path for the location of the css files. Alternatively, you can hardcode a full URL http://www... this however, causes problems if you want to test offline, have the same application running on several urls or in case you move the application to a different url.
<%@ taglib uri="/tags/struts-tiles" prefix="tiles" %>
<html>
<head>
<style type="text/css" media="screen">
@import "<%= request.getContextPath() %>/styles/styles.css";
</style>
<title> <tiles:getAsString name="title"/> </title>
</head>

Error Handling

Error handling is spread out across a number of entries.
  1. The ressoure property file can include the l8n string for the errror message.
  2. The action must be configured in struts-configuration.xml to validate by setting the validation="true" attribute.
  3. The action class must implement validate()
    @Override
    public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) {
        ActionErrors errors = super.validate(mapping, request);
    
        if(...) {
            errors.add("warehouseName", new ActionMessage("foo.bar.passwordtooshort")); // using a key from the ressource file
    	}
        return errors;
    }
    
  4. In the view, the error will be displayed in the view using the tag <html:errors property="password"/>

Configuration file magic and Modularization

Struts allows you to modularize your webprojects into smaller chunks in two different ways. The simple solution basically allows you to split your stuts-configuration.xml into several pieces whilst still having Struts regarding them as one file. The other solution, called modules, allows you to split your application into many name spaces. This allows you to for each module to define a mapping between a name and a class independently of other modules.

Finally we look at how to significantly reduce the size of your configuration files by the use of wildcards.

Splitting struts-configuration.xml

To split up your struts-configurations.xml into several pieces, all you need to do is to specify those names in web.xml
<servlet>
    <servlet-name>action</servlet-name>
    <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
    <init-param>
        <param-name>config</param-name>
        <param-value>
            /WEB-INF/struts-config.xml,
            /WEB-INF/struts-config-global.xml,
            /WEB-INF/struts-config-admin.xml
            /WEB-INF/struts-config-reports.xml
            /WEB-INF/struts-config-user.xml
        </param-value>
    </init-param>
</servlet>
The above code has split the config file in 4. One file to

Struts modules

Configuration file wildcards

Lenghty configuration files can be avoided if you stick to a few simple naming rules and wildcards. Below you'll find two actions suffering from the fact that they are almost identical, but being quite spacious.
<action path="/ContractFormHandler_full"
    type="foo.action.ContractAction"
    name="ContractFormBean"
    scope="request"
    validate="true"
    input="/full.jsp">
    <forward name="success" path="/full.jsp"/>
</action>
<action path="/ContractFormHandler_mini"
    type="foo.action.ContractAction"
    name="ContractFormBean"
    scope="request"
    validate="true"
    input="/mini.jsp">
    <forward name="success" path="/mini.jsp"/>
</action>
Using wildcards, these two actions can be merged into:
<action path="/ContractFormHandler_*"
    type="foo.action.ContractAction"
    name="ContractFormBean"
    scope="request"
    validate="true"
    input="/{1}.jsp">
    <forward name="success" path="/{1}.jsp"/>
</action>
A * matches zero or more characters upto a /. If you need to included slashes in your wildcard use ** instead. The values of the wildcards are accessed by {n} where n is the possition of the wildcard. Generally, you should refrain from using more than one or two wildcard at any one time to reduce the complexity of reading the config file. Finally, {0} has a special meaning being the full path. If more than one <action> matches a request, then its the action without wildcards that takes precedence. After that, its the last defined action that has precedence.

Struts best practices

Disallow direct access to *.jsp files

A good way to enforce the MVC and in general to ensure the validity of your applications are to disallow direct access to JSP pages outside the webapplication itself. This forces page display to only take place after an action has executed. The following code should be added to you web.xml. It sets up a role
<security-constraint>
	<web-resource-collection>
		<web-resource-name>Deny Direct Access</web-resource-name>
		<description>Deny access to JSP's by associating them with denied role</description>
		<url-pattern>*.jsp</url-pattern>
	</web-resource-collection>
	<auth-constraint><role-name>Denied</role-name></auth-constraint>
</security-constraint>

<security-role>
	<role-name>Denied</role-name>
</security-role>

Date in actionform

In order to store dates in your forms, your accessor methods must be able to code and de-code Strings into Dates. At first, it may sound as though Struts is missing a vital compontent of supported types. However, the following solution enables the most versatile approach to date formatting and parsing.

public DateForm extends ActionForm {
	Date start;

    public void setStart(String startDate) {
        start = DateFormat.getDateInstance().parse(startDate);
	}

	public String getStart() {
		return DateFormat.getDateInstance().format(start);
	}
}
You will most likely benefit from using the helper class DateParser defined in formatting and parsing.

Error messages

A form bean that is missing the attribute+get/set methods "distributorAccount"
avax.servlet.jsp.JspException: No getter method for property
distributorAccount of bean org.apache.struts.taglib.html.BEAN

       at org.apache.struts.util.RequestUtils.lookup(RequestUtils.java:968)

       at org.apache.struts.taglib.html.BaseFieldTag.doStartTag(BaseFieldTag.java:176)

       at org.apache.struts.taglib.html.HiddenTag.doStartTag(HiddenTag.java:123)

       at _newreturns._searchPopup1._jspService(_searchPopup1.java:111)

       [SRC:/newreturns/searchPopup1.jsp:19]

How to have 2 submit buttons in a form

How to add two submit buttons to a form in struts?

Use a DispatchAction as described above..

Often, however, the following hack is used. Just use the <html:submit.../> tag multiple times. Use different "property" attributes in each tag. In your Action, if the property associated with an <html:submit... /> tag is not null, then that's the button that was clicked.



Comments

If you have any comments to this article, please drop me a mail at firstclassthoughts at gmail dot com please indicate if I can't publish whole or parts of your comment on the site.


If you like this site consider subscribing to my RSS feed or how about subscribing by Email.


Help spread the word

Share this post on your favorite social bookmarking sites:
If you enjoyed this article, found it thought provoking, educative or otherwise good, please link to this page from your page or social bookmarking page. If you have any texts you think are worth publishing on First Class Thoughts, don't hesitate to send me a mail! Quality always welcome.


Bookmark and Share


The most recent contributions
28/07/09 Magic in mathematics II Fun with the number cyclic numbers, and specifically with 142857 as it is the smallest of such numbers.
13/07/09 My top 8 time-saving Firefox shortcuts This article presents my favorite top 8 time-saving shortcuts in Firefox 3.0 and Firefox 3.5. Get to know these and you'll be saving a lot of time. They have been ordered by "the element of most surprise"
20/05/09 Board Game Jungle speed / Arriba Review of the cool game "Jungle Speed" aka. "Arriba".
16/05/09 Danish Twin words "Twin words" are words that not only have multiple meanings, they must be composed next to each other in meaningful sentences. This article explores the concept of twin words.
Nothing of interest? Try browsing the entire article archive...