Chapter 2. Configuring Struts Applications

Introduction

Struts provides a flexible framework. It can be used in many different ways and supports customizations and extensions at several levels. As a result of this flexibility, many developers have difficulty understanding how to configure Struts to suit their purposes. Struts can be configured using the web application’s web.xml file as well as one or more Struts configuration files (struts-config.xml). In addition, the Struts Validator is configured through an additional set of XML files.

This chapter goes over some of the more common and not-so-common scenarios that utilize Struts configurability. The coverage includes creating plug-ins and is a simple, yet powerful mechanism that can be used to solve a number of problems. We will cover the use of multiple configuration files, for facilitating team development as well as structuring and partitioning the web application. Finally, we will review Struts’ built-in support for extensibility and the use of custom configuration properties.

2.1. Using Plug-ins for Application Initialization

Problem

You want to load initial data into the application context when your application starts up.

Solution

Create a class that implements the org.apache.struts.action.PlugIn interface and specify the plug-in element in the struts-config.xml. The following XML fragment shows a plug-in declaration and a nested set-property element for setting a custom property:

<plug-in className="com.oreilly.strutsckbk.CustomPlugin" >


  <set-property property="customData"


                   value="Hello from the plugin"/>


</plug-in>

Discussion

Struts provides a PlugIn interface you can use to create custom services that are initialized on application startup. The Java source for the PlugIn interface is shown in Example 2-1. (For clarity, the JavaDoc documentation has been removed from this listing.)

Example 2-1. The Struts PlugIn interface
package org.apache.struts.action;

import javax.servlet.ServletException;
import org.apache.struts.config.ModuleConfig;

public interface PlugIn {

    public void destroy( );

    public void init(ActionServlet servlet, ModuleConfig config)
        throws ServletException;
}

To implement a plug-in, you only need to implement this interface and declare the plug-in implementation in the struts-config.xml file. The two methods that must be implemented, init() and destroy( ), are called during the lifecycle of the plug-in. Struts calls the init( ) method after it instantiates the plug-in on startup of the ActionServlet. Struts calls the destroy() method when the ActionServlet is destroyed, usually on shutdown of the application server. At first, this plug-in feature may seem simplistic and limited. However, by utilizing another feature of Struts, the set-property element, you can pass ad hoc information to the plug-in. This capability enhances the flexibility of these classes.

Tip

The set-property element is supported by most all of the elements that define Struts entities such as form-bean, action-mapping, action, and plug-in. The set-property element takes two attributes: name and value. Struts calls the setter method for the property identified by the value of the name attribute, setting the property to the String value of the value attribute.

All set-property elements for a given plug-in will be processed prior to calling the plug-in’s init( ) method. This allows the plug-in to use the value of the properties in the init( ) method.

Warning

If you are using multiple set-property elements for a plug-in, Struts can’t guarantee the order in which they will be called. Each setter method should be independent of any other methods.

Struts passes references to the ActionServlet and the plug-in’s ModuleConfig as arguments to the init( ) method. The ActionServlet allows access to the ServletContext for storing application-scoped objects. The ActionServlet gives you access to more advanced container-managed J2EE components, such as data sources and message queues. The ModuleConfig gives you access to the Struts configuration of the module in which the plug-in is defined.

Tip

Every Struts application has at least one module: the default module. If you are unfamiliar with modules, you can read more about them in Recipe 2.5.

To make this more concrete, consider a simple, yet relevant example. You want to define a plug-in that lets you to determine when your application was started, and how long it has been up and running. You can use the class shown in Example 2-2 to track and report on your application’s uptime.

Example 2-2. Application uptime tracker object
package com.oreilly.strutsckbk;

import java.util.Date;

public class TimeTracker {

  private long startUpTimeMillis;
  private Date startedOn;

  public TimeTracker( ) {
     startUpTimeMillis = System.currentTimeMillis( );
     startedOn = new Date( );
   }

  public long getUptime( ) {
    return System.currentTimeMillis( ) - startUpTimeMillis;
  }

  public Date getStartedOn( ) {
    return startedOn;
  }
}

Create an implementation of the PlugIn interface, like the one shown in Example 2-3, that instantiates the TimeTracker. The plug-in stores the TimeTracker instance in the ServletContext using the value of a plug-in property. You can use this value to retrieve the TimeTracker instance from the servlet context. Though this value could have been hard-coded, using a property provides greater flexibility.

Example 2-3. Time tracker plugin
package com.oreilly.strutsckbk;

import javax.servlet.ServletException;

import org.apache.struts.action.ActionServlet;
import org.apache.struts.action.PlugIn;
import org.apache.struts.config.ModuleConfig;

public class TimeTrackerPlugin implements PlugIn {

    private String contextKey;
   
   public void setContextKey(String key) {
       this.contextKey = key;
   }
   
   public void init(ActionServlet servlet, ModuleConfig conf)
         throws ServletException {
      servlet.getServletContext( ).setAttribute(contextKey, new TimeTracker( ));
   }

   public void destroy( ) {
    }
}

Now that you have the Java classes created for the plug-in, you can integrate them into your Struts application by adding the plug-in element to the struts-config.xml file:

<plug-in className="com.oreilly.strutsckbk.TimeTrackerPlugin">
    <set-property property="contextKey" value="timeTracker"/>
</plug-in>

The plug-in stores the time tracker object in the servlet context. You can access the TimeTracker to display information about your application’s uptime as in the following JSP snippet:

<h4>Continuously running since 
    <bean:write name="timeTracker" 
            property="startedOn" 
              format="MM/dd/yyyy HH:mm"/> for
    <bean:write name="timeTracker" 
            property="uptime"/> milliseconds!
</h4>

You can use a servlet to load initial data like a Struts plug-in. A servlet with the load-on-startup initialization parameter set to a low number, such as 1, will be loaded on application startup. The container will call the servlet’s init( ) method after instantiating the servlet. But the Struts plug-in approach has several advantages. First, most Struts applications don’t require changes to the web.xml file once it’s initially setup. Having to declare additional servlets in web.xml means an extra file to be maintained. Second, the PlugIn interface provides access to Struts-specific information if needed. Finally, since the lifecycle of the plug-in follows the lifecycle of the ActionServlet, you are guaranteed that the data will be available when needed by your Struts application.

See Also

Recipe 2.8 shows another usage of the set-property element. The Struts documentation on plug-ins can be found at http://jakarta.apache.org/struts/userGuide/building_controller.html#plugin_classes.

You can use a Servlet context listener to load initial data.

2.2. Eliminating Tag Library Declarations

Problem

You want to avoid having to add taglib elements to the web.xml file every time you want to use a new tag library.

Solution

Create a JSP file containing taglib directives that refer to the absolute URIs for the tag libraries you are using. Example 2-4 (taglibs.inc.jsp) shows a JSP file containing the taglib declarations for the Struts bean, html, and logic tag libraries as well as the JSTL core and formatting tag libraries.

Example 2-4. Common tag library declarations
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-bean" prefix="bean" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-html" prefix="html" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-logic" prefix="logic" %>
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jstl/fmt" prefix="fmt" %>

Then include this file in all of your JSP pages using the include directive:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<!-- start taglib -->
<%@ include file="/includes/taglibs.inc.jsp" %>
<!-- end taglib -->

<html:html>
  <body>
    ...

Since you are using the absolute URIs in the taglib directive, you aren’t required to enter a corresponding taglib element in the application’s web.xml file.

Discussion

If you are using a JSP 1.2/Servlet 2.3 compliant container, such as Tomcat 4.x or later, you can use an absolute URI in the taglib directive on the JSP page and you don’t have to specify taglib elements in the web.xml.

Prior to the Servlet 2.3 specification, you were required to declare any JSP tag libraries you used in your applications web.xml deployment descriptor. The following snippet from a web.xml deployment descriptor shows the typical taglib descriptors used for a Struts application:

<!-- Struts Tag Library Descriptors -->
<taglib>
  <taglib-uri>/tags/struts-bean</taglib-uri>
  <taglib-location>/WEB-INF/struts-bean.tld</taglib-location>
</taglib>
<taglib>
  <taglib-uri>/tags/struts-html</taglib-uri>
  <taglib-location>/WEB-INF/struts-html.tld</taglib-location>
</taglib>
<taglib>
  <taglib-uri>/tags/struts-logic</taglib-uri>
  <taglib-location>/WEB-INF/struts-logic.tld</taglib-location>
</taglib>
<taglib>
  <taglib-uri>/tags/struts-nested</taglib-uri>
  <taglib-location>/WEB-INF/struts-nested.tld</taglib-location>
</taglib>

With the introduction of the Servlet 2.3 specification, a tag library’s absolute URI is specified in that library’s tag library descriptor (TLD) file. For example, here’s this declaration from the struts-bean.tld file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag 
Library 1.1//EN" "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
<taglib>
  <tlibversion>1.0</tlibversion>
  <jspversion>1.1</jspversion>
  <shortname>bean</shortname>
  
<uri>http://jakarta.apache.org/struts/tags-bean</uri>

Any JSP page that needs to use this tag library can reference it with the following page-level directive. The tag library doesn’t need to be referenced in the web.xml file:

<%@ taglib
        uri="http://jakarta.apache.org/struts/tags-bean"
        prefix="bean" %>

If you use the same tag libraries throughout your application, you can use the approach shown in the Solution, which is creating an include file that contains all your needed taglib directives. No significant performance hit occurs if you refer to tag libraries that aren’t needed, so you can safely include this file on every JSP page. If a URI changes, you will only need to change the one include file instead of every JSP.

Table 2-1 shows the complete list of tag library absolute URIs for Versions 1.1 and 1.2 of Struts.

Some developers (like me) enjoy using the absolute URIs, others still prefer to make the declarations in the web.xml. They make the point that using the latter approach ensures that your application code is shielded from underlying changes to the tag libraries. If a URI changes, you only need to change the web.xml file which is a deployment descriptor. You don’t have to make changes to any JSP pages, even included JSP fragments. This argument is valid and has merit. In the end, it primarily comes down to personal preference.

See Also

The JavaServer Pages specification has some fairly complicated rules that it follows to resolve tag library URIs. Complete details can be found in the JSP specifications which can be downloaded from http://java.sun.com/jsp. Recipe 3.1 shows how to use the JSP Standard Tag Library (JSTL) tags within your Struts application.

2.3. Using Constants on JSPs

Problem

Without resorting to scriptlets, you want to use application constants— public static fields defined in Java classes—on a JSP page.

Solution

Use the bind tag provided by the Jakarta Taglibs unstandard tag library to create a JSTL variable containing the value of the constant field:

<%@ taglib uri="http://jakarta.apache.org/taglibs/unstandard-1.0" prefix="un" %>
<un:bind var="constantValue"
        type="com.foo.MyClass"
       field="SOME_CONSTANT"/>
<bean:write name="constantValue"/>

Discussion

A lot of teams put hard work into avoiding hard-coded String literals in their Java classes by using public static fields (constants). Unfortunately, neither Struts nor JSP provide a means to access these constants from a JSP page without resorting to JSP scriptlet like the following:

<%= com.foo.MyClass.SOME_CONSTANT %>

However, many development teams ban, or at least frown on scriptlet use on JSP pages.

Warning

Scriptlets (<% . . . %>) and runtime expressions (<%= . . . %>) place Java code directly onto a JSP page. They are not inherently evil, but they can lead your development down a slippery slope by turning your JSP pages into a tangled brittle mass of intermixed HTML, JSP, and Java code. Find solutions that don’t require you to use scriptlets. You’ll find—particularly with the introduction of JSTL—that you can always find a way around the dreaded scriptlet.

The Solution provides a way around this quandary through the use of a custom JSP tag, the un:bind tag. This tag is part of the unstandard tag library, part of the Jakarta Taglibs distribution. The unstandard tag library contains custom JSP tags that have been or are being considered for use in the standard tab library. The standard tag library is the Jakarta Taglibs implementation of the JSTL specification.

The unstandard tag library can be downloaded from http://cvs.apache.org/builds/jakarta-taglibs-sandbox/nightly/projects/unstandard/. To use the library, copy the unstandard.jar and unstandard.tld files into your web applications WEB-INF/lib directory.

The un:bind tag provides a means for creating a JSP page context variable, which references a field of any Java class. The fields can be instance variables or static variables. Most well-designed Java classes don’t expose instance variables as public fields, yet many expose static variables as public fields.

You can put the Solution to work by creating a simple JSP page that uses some of the static fields provided by Struts. Since Struts 1.1, the constants used by Struts are kept in the class org.apache.struts.Globals. The values of these constants specify the key values under which various Struts-related entities are stored in the request, session, or application context. The JSP page in Example 2-5 uses the un:bind tag to show one of these values.

Example 2-5. Using bind to access Struts globals
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://jakarta.apache.org/taglibs/unstandard-1.0" prefix="un" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-bean" prefix="bean" %>
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
<html>
<head>
  <title>Struts Cookbook - Chapter 4 : Using Bind</title>
</head>
<body>
  <un:bind var="servletKey"
          type="org.apache.struts.Globals"
         field="SERVLET_KEY"/>
  <p>
  Field name: SERVLET_KEY<br />
  Field value: <bean:write name="servletKey"/>< br />
  Attribute Value: <c:out value="${applicationScope[servletKey]}"/>< br />
  </p>
</body>
</html>

This page uses un:bind to retrieve the value of the SERVLET_KEY field from the Struts Globals class. The value of the SERVLET_KEY field is used as the servlet context attributes key under which the mapping (e.g. /action/* or *.do) defined for the Struts controller servlet is stored.

The un:bind tag is good to use when you need to access constants on an ad hoc basis. However, it is fairly verbose since you need to use the un:bind tag first to create the variable, and then the bean:write or c:out tag to render the value. Imagine the size of a JSP page showing all of the constants from the Globals class—there are 17 constants in all! Many applications rely heavily on constants and may have several classes, each containing dozens of these fields. Requiring the use of a separate tag for every single use of a constant can be cumbersome.

You can use an alternative solution, albeit one requiring some additional Java coding, by binding the constants into a Map property of a JavaBean. You store the bean in the servlet context. You can then access the values directly using the bean:write and c:out tags. The Constants class in Example 2-6 defines a JavaBean containing a map of the constants from the Struts Globals class. The ConstantsPlugin in Example 2-7 loads an instance of Constants into the servlet context.

Example 2-6. JavaBean holding constants from Struts Globals
package com.oreilly.strutsckbk.ch04;

import java.util.HashMap;
import java.util.Map;

import org.apache.struts.Globals;

public class Constants {
    private Map strutsGlobals;
    
    public Constants( ) {
        strutsGlobals = new HashMap( );
        strutsGlobals.put( "ACTION_SERVLET_KEY", 
                           Globals.ACTION_SERVLET_KEY );
        strutsGlobals.put( "SERVLET_KEY", Globals.SERVLET_KEY );
    }
    
    public Map getStrutsGlobals( ) {
        return strutsGlobals;
    }
    public void setStrutsGlobals(Map strutsGlobals) {
        this.strutsGlobals = strutsGlobals;
    }
}
Example 2-7. Plug-in that loads Constants into the servlet context
package com.oreilly.strutsckbk.ch04;

import javax.servlet.ServletException;

import org.apache.struts.action.ActionServlet;
import org.apache.struts.action.PlugIn;
import org.apache.struts.config.ModuleConfig;

public class ConstantsPlugin implements PlugIn {
    public void destroy( ) {
    }
    public void init(ActionServlet servlet, ModuleConfig module)
            throws ServletException {
        Constants constants = new Constants( );
        servlet.getServletContext( ).setAttribute("Constants", constants);
    }
}

Example 2-8 (globals_test.jsp) shows a JSP page that accesses and displays values loaded by the ConstantsPlugin. The first constant is rendered using the Struts bean:write tag and the second using the JSTL c:out tag.

Example 2-8. Accessing constants using Struts and JSTL
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-bean" prefix="bean" %>
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
<html>
<head>
  <title>Struts Cookbook - Chapter 4 : Accessing Constants</title>
</head>
<body>
  <p>
  Field name: ACTION_SERVLET_KEY<br />
  Field value: <bean:write name="Constants" 
                       property="strutsGlobals(ACTION_SERVLET_KEY)"/><br />
  </p>
  <p>
  Field name: SERVLET_KEY<br />
  Field value: <c:out value="${Constants.strutsGlobals.SERVLET_KEY}"/><br />
  </p>
</body>
</html>

The greatest drawback to this approach is that you manually have to create the map to hold the values. If a new constant is added to a class, you will have to change the source for the Constants class to retrieve it.

See Also

All details on the Jakarta Taglibs project can be found at http://jakarta.apache.org/taglibs. Recipe 5-4 provides details on accessing maps from Struts and JSTL. Struts plug-ins are discussed in Recipe 2.1.

Kris Schneider built a JavaBean similar to the Constants class of Example 2-6 that uses reflection to access the public static variables of any specified class. The source is available from the archived struts-user mailing list discussion at http://marc.theaimsgroup.com/?l=struts-user&m=108929374300664&w=2.

2.4. Using Multiple Struts Configuration Files

Problem

You want to break apart a large struts-config.xml file into smaller files for improved organization and easier maintenance, particularly in a team environment.

Solution

Split your monolithic struts-config.xml into multiple configuration files. Each file must be well-formed and valid according to the struts-config XML DTD. Reference these files as the value for the config initialization parameter of the ActionServlet in the web.xml file as shown in Example 2-9.

Example 2-9. Multiple config files (single module)
<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-2.xml
    </param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>

When the ActionServlet is loaded, Struts will merge the results of the specified configuration files into a single in-memory configuration.

Discussion

For anything other than the most trivial applications, the struts-config.xml file tends to get large and unwieldy. Many applications may have action elements numbering in the hundreds. Combine this with the use of a locking source control system, and soon you will have developers in constant contention for the same file.

Struts 1.1 introduced support for multiple configuration files. Each configuration file must be a valid XML file and conform to the struts-config XML Document Type Definition (DTD). You declare the set of files as the parameter value for the config ActionServlet initialization parameter in your web.xml file. You specify the files as a comma-separated list of file paths. At runtime, the files are merged in memory into a single configuration set. If duplicate elements exist—for example, a form bean declaration with the same value for the name attribute—the value read from the last configuration file listed takes precedence.

Just because you are using multiple configuration files doesn’t necessarily mean that you’re using Struts modules. In fact, you can have multiple configuration files for one individual module. In the Solution above, the param-name element value of config dictates to the ActionServlet the path to the Struts configuration files for the default module. Additional modules are indicated by using a param-name value of config/ module-name. Example 2-10 shows a Struts ActionServlet declaration from a web.xml file with a default module and two additional modules. The default module uses two configuration files: module1 uses only one configuration file, and module2 uses three configuration files.

Example 2-10. Multiple config files (multiple modules)
<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-default-config.xml,
      /WEB-INF/struts-default-config-2.xml
    </param-value>
  </init-param>
  <init-param>
    <param-name>config/module1</param-name>
    <param-value>
      /WEB-INF/struts-module1-config.xml
    </param-value>
  </init-param>
  <init-param>
    <param-name>config/module2</param-name>
    <param-value>
      /WEB-INF/struts-module2-config.xml,
      /WEB-INF/struts-module2-config-2.xml,
      /WEB-INF/struts-module2-config-3.xml
    </param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>

If you are doing team development, consider splitting your configuration files based on functional area, use cases, or user stories. Individual team members can focus on their areas of responsibility without bumping into the rest of the team.

See Also

Recipe 2.5 details the nuances of using Struts modules. Recipe 1.8 describes how to generate the struts configuration files automatically.

2.5. Factoring Your Application into Modules

Problem

You want to segregate portions of a web application into distinct subapplications, or modules, each with its own separate configuration.

Solution

Create a Struts configuration file for each module in addition to a Struts configuration file for the default module. Then declare each module using initialization parameters for the ActionServlet in the web.xml, as shown in Example 2-11.

Example 2-11. ActionServlet configuration for modules
<!-- Action Servlet Configuration -->
<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</param-value>
  </init-param>
  <init-param>
    <param-name>config/module1</param-name>
    <param-value>/WEB-INF/struts-config-module1.xml</param-value>
  </init-param>
  <init-param>
    <param-name>config/module2</param-name>
    <param-value>/WEB-INF/struts-config-module2.xml</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>

Discussion

Struts 1.1 introduced the ability to define separately configurable sub-applications known as modules. Modules were incorporated into Struts to address the need for subdividing a web application into distinct, manageable portions. Each module is defined with its own configuration file(s). Every Struts application implicitly has a default module. The default module has no module name.

Tip

To provide backward compatibility with Struts 1.0 applications, the actual name of the default module is the empty string.

Additional modules are defined by specifying a module prefix. The prefix is the value following config/ in the initialization parameter for the Struts’ ActionServlet. In Example 2-11, three modules are defined. The first init-param element defines the default module. The second and third init-param elements establish module1 and module2, respectively.

Struts pre-pends the module prefix to every URL accessed through declarations in the struts-config.xml file for each module. This rule applies to path attributes used in global forwards, global exceptions, action mappings, local forwards, and local exceptions. However, the module’s Struts configuration file doesn’t need to know and should not use the module prefix.

URLs that are generated using Struts tags, such as html:link and html:rewrite, will include the module name. This implicit inclusion of the module prefix can be a headache when you want to refer to globally shared web resources like images and cascading stylesheets. Web applications commonly place all images referenced throughout the site in a top-level /images folder. If you are using modules and you use the html:img tag to display these images, you must create a separate /images folder for each module or set the module attribute of the html:img tag to the empty string (“”) to indicate the default module.

Suppose you wanted to create an administrative user interface for the struts-example MailReader application. The changes required to add the administrative module to the web.xml file are shown below, and the second init-param element defines the admin module:

  <!-- Action Servlet Configuration -->
  <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-registration.xml
      </param-value>
    </init-param>
    <init-param>
                     <param-name>config/admin</param-name>
                     <param-value>/WEB-INF/struts-config-admin.xml</param-value>
                   </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

A common mistake when working with modules is to assume that if you navigate to a URL containing the module prefix, Struts will know you are in that module. For example, you might provide a standard HTML link (i.e., <a href . . . >) to the administrative module’s index page from the application’s main page. If users select that link, they may see the correct page rendered. However, as far as Struts is concerned, the user is still within the default module. To switch between modules in a Struts 1.1 application, you must forward the user through a special action, called the SwitchAction. In Struts 1.2, the Struts html tags that generate links and URLs support the module attribute, allowing you to explicitly indicate the target module.

Using modules with Struts 1.1 can have its drawbacks. Not all of the tags in Struts 1.1 support modules; so, you may find that the JSP pages within a module can’t be written completely irrespective of the module to which they belong. Many of these gaps in usability for modules have been filled in Struts 1.2. Therefore, I recommend you use modules only if you are using Struts 1.2. If you must use Struts 1.1 and your application is anything more than even slightly complex, you may want to avoid the inconsistencies with using modules and instead organize your application by into subfolders, using the technique shown in Recipe 2.4 to split apart your struts-config.xml file.

See Also

Recipe 2.4 describes a technique for splitting the struts-config.xml file into multiple files without using modules. Recipe 6.7 shows techniques to use when you need to switch between modules in a running Struts application.

2.6. Using Multiple Resource Bundles

Problem

You want to break apart your application resources properties file into multiple files for improved organization and easier maintenance, particularly in a team environment.

Solution

Create separate properties files and declare a message-resources element for each file in your struts-config.xml file:

<message-resources
  parameter="com.oreilly.strutsckbk.MessageResources"/>
<message-resources 
  parameter="com.oreilly.strutsckbk.LabelResources"
  key="labels">
</message-resources>
<message-resources 
  parameter="com.oreilly.strutsckbk.HeaderResources"
  key="headers">
</message-resources>

Discussion

Struts uses a concept known as message resources to provide a mechanism for storing error messages, field labels, and other static text. With the default Struts implementation, you store the messages as name/value pairs in a .properties file. A message resources set is basically the same as a Java ResourceBundle.

You make your message resources properties file available to Struts using the message-resources element. The parameter attribute identifies the classpath-relative name of the properties file. You derive the value for this attribute by replacing the path separator in the file’s path with a dot (“.”) and removing the .properties extension from the filename. For example, if the properties file was located in /WEB-INF/classes/com/oreilly/strutsckbk/MessageResources.properties, you would set up the message resources element as follows:

<message-resources
  parameter="com.oreilly.strutsckbk.MessageResources"/>

On application startup, Struts creates a runtime representation of the message resources and stores it in the servlet context.

You are not limited to one set of message resources. However, unlike using multiple Struts configuration files, if you use multiple message resource files, they are not combined or merged. Instead, you define distinct sets of message resources. Each set is identified with a unique value specified using the key attribute. If this attribute isn’t used, then that message resources set is created as the default set. Only one default set exists. Likewise, only one message resources set corresponds to each unique key within the same module. If you were to define multiple message resources bundles with the same key, the last one specified would be the one used.

The value of the key attribute serves as the name of the servlet context attribute under which the message resources bundle, created from the properties file, is stored. The key value is used in Struts tags, such as bean:message, to identify the message resources set, referred to as the bundle, from which to retrieve a property value. Here is how you would access a message from the labels message resources specified in the Solution:

<bean:message bundle="labels" key="label.url"/>

The value of the bundle attribute corresponds to the key attribute of the message-resources element in the struts-config.xml file. The bean:message tag has a key attribute that has a different meaning with of the message-resources element. It specifies the specify property to access from the message resources.

Warning

Unfortunately, this approach of using the same attribute for different purposes across XML elements and JSP tags is common in Struts. Make sure you keep the Struts taglib documentation (http://jakarta.apache.org/struts/userGuide/index.html) as well as the notes on configuring Struts (http://jakarta.apache.org/struts/userGuide/configuration.html#struts-config) bookmarked for handy reference to avoid confusion.

Struts doesn’t care how you split your message resource property files. One approach is to split it by message type. For example, you could have separate message resource bundles for the following:

  • Error messages

  • Informational messages

  • Field labels

  • Table header cell text

While breaking up the message resources by these types is logical and reasonable, in a team environment it makes more sense to divide the resources by functional area. For example, consider a human resources application that has functional areas for payroll, benefits, and administration. You would create a message resources properties file for each of these areas. Each property file would have within it the error messages, field labels, and other message types specific to that area. If your development team has been divided around these business functional areas, breaking up configuration files in the same manner makes a lot of sense and reduces contention for these resources. The same approach can be used for splitting the Struts configuration file, as well.

If you are familiar with Struts modules, each of the functional areas mentioned above could be a good candidate for a Struts module. If you are using modules, the message resources you define in the struts-config file for a module only apply to that module. In fact, you can define message-resource elements in different modules that have the same key attribute. Recall from the discussion that Struts stores the MessageResources in the servlet context using the key value. More precisely, the actual value used is a concatenation of the module name, often referred to as the module prefix, with the message resources key attribute value.

See Also

Recipe 2.4 provides additional information on segregating application components. The Struts User Guide provides documentation on defining message resources that can be found at http://jakarta.apache.org/struts/userGuide/configuration.html#resources_config.

The JavaDoc API for the Struts MessageResources can be found at http://jakarta.apache.org/struts/api/org/apache/struts/util/MessageResources.html.

The Struts documentation on the bean:message tag can be found at http://jakarta.apache.org/struts/userGuide/struts-bean.html#message.

2.7. Accessing Message Resources from a Database

Problem

You want to store all labels, messages, and other static text in a database rather than a properties file, while still being able to access the values using the bean:message tag.

Solution

  1. Download the OJBMessageResources distribution from http://prdownloads.sourceforge.net/struts/ojb-message-resources.zip?download.

  2. Extract the ZIP file into a directory on your computer.

  3. Copy the ojb-msg-res.jar file from the ojb-message-resources/dist folder into your application’s WEB-INF/lib folder.

  4. Copy the properties, XML, and DTD files from the ojb-message-resources/config folder to your application’s src folder. When you build your application, these files must be copied to the WEB-INF/classes folder.

  5. Create the tables to hold the Objec Relational Bridge (OJB) metadata tables. OJB uses these tables to keep internal mapping information. Example 2-12 shows the data definition SQL that creates these tables for the MySQL database. These statements for other databases are included with the OJB distribution.

Example 2-12. OJB metadata DDL (MySQL)
CREATE TABLE ojb_dlist (
  ID int NOT NULL default '0',
  SIZE_ int default NULL,
  PRIMARY KEY  (ID)
) TYPE=MyISAM;

CREATE TABLE ojb_dlist_entries (
  ID int NOT NULL default '0',
  DLIST_ID int NOT NULL default '0',
  POSITION_ int default NULL,
  OID_ longblob,
  PRIMARY KEY  (ID)
) TYPE=MyISAM;

CREATE TABLE ojb_dmap (
  ID int NOT NULL default '0',
  SIZE_ int default NULL,
  PRIMARY KEY  (ID)
) TYPE=MyISAM;

CREATE TABLE ojb_dmap_entries (
  ID int NOT NULL default '0',
  DMAP_ID int NOT NULL default '0',
  KEY_OID longblob,
  VALUE_OID longblob,
  PRIMARY KEY  (ID)
) TYPE=MyISAM;

CREATE TABLE ojb_dset (
  ID int NOT NULL default '0',
  SIZE_ int default NULL,
  PRIMARY KEY  (ID)
) TYPE=MyISAM;

CREATE TABLE ojb_dset_entries (
  ID int NOT NULL default '0',
  DLIST_ID int NOT NULL default '0',
  POSITION_ int default NULL,
  OID_ longblob,
  PRIMARY KEY  (ID)
) TYPE=MyISAM;

CREATE TABLE ojb_hl_seq (
  TABLENAME varchar(175) NOT NULL default '',
  FIELDNAME varchar(70) NOT NULL default '',
  MAX_KEY int default NULL,
  GRAB_SIZE int default NULL,
  PRIMARY KEY  (TABLENAME,FIELDNAME)
) TYPE=MyISAM;

CREATE TABLE ojb_lockentry (
  OID_ varchar(250) NOT NULL default '',
  TX_ID varchar(50) NOT NULL default '',
  TIMESTAMP_ decimal(10,0) default NULL,
  ISOLATIONLEVEL int default NULL,
  LOCKTYPE int default NULL,
  PRIMARY KEY  (OID_,TX_ID)
) TYPE=MyISAM;

CREATE TABLE ojb_nrm (
  NAME varchar(250) NOT NULL default '',
  OID_ longblob,
  PRIMARY KEY  (NAME)
) TYPE=MyISAM;

CREATE TABLE ojb_seq (
  TABLENAME varchar(175) NOT NULL default '',
  FIELDNAME varchar(70) NOT NULL default '',
  LAST_NUM int default NULL,
  PRIMARY KEY  (TABLENAME,FIELDNAME)
) TYPE=MyISAM;
  1. Create the table to hold the message resources data using the SQL DDL shown in Example 2-13.

Example 2-13. MessageResources DDL
create table application_resources ( 
  subApp            varchar(100)     not null,
  bundleKey         varchar(100)     not null,
  locale            varchar(10)      not null,
  msgKey            varchar(255)     not null,
  val               varchar(255),
  Primary Key(
    subApp,
    bundleKey,
    locale,
    msgKey
  )
);
  1. Populate the table with your message resources. Example 2-14 shows an easy means of loading the table using SQL.

Example 2-14. SQL to load message resources table
insert into application_resources ( 
    subApp, bundleKey, locale, msgKey, val ) 
  values ('', '', '', 'label.index.title', 
          'Struts Cookbook'),
insert into application_resources ( 
    subApp, bundleKey, locale, msgKey, val ) 
  values ('', '', 'fr', 'label.index.title', 
          'Struts Livre de Cuisine'),
  1. Change the Struts configuration file to use the OJBMessageResources factory:

    <message-resources 
       factory="org.apache.struts.util.OJBMessageResourcesFactory"
       parameter="."
    />
  2. Change the repository.xml in your WEB-INF/classes folder (copied over in step 4) to use the database connection properties specific to your database. Example 2-15 demonstrates a configuration for a MySQL database.

Example 2-15. OJB connection descriptor for MySQL
<jdbc-connection-descriptor
     platform="MySQL"
     jdbc-level="2.0"
     driver="com.mysql.jdbc.Driver"
     protocol="jdbc"
     subprotocol="mysql"
     dbalias="//localhost:3306/test"
     username="user"
     password="pass"
/>

Discussion

The Struts MessageResources facility manages static text such as error messages, field labels, table headers, and window titles. With this facility, the text is stored as name/value pairs in one or more .properties files known as resource bundles; the name is a logical key and the value is the text to display. If your application needs to be localized for a particular language and country, then you create a new properties file. You associate the file with the particular locale (language and country) by adding a suffix composed of a language and country code for the particular locale. For example, the MessageResources.properties file for French Canadian users would be MessageResources_fr_CA.properties. The properties in the localized file contain values specific to the locale. This approach of localizing resources is specified by Java itself.

Tip

More details, including a list of language and country codes, can be found at http://java.sun.com/j2se/1.4.2/docs/guide/intl/locale.doc.html.

This facility works well for most small to medium-sized applications. However, you may want to store the text using a more manageable persistent means such as a database. While Struts does not support this out-of-the-box, it does support it through extension. Behind the scenes, Struts uses an implementation of the MessageResourcesFactory to create the MessageResources object that is stored in the servlet context at runtime. You can provide a custom implementation of the Struts MessageResourcesFactory and then declare that implementation in your Struts configuration file:

<message-resources 
  factory="com.foo.CustomMessageResourcesFactory"
  parameter="
moduleName
.
bundleKey
"
/>

The parameter attribute specifies the Struts module name and bundle key (bundle name) that the message resources factory creates messages for.

So, you could create your own message resources factory that reads the resources from a database. Thankfully, the grunt work of creating such an extension has been done! James Mitchell, a long-time Struts committer, created the OJBMessageResources implementation. This set of classes leverages the object-relational mapping framework OJB to provide an easy-to-use database-driven MessageResources implementation.

If you are unfamiliar with OJB, don’t let that dissuade you from this Solution. You don’t need to know anything about OJB to use the OJBMessageResources. OJB is simply used under the covers to map the relational data to the object data. If you use the table schema specified in the Solution, no additional changes are needed to map the data. However, if you want to use a different schema, you can change the mappings in the OJB XML-based configuration files to suit your needs. You will not need to make any changes to the actual Java code that implements the MessageResourcesFactory. OJBMessageResources is well documented and comes with a step-by-step installation and configuration README file. The Solution above is derived from those instructions.

To use OJBMessageResources most effectively, it helps to understand how the database schema maps to the object data. First, the schema only requires creation of a single table to hold the message resources (step 6 in the Solution). Using a single table simplifies the data mapping. Table 2-2 describes the columns that make up this table and how they are used in Struts.

Table 2-2. OJBMessageResources schema

Column name

Corresponding Struts concept

Notes and examples

subApp

Module prefix

Not null. Use an empty String(“”) to represent the default module.

bundleKey

Key to locate a set of message resources when using multiple sets. This value must match the value of the key attribute of the message-resources element in the Struts configuration file. This value corresponds to the value for the bundle attribute in the Struts tags (e.g., bean:message bundle="labels"

Not null. Use an empty String to represent the default key. Otherwise, the name is a logical value such as “labels,” “headers,” and “errors.”

Locale

The locale code representing the locale of the message. This value is a combination of the two-letter language code and two-letter country code.

Not null. Use an empty String to represent the default (server’s) locale. Examples of values are “en_US” for English/United States and “fr” for French.

msgKey

The message name used to look up the message. This value will be the same for all locales. This value is the same as the left-hand side of a property in a .properties file. The value of this column corresponds to the value for the key attribute in the Struts tags that retrieve values from MessageResources.

Not null, and should never be empty. A key value might look something like “title.hello.world”.

val

Value corresponding to the msgKey. This is the localized text and corresponds to the right-hand side of a property. This is the value that will be retrieved and displayed by a Struts tag.

Can be null. This is the text that will be displayed on the page. For example, “Hello, World!”

Keep in mind that while OJBMessageResources uses a single database table to hold all message resources, each resource set must still be configured in the struts-config.xml with a message-resources element. In other words, you will need to have a message-resource element for each resource set distinguished by a bundleKey and subApp. See Recipe 2.6 for more details.

See Also

Recipe 2.6 describes how to configure Struts to use multiple message resource bundles. Chapter 14 has additional recipes related to internationalizing Struts applications.

OJB is a project under the Apache umbrella. Complete information on OJB can be found online at http://db.apache.org/ojb.

An effort is underway to factor out the Struts message resources into a reusable set of common classes. The Commons Resources project, http://jakarta.apache.org/commons/sandbox/resources/, will provide an implementation of message resources backed by properties files as well as other persistence mechanisms including OBJ, Hibernate, Torque, and straight JDBC. It’s anticipated that a future version of Struts will deprecate the internal message resources implementation and in favor of Commons Resources.

2.8. Selectively Disabling Actions

Problem

You want to disable an action using a custom property that can be set on the action element in your struts-config.xml file; forwarding any requests to the disabled action to an “under construction” page.

Solution

Create a custom ActionMapping extension (as shown in Example 2-16) that provides a boolean property indicating if the action is disabled or not.

Example 2-16. Custom ActionMapping
import org.apache.struts.action.ActionMapping;

public class DisablingActionMapping extends ActionMapping {

    private String disabled;
    private boolean actionDisabled = false;
    
    public String getDisabled( ) {
        return disabled;
    }

    public void setDisabled(String disabled) {
        this.disabled = disabled;
        actionDisabled = new Boolean(disabled).booleanValue( );
    }
    
    public boolean isActionDisabled( ) {
        return actionDisabled;
    }
}

This action mapping class can now be specified in the struts-config.xml file. You set the disabled property to true if an action is to be disabled:

<action-mappings type="com.oreilly.strutsckbk.DisablingActionMapping">

  <!-- Edit mail subscription -->
  <action    path="/editSubscription"
             type="org.apache.struts.webapp.example.EditSubscriptionAction"
        attribute="subscriptionForm"
            scope="request"
         validate="false">
    <set-property property="disabled" value="true"/>
    <forward name="failure"              path="/mainMenu.jsp"/>
    <forward name="success"              path="/subscription.jsp"/>
  </action>

Then create a custom RequestProcessor, such as the one shown in Example 2-17, that handles the DisablingActionMapping.

Example 2-17. Processing requests for disabled actions
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.RequestProcessor;

public class CustomRequestProcessor extends RequestProcessor {
    
    protected ActionForward processActionPerform(HttpServletRequest request, 
           HttpServletResponse response, Action action,ActionForm form, 
            ActionMapping mapping) throws IOException, ServletException {
        ActionForward forward = null;
        if (!(mapping instanceof DisablingActionMapping)) {
            forward = super.processActionPerform( request, response, 
                                                    action, form, mapping);
        }
        else {
            DisablingActionMapping customMapping = 
                  (DisablingActionMapping) mapping;
            if (customMapping.isActionDisabled( )) {
                forward = customMapping.findForward("underConstruction");
            }
            else {
                forward = super.processActionPerform( request, response,
                                                         action, form, mapping);
            }
        }
        return forward;
    }
}

Discussion

Struts supports the capability of providing custom properties to an Action through two primary mechanisms. First, every Struts action can be passed through a general purpose parameter value:

<action    path="/editRegistration"
           type="org.apache.struts.webapp.example.EditRegistrationAction"
      attribute="registrationForm"
          scope="request"
       validate="false"
       parameter="disabled">
  <forward name="success" path="/registration.jsp"/>
</action>

Second, in the Action implementation, the value of the parameter can be accessed with this code:

String parameterValue = mapping.getParameter(  );

However, some of the Struts-provided Action subclasses, such as the DispatchAction, are predicated on the use of the parameter attribute. Since you can specify only one parameter attribute, you will not be able to use the parameter for a custom value if you are using one of these pre-built Action subclasses.

For complete extensibility, you can extend the ActionMapping class, optionally providing accessor and mutator methods for custom properties of your own choosing:

package com.oreilly.strutsckbk;

import org.apache.struts.ActionMapping

public class MyCustomActionMapping extends ActionMapping {
    private String customValue;
    public String getCustomValue( ) { return customValue; }
    public String setCustomValue(String s) { customValue = s; }
}

You can reference this custom extension in the struts-config.xml file. If the custom action mapping should be used globally for all actions, set the type attribute of the action-mappings element to the fully qualified class name of the custom extension:

<action-mappings type="com.oreilly.strutsckbk.MyCustomActionMapping">

Otherwise, set the className attribute of the action element for which the custom action mapping is needed. In either case, the set-property element can be used to set values for JavaBean properties in the custom extension for a specific action element:

<action    path="/someAction"
           type="com.oreilly.strutsckbk.SomeAction"
      className="com.oreilly.strutsckbk.MyCustomActionMapping">
  <set-property property="customValue" value="some value"/>
</action>

Warning

To make your custom mapping as robust as possible, accept only a String value in the setter method for a set-property. You can perform any necessary data conversion in the class itself, setting the value to acceptable default if an unexpected value is passed.

The Solution uses a custom RequestProcessor for handling the disabled property of the custom ActionMapping. If you were using a custom ActionMapping for only specific actions, you could access the custom ActionMapping property directly in the Action.execute() method:

boolean disabled = ((DisablingActionMapping) mapping).isActionDisabled(  );
if (disabled) return mapping.findForward("underConstruction");

See Also

You could use an authorization servlet filter to solve this problem. Recipe 11.8 shows an approach that could be applied to this problem.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.118.229