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.
You want to load initial data into the application context when your application starts up.
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>
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.)
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.
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.
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.
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.
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.
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.
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.
You want to avoid having to add taglib
elements to
the web.xml file every time you want to use a
new tag library.
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.
<%@ 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.
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.
Tag library |
Struts 1.1 URI |
Struts 1.2 URI |
| ||
| ||
| ||
| ||
|
No longer included with Struts; replaced by Tiles | |
| ||
| ||
| ||
|
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.
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.
Without resorting to scriptlets, you want to use application
constants—
public static
fields
defined in Java classes—on a JSP page.
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"/>
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.
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.
<%@ 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.
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; } }
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.
<%@ 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.
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.
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.
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.
<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.
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.
<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.
Recipe 2.5 details the nuances of using Struts modules. Recipe 1.8 describes how to generate the struts configuration files automatically.
You want to segregate portions of a web application into distinct subapplications, or modules, each with its own separate configuration.
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.
<!-- 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>
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.
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.
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.
You want to break apart your application resources properties file into multiple files for improved organization and easier maintenance, particularly in a team environment.
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>
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.
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.
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.
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.
Download the OJBMessageResources distribution from http://prdownloads.sourceforge.net/struts/ojb-message-resources.zip?download.
Extract the ZIP file into a directory on your computer.
Copy the ojb-msg-res.jar file from the ojb-message-resources/dist folder into your application’s WEB-INF/lib folder.
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.
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.
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;
Create the table to hold the message resources data using the SQL DDL shown in Example 2-13.
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 ) );
Populate the table with your message resources. Example 2-14 shows an easy means of loading the table using SQL.
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'),
Change the Struts configuration file to use the OJBMessageResources factory:
<message-resources factory="org.apache.struts.util.OJBMessageResourcesFactory" parameter="." />
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.
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.
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.
Column name |
Corresponding Struts concept |
Notes and examples |
|
Module prefix |
Not |
|
Key to locate a set of message resources when using multiple sets.
This value must match the value of the |
Not |
|
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 |
|
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 |
Not |
|
Value corresponding to the |
Can be |
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.
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.
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.
Create a custom
ActionMapping
extension (as shown in Example 2-16) that provides a
boolean property indicating if the action is disabled or not.
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
.
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; } }
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>
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");
You could use an authorization servlet filter to solve this problem. Recipe 11.8 shows an approach that could be applied to this problem.
18.116.118.229