Home Struts new Hot Jobs new

Free Downloads


The Struts Catalog

Toward an overview of Struts Design Patterns and Strategies. 

NOTE: Since this is catalog, some patterns or strategies may be mutually exclusvie. An application is not expected to implement every pattern or strategy in this catelog.


Remove the sample "admin" mappings from a production application

The example struts-config.xml file in Struts 1.0.x includes the following mappings for administration purposes:

<!-- The standard administrative actions available with Struts -->
<!-- These would be either omitted or protected by security -->
<!-- in a real application deployment -->
<action path="/admin/reload"
type="org.apache.struts.actions.ReloadAction"/>

and so forth. 

If these are not removed when the application is deployed, malicious users can create havoc by removing mappings. They might notice, for example, that they are sent to /login.do to log in. They can remove this mapping by sending a request to /admin/removeMapping.do?path=/login, effectively removing the front door of the site and preventing all users from entering.

It is important to follow the advice in the sample configurations, and either omit these mappings from a shipping application, or protect them with security.

This does not apply to Struts 1.1 beta or the nightly build, since the admin functions are not available.


Use <html:link rewrite> to reference stylesheets and other HTML assets.

The html:link rewrite accepts a context-relative URL, and also encodes it if necessary, so resources subject to container managed security can be retrieved.

<LINK REL='stylesheet' HREF="'<html:rewrite page="/path/to/CSS"/>' TYPE='text/css'>


Link only to Actions

In a Model 2 environment, the pages are suppose to be pretty but stupid. Flow to a page should go through an
Action first, and the Action should assemble all the data that a page might need and put it into the request or session context. The page then just grabs what it needs, and figures out how to display it. The Action
may not known the address of the page (that's in the config file), but it does need a punch list of what data the page may require. Every page should have an Action handler. One Action may handle several different
pages, or a page could be handled by several different Actions (e.g. an error page), but each page should have at least one handler. 

If a JSP ends up on your browser's address bar after the initial index page, then you're missing an Action ;-)

[Since .04]


Use a forwarding Action for static pages

If your page does not need any data collected for it, you can use a generic forwarding Action as a handler. This way if you're needs change, you can just update the configuration file, and you have not given away the location of your pages. 

/**
* Generic dispatcher to ActionForwards.
* @author Ted Husted
* @version $Revision: 1.1 $ $Date: 2001/08/01 $
*/
public final class DispatchForward extends Action {

// --------------------------------------------------------- Public Methods

/**
* Forward request to "cancel", {forward}, or "error" mapping, where {forward}
* is an action path given in the parameter mapping or in the request as
* "forward=actionPath".
*
* @param mapping The ActionMapping used to select this instance
* @param actionForm The optional ActionForm bean for this request (if any)
* @param request The HTTP request we are processing
* @param response The HTTP response we are creating
*
* @exception IOException if an input/output error occurs
* @exception ServletException if a servlet exception occurs
*/
public ActionForward perform(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {

// -- isCancelled?
if (isCancelled(request)) {
form.reset(mapping,request);
return (mapping.findForward("cancel"));
}

// -- Locals
ActionForward thisForward = null;
String wantForward = null;

// -- Check internal parameter for forward
wantForward = mapping.getParameter();

// -- If not found, check request for forward
if (wantForward==null)
wantForward = request.getParameter("forward");

// -- If found, consult mappings
if (wantForward!=null)
thisForward = mapping.findForward(wantForward);

// -- If anything not found, dispatch error
if (thisForward==null) {
thisForward = mapping.findForward("error");
ActionErrors errors = new ActionErrors();
errors.add(ActionErrors.GLOBAL_ERROR,
new ActionError("action.missing.parameter"));
saveErrors(request, errors);
}

return thisForward;

} // end perform

} // end Action

[Since .04]


Place all Java ServerPages below WEB-INF

Note: This is not supported by all containers (e.g. WebLogic). If you believe it should be, contact your vendor. The Servlet 2.3 specification makes it much clearer that this is allowed.

The container provides security for all files below WEB-INF. This applies to client requests, but not to forwards from the ActionServlet. Placing all JSPs below WEB-INF ensures they are only accessed through Actions, and not directly by the client or each other. This allows security to be moved up into the Controller, where it can be handled more efficiently, and out of the base presentation layer.

However, if your Java ServerPages need to access a number of other HTML resources, like images and stylesheets, it may be more convenient to leave them in the document root. If you are using Link only to Actions, then the address of your JSP's is never exposed anyway, and this strategy loses much of its value. The Link only to Actions strategy is usually sufficient.


Use the ActionMapping Parameter property to distinguish operations

When more than one mapping leads to an Action, the standard Parameter property can be used to determine which mapping was dispatched. For the best flexibility, also retain the "action" property in base form class, and check for both when dispatching:

Organize ActionMappings into a command structure

It's been said that query strings are the universal command line. This is especially true of ActionMappings, which usually lead to some type of dynamic operation. Since ActionMappings can be named in any convenient manner, name them after the command structure (which may also be embedded in the menu system). A set of mappings for a file management system might read "file/Save", "file/Open", and "file/Exit", regardless of how the source tree or JSP tree is organized.

Use Global Forwards for all entry points to application

All links in the application should be to ActionMappings. Rather than embed the path to an ActionMapping in a JSP, a link should refer to a Global Forward instead. The Global Forward can include parameters as needed, and should use a logical name that follows the command structure. Compound or path-like names can be used as preferred.

     <forward name="itemSearch"  path="/item/Search.do"/>
     <forward name="itemFindDonor" path="/item/Search.do?column=donor"/>
     <forward name="itemFindScript" path="/item/Search.do?column=script"/>

     <forward name="item/Search"  path="/item/Search.do"/>
     <forward name="item/Find/Donor" path="/item/Search.do?column=donor"/>
     <forward name="item/Find/Script" path="/item/Search.do?column=script"/>

     <forward name="item.Search"  path="/item/Search.do"/>
     <forward name="item.Find.Donor" path="/item/Search.do?column=donor"/>
     <forward name="item.Find.Script" path="/item/Search.do?column=script"/>

This also allows the parameters to be changed without modifying the JSPs or Actions that use the Global Forward.

Very early releases of Struts required subclassing the ActionMappings if you wanted to use names other than "success" and "failure". The mappings have long since been upgraded so that any arbitrary name can be used.

Organize ApplicationResources to follow ActionMapping namespaces

Rather than re-invent the wheel, the ApplicationResources and ActionMappings can often share the same naming conventions, or namespaces. The label for the name property in the accounts package may be named accounts.name in the Application Resources.

Distribute Struts-config.xml and ApplicationResources.properties

In a team environment split up the Application Resources and Struts Configuration files and concatenate them during the Ant build process. So long as each team has their own "namespace", then the properties will not conflict. This allows each team to make changes to their area without stepping on each other.

The DTD for Struts 1.0 does not support joining multiple config files, but a patch is available.


Define and subclass ActionForms to follow the ActionMapping structure

Each ActionMapping can optionally specify an ActionForm. When several related ActionMappings are used with a single Action, define a single ActionForm to use with the related mappings. The mapping is passed to the validate() and reset() methods, and can be used to determine which set of properties to validate or reset. Alternatively, define a subclass for each mapping, and subclass validate() and reset() instead.

Validate domain type and constraints on all user input

The optional ValidatorForm can be used to confirm the type and range of the properties input to a form. These are defined in a separate XML file, using regular expressions or custom techniques. Helper classes can then be written that assume that properties passed to them will convert cleanly to required types, and streamline processing. The ValidatorForm class includes custom tags that can also write Javascript for client-side validations.

Submit Once, Validate Twice

The general thinking is that there are at least two levels of validation. First, there is the simple domain-type checking, such as fields that are suppose to be numeric should contain only numerals. Second, there is business-logic checking, like invoice numbers are all greater than 1000 or no start dates should occur before 1984, or that the username and password match.

The first type is easy to automate and doesn't require access to the business logic, so we have a standard method that you can override if you want to do that as part of your ActionForm. With that method, therewould not be a good place for you to plug-in simple validations. 

The second type gets to be application specific, and is usually handled in the Action perform method. Since you are already overriding perform, there didn't seem to be much value in providing a yet another method to
override here.


Use a "populate" utility to exchange data with value objects

The BeanUtils.populate()and describe() methods can be used to transfer data between beans of arbitrary types. All that is required is that the names of the public properties correspond and that the properties be native types (or arrays of  native types). The wrapper objects, like Integer, are supported. Optional types, like Date, are not supported. 

describe reduces the source bean to a map of String values. populate reads such a map and uses it to set the properties on the target bean. If the target type is not a String, then the String is converted to the needed type. An exception is thrown if the conversion fails.

To transfer data between any two beans:

BeanUtils.populate(target,BeanUtils.describe(source));

In some cases, a custom describe() method might be needed on an ActionForm to provide a tailored Map of the values to transfer automatically. Other values (like Dates and Timestamps) can then be set manually.

BeanUtils.populate( target,source.describe() );
target.setDate(source.getDateBinary());

It can be useful to encapsulate transfer methods like this as static methods on a utility class. This avoids binding the ActionForms with a specific value object, and keeps similar code together to ease maintenance. 

valueUtils.populate(ValueObject target,ActionForm source);
valueUtils.populate(ActionForm target, ValueObject source);


Use reflection and metadata to transfer data from a result set

A problem with writing flexible helper action classes is populating objects of the correct type. One solution is to define a utility class that can convert an Integration layer object, like a result set, into a Map that can be used with BeanUtils.populate()to transfer data to an arbitrary bean. The bean must simply have one or more accessors that match column names in the Map (which can be generated from a ResultSet, for example). The columns that have a matching accessor will be used to populate the beans. Other accessors and columns are ignored.

Here is an example method using a ResultSet:

    public static void populate(Object bean,ResultSet resultSet)
        throws SQLException {
        // Build a list of relevant column properties from this resultSet
        HashMap properties = new HashMap();
        // Acquire resultSet MetaData
        ResultSetMetaData metaData = resultSet.getMetaData();
        int cols = metaData.getColumnCount();
        // Scroll to next record and pump into hashmap
        if (resultSet.next()) for (int i=1; i<=cols ; i++) {
            properties.put(metaData.getColumnName(i),
                resultSet.getString(i));
        }
        // Set the corresponding properties of our bean
        try {
            BeanUtils.populate(bean, properties);
        } catch (Exception e) {
            throw new SQLException("BeanUtils.populate threw " + e.toString());
        }
    }

A base bean object can then be passed to an Integration layer helper:

     public static final void selectBean(String key, String command, Object bean)

and the data can be transferred from within the data access method

                // Transfer ResultSet (using reflection and metadata)
        ResultSetUtils.populate(bean,resultSet);

In this case, the command and key string could retrieve many different sets of data, without creating specialized helpers for each set. A bean appropriate to the result is passed with the call, and reflection does the rest.

Here's another method that can be used to return a Collection of instantiated beans, each representing a row from the ResultSet

public static Collection getCollection(Object target, ResultSet resultSet)
throws SQLException {

// Acquire resultSet MetaData
ResultSetMetaData metaData = resultSet.getMetaData();
int cols = metaData.getColumnCount();

// Use ArrayList to maintain ResultSet sequence
ArrayList list = new ArrayList();

// Acquire target class
Class factory = target.getClass();

// Scroll to each record; copy to hashmap; populate target
while (resultSet.next()) {
HashMap map = new HashMap(cols,1);
for (int i=1; i<=cols; i++) {
map.put(metaData.getColumnName(i),
resultSet.getString(i));
}
// Create bean and populate from hashmap
try {
Object bean = factory.newInstance();
BeanUtils.populate(bean,map);
list.add(bean);
} catch (Exception e) {
throw new SQLException("RequestUtils.getCollection: " + e.toString());
}
}
return ((Collection) list);
}

Like the other method, this can be called from within a Helper action to disengage the Resource Layer from the rest of the application. All that is required is to pass along an instance of a bean which has some accessors that some column names in the ResultSet. 

This is a good way to reuse beans for both pick-lists and detail pages. The detail bean can be used for both, but passed to a different query that only populates properties used on the pick-list. Since the reflection runs from the result set to the bean, you can use fewer columns than may be available on the bean. Unused properties will simply be null.


Create convenience objects for commonly used attributes

Inside of a Web application, there often several attributes that need to be used throughout the application. These can include a user profile, bookmark, menu settings, and other properties. Rather than manage each of these separately, a convenience object can be defined to wrap these together. While the objects as a set may not be cohesive, it may be convenient to handle them as a single API.

Check logins at the Controller or Action level

When pages are placed under WEB-INF, you can make all security checks at the Action or controller level, and forgo putting any type of  "isLoggedIn" tag on your pages. The page should be able to assume that the Action authorized the user before forwarding control (since the user could not access the page except through an ActionForward).

Use multifunction Actions to service related ActionMappings

Here's an example:

In an Insert / Update workflow, there are usually three ActionMappings, one for "add", to return an empty ActionForm, one for "edit", to return a populated ActionForm, and one for "store" to either insert or update the ActionForm properties into the model. Usually, all three of these are mapped to the same Action. A good way to tell them apart is to use the parameter property with your mapping. 

<!-- Input Article (admin) -->
<action 
path="/admin/article/Add"
type="org.apache.struts.artimus.http.Client"
name="articleForm"
scope="request"
validate="false"
parameter="add">
<forward 
name="continue" 
path="/WEB-INF/pages/Entry.jsp"/>
</action>

<!-- Article Edit action mapping (admin) -->
<action 
path="/admin/article/Edit"
type="org.apache.struts.artimus.http.Client"
name="articleForm"
scope="request"
validate="false"
parameter="select">
<forward 
name="continue" 
path="/WEB-INF/pages/Entry.jsp"/>
</action>

<!-- Article Save action mapping (admin) -->
<action 
path="/admin/article/Save"
type="org.apache.struts.artimus.http.Client"
name="articleForm"
scope="request"
validate="true"
input="/WEB-INF/pages/Entry.jsp"
parameter="save">
<forward 
name="continue" 
path="/WEB-INF/pages/Article.jsp"/>
</action>

The Action can the call mapping.getParameter() and switch to the appropriate "sub-Action". When "add" is called, the Action checks the parameter, and returns a blank form. When the parameter is "edit", the Action uses other properties on the form to retrieve a record and populate the form. 

When the parameter is "save", the Action examines properties on the form to determine whether it needs to add or update the records. Usually, a record needs a primary key, which is null on an add and not null on update, and this is an easy way to tell which kind of save is needed.


Look at DispatchAction for a model of creating multi-function Actions

The optional DispatchAction class in the Struts Action package allows you to create several methods with the same signature of perform(). Given the right parameter, it can then automatically switch between these methods, which all act just like independent copies of perform().

In practice, related functions can also share much of the Struts error-handling and control code, so creating new perform-like methods can be more work than it should be. However, the principal is sound, and a good approach to multi-function Actions is to put each function in a private method that returns a control value, or an array of error tokens. As long as all needed properties are passed on the stack, and class properties are not used, then the methods will be thread safe.

// :TODO: CRUD example


Use a Base Action for your application

Often, the Actions in your application will need to perform some basic tasks. To ensure these tasks are implement consistently, create a base Action for the others in your application to subclass. 

If key tasks needs to be completed in each Action's perform() method, a good approach is to create a new abstract methods for your subclasses to use in lieu of perform(). The base Action class does its business, and if all is well, then returns the result of the new method. This allows you to change the signature of your new method, if there is ever any reason for that, and provides a cleaner flow.

    public ActionForward perform(ActionMapping mapping,
                 ActionForm form,
                 HttpServletRequest request,
                 HttpServletResponse response)
    throws IOException, ServletException {

        // Application specific behaviour
        // if everything is kosher call subclass

        return ( performAction(mapping,form,request,response, myParameter ) )
   }

   where performAction is an abstract method of the base class.


Provide session management for cached resources

Most Web applications require use of a workflow, and need to save ActionForms and other attributes for future use. These items may also need to be released in a block when the workflow completes. A convenient way to handle this is to define a "Resource Cache". This can simply be a hash map that uses a wrapper object to manage its items. The wrapper object can include a named "scope", a property to indicate whether the item is permanent or temporary, and a counter. To keep the cache manageable, a threshold can be set, and the oldest temporary items removed when the threshold is reached and a new item needs to be added.

Extend ActionServlet to support your API

The Struts ActionServlet can be extended to provide application-specific methods. These methods can streamline tasks like

Using cached forms, bookmarks, and key notifiers to implement workflows

Tasks that must be completed over more than one request (or step) can be difficult to manage in a Web application. A good approach is to setup a session manager that allows you to save attributes under a named scope, and then release them all at once. This makes it relatively easy to save information between steps, retrieve information from another step, and make it all go away when you are done. There is a new workflow proposal between Struts and the Jakarta Commons that uses a more sophisticated version of this approach. 

// :TODO: Workflow snapshot

Another helpful component for workflows is a "bookmark". This is a stored URI that will return the user to a particular page in the application. If a user exits a workflow, the exit page can be bookmarked for later return. A custom tag can be made part of the template which looks for the bookmark and displays a button to return to the bookmarked page. When a bookmark is not present, then the tag does not display the return form. This can also be used to return to a page after linking to a help area.

Many forms in a system have a key field that is used by other forms as a foreign key. When implementing wizards, setup a key notification system. Here, the Actions post the primary key for a form whenever it is accessed. (An abstract getKey() accessor on your base ActionForm is helpful here.) Other forms that use this key as a foreign key can then use the latest value to prepopulate their own related property (when null). The result is that if a user goes off to edit or add a record to complete one workflow, and the key for the related record is logged. When they return to the first workflow, the foreign key they found or added is now inserted into the form.


Use the Action Token methods to prevent duplicate submits

There are methods built into the Struts action to generate one-use tokens. A token is placed in the session when a form is populated and also into the HTML form as a hidden property. When the form is returned, the token is validated. If validation fails, then the form has already been submitted, and the user can be apprised.

Define a base ActionForm for your application with workflow properties

In practice, many applications need to use standard hidden properties to manage workflows, which are best put in a base ActionForm class. Some common properties needed are

Use the LabelValueBean as a core value object class

When a good number of drop-down controls are being used, standardize on the LabelValueBean object provided in the Struts example. This is an easy way to create both the Label and Value collections the Struts options tag expects.

Define a standard ResultBean for delivering value objects to the presentation layer

In practice, most pages require the display of more than one element. Usually there are standard elements that most request pages display, like a subtitle or the search parameters. These can be encapsulated in a helper bean along with a property for an iterator, when set of beans needs to be displayed (e.g. in a table).

// :TODO: Sample from ext.sql.AccessBean


Use request.isUserInRole() to implement role-based presentation logic

The request tag extension in Jakarta Taglibs exposes the RequestUtil bean, including the isUserInRole method. This is an excellent way for the presentation layer to determine what gestures can be made by what users, and provide the relevant controls. Since the controller layer is determining the content of the underlying bean, and the presentation layer is simply reading it, the integrity of the tiers is intact.

For customized security, place a role bean in session for access by custom tags and other resources

The standard security model is exposed through methods on the RequestUtils bean. If your security system is not fully integrated with the standard model, provide your own bean in the session with similar methods, or extend the ActionController to add this bean to each request. Custom tags, like those provided by the Jakarta Tagibs request library, can then access your bean instead of the standard RequestUtil bean.

Do not blindly check for null ActionForm beans in all your Actions

If an Action expects an ActionForm bean, then its API contact with the ActionMappings should require that a particular ActionForm bean, or subclass thereof, be named in the ActionMapping. The Action's contract with the controller is that it will always instantiate the bean before the Action is called. If either contract is broken, the application should expose a null pointer exception so that the programming error is fixed and the misunderstanding resolved. Whether an Action expects an ActionForm bean should be specified in its Javadoc.

Do check for essential preconditions in your Actions

The perform method in the Action is a key hotspot in the firamework, and may be realizing several different API contracts. To be sure all the contracts are being met, provide a general error catching routine for your Actions. This can look for any number of preconditions including whether there is a form bean when one is expected, and whether it is of the requesite class, and provide the appropriate error messages. 

See the SuperAction class in the Scaffolding package for a working example. Scaffolding can be found in the Contrib folder of the nightly build.


Develop a library of source code skeletons and sample classes

The classes for frameworks like Struts always have several dependencies and standard methods that must be subclassed. It's important to provide developers with skeletons for these classes, that are specifically designed for this use. Otherwise, people are continually picking a likely Action and spending valuable time creating a skeleton on-the-fly. Use of a formal skeleton library gives you a place to insert new features as they become available. Skeletons should also include starter Javadocs to encourage developers to maintain these.

ActionForm skeletons may also include sample property names, that developers can quickly change using search and replace. Since an ActionForm, Action, and JSP often travel together, a boilerplate file can be provided that allows a developer to setup all three with a single search/replace pass. This should be followed by an editor macro that finds the get and set methods and capitalizes the next letter (getthis -> getThis).

// :TODO: Example from Stub

A samples folder can also be a place where developers can save copies of classes that use important features, and make be made into skeletons at some point. Compared to the skeleton library, the samples library is a scrapbook, where the skeletons are blank forms.


Development Cycle

One effective approach to building a Struts application is this:

Other Suggestions

These are not Struts-specific, but can apply to any project using Struts.


Adopt coding standards

Using a coding standard is often more important than which one you use. The essential requirement is that the standard is clearly documented. One such standard is described in The Elements of Java Style.


Organize the code tree around tiers and the standard packages

The careful placement of a class within a package structure can make classes  easier to find and use, and also send developers a signal about how the classes are used in relation to each other. By clearly placing "business" tier code and "presentation" tier code on parallel branches, the tiers become a natural part of the developer's frame of reference.

Organize the code tree around teams

Within the branch of the source tree, it is often useful to organize around the command structure, which often follows the delegation of labor within a team. The command structure through a dropdown menu may read "accounts/Add", "accounts/Update", and "accounts/Search". This could lead to a source tree with an accounts package with Add, Update, and Search classes, which is managed by the Accounts team members.

Require documentation of all properties and methods, public and private

The fastest way to develop robust applications is for developers to thoroughly document their code using the standard Javadoc utilities. This helps developers focus on the quality of the code they write, and the importance of communicating their ideas to other workers. This is especially important when team members are not working at the same location, or do not share the same native language.

Provide a developer's portal page with links to common resources.

This is something every developer needs, even when they are working alone, and how More About Struts got started. Here are some likely items for a Struts developers portal:

Install a formal bug and feature tracking package, like Bugzilla

For any project involving three or more developers, it's worthwhile to install something like Bugzilla for internal use, to help keep everyone on the same page. This can also be used to manage work assignments, where a team lead delegates a task by assigning the feature request to another member of the team.

Implement an automatic nightly build, deploy, document, and test routine

To move into rapid deployment mode, it is important that team members get feedback as to how their work interacts as soon as possible. The best way to do this is to build and deploy the latest CVS nightly, along with updated Javadocs. The unit tests should then be run to ensure that new changes have not broken any existing contracts.

 

 

TODO 

BACK TO STRUTS