Tải bản đầy đủ (.pdf) (53 trang)

Apress Pro Apache Struts with Ajax phần 6 pps

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (469.73 KB, 53 trang )

<tiles-definitions>
<! Base Tile Definition from the previous section >
<definition name=".baseDef" path="/WEB-INF/jsp/tiles/template.jsp">
. . .
</definition>
<definition name=".homePage" extends=".baseDef">
. . .
</definition>
<definition name=".postComment" extends=".baseDef">
<put name="title" value="Post a Comment"/>
<put name="content"
value="/WEB-INF/jsp/tiles/postCommentContent.jsp"/>
</definition>
<definition name=".postStory" extends=".baseDef">
<put name="title" value="Post a Story"/>
<put name="content"
value="/WEB-INF/jsp/tiles/postStoryContent.jsp"/>
</definition>
<definition name=".searchForm" extends=".baseDef">
<put name="title" value="Search JavaEdge"/>
<put name="content"
value="/WEB-INF/jsp/tiles/searchFormContent.jsp"/>
</definition>
<definition name=".searchForm" extends=".baseDef">
<put name="title" value="Search JavaEdge"/>
<put name="content"
value="/WEB-INF/jsp/tiles/searchFormContent.jsp"/>
</definition>
<definition name=".signUp" extends=".baseDef">
<put name="title" value="Become a JavaEdge Member"/>
<put name="content" value="/WEB-INF/jsp/tiles/signUpContent.jsp"/>


</definition>
<definition name=".storyDetail" extends=".baseDef">
<put name="title" value="View a specific story"/>
<put name="content"
value="/WEB-INF/jsp/tiles/storyDetailContent.jsp"/>
</definition>
</tiles-definitions>
CHAPTER 6 ■ BUILDING FLEXIBLE FRONT-ENDS WITH THE TILES FRAMEWORK 243
Ch06_7389_CMP3 9/27/06 11:30 AM Page 243
Extending a Tiles Definition
It is possible to declare a new Tiles definition that inherits all of the attributes of an existing
Tiles definition and then adds new attributes that are unique to that tile. This is often done
when you want to write a page that has the same basic look and feel as all the rest of the pages
in an application, but has some additional visual elements that are unique.
Let’s say that the marketing department has decided that they want to try and underwrite
some of the costs of the JavaEdge site by selling ad space immediately below the header on the
main page. However, the marketing department is also considering adding ads to other pages
in the JavaEdge application at some later point in the future.
The easy approach would be to modify the homePageContent.jsp file to include the ad
information. However, this is not very reusable because every time the marketing department
wants to add more pages with ads, the advertisement code added to the homePageContent.jsp
would need to be replicated.
A more flexible approach would be to use the inheritance and extensibility features found
in Tiles definitions to build a hierarchy of definitions. The root Tiles definition for the JavaEdge
application is still the .baseDef definition. All of the pages in the JavaEdge application, except
for the home page, would use this definition.
A new definition is going to be created for the JavaEdge home page that will extend the
.baseDef definition. This new definition, which will be called .baseWithOneAd, will include a
new attribute parameter that defines what HTML or JSP file is going to be used for the ad that
is going to be placed in the JavaEdge application.

Figure 6-5 shows the relationship between the .baseDef definition, the .baseWithOneAd
definition, and the pages that implement these definitions.
Figure 6-5. Building a base definition
CHAPTER 6 ■ BUILDING FLEXIBLE FRONT-ENDS WITH THE TILES FRAMEWORK244
Ch06_7389_CMP3 9/27/06 11:30 AM Page 244
To implement the Tiles definition hierarchy shown previously, the following steps need to
be taken:
• The template.jsp that is being used by the JavaEdge application needs to be modified
to include additional attributes that are only going to be implemented by the
.baseWithOneAd Tiles definition.
•A new Tiles definition needs to be implemented in the tiles-defs.xml file that inherits
and extends the attributes defined in .baseDef.
• The .homePage Tiles definition needs to be modified to use the .baseWithOneAd defini-
tion instead of the .baseDef definition.
Modifying the template.jsp File
All of the attributes defined in the template.jsp file are being passed values from the individual
pages using the .baseDef Tiles definition. Every individual JavaEdge page that uses the
.baseDef definition must override the attribute values defined in the definition. If the page
does not override these values, the page will be rendered using the default values from the
definition.
Implementing the condition that some pages in the JavaEdge application support adver-
tisements requires a new attribute be added to the template.jsp page. This attribute, called
adone, holds the path to a JSP file that contains the advertisement. The template.jsp file with
the adone attribute declared is shown here:
<%@ taglib uri="/taglibs/struts-tiles" prefix="tiles" %>
<html>
<head>
<title><tiles:getAsString name="title"/></title>
</head>
<body>

<p>
<tiles:insert attribute="header"/>
<tiles:insert attribute="adone" ignore="true"/>
<tiles:insert attribute="content"/>
<tiles:insert attribute="footer"/>
</body>
</html>
Typically when a Tiles definition uses a template, each attribute declared in the template
must have a value passed into it. If a value is not passed into it, the Tiles framework will raise
an exception.
However, with the introduction of the advertisement requirement, there is now an attrib-
ute, adone, in the template.jsp file that does not need to have a value passed to it by every Tiles
definition using the template.
CHAPTER 6 ■ BUILDING FLEXIBLE FRONT-ENDS WITH THE TILES FRAMEWORK 245
Ch06_7389_CMP3 9/27/06 11:30 AM Page 245
To make the adone attribute nonmandatory, the ignore XML attribute is added to the
attribute’s <tiles:insert> tag:
<tiles:insert attribute="adone" ignore="true"/>
The ignore XML attribute, when set to true, tells the Tiles framework to not throw an
exception if the Tiles definition using the template does not pass a value for this attribute
using a <tiles:put> tag. If the ignore XML attribute is set to false or is omitted altogether,
every attribute defined in the template JSP must have a corresponding <tiles:put> tag
defined in the Tiles definition implementing the template.
Now that the template.jsp has been modified, let’s look at setting up the inheritance
hierarchy for the JavaEdge Tiles definitions in the tiles-defs.xml file.
Adding the New Definition to tiles-defs.xml
Having one Tiles definition extend the attributes of another definition is straightforward.
Shown here is the tiles-defs.xml file rewritten with an additional Tiles definition,
.baseWithOneAd, added to it:
<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration//EN"
" /><tiles-definitions>
<definition name=".baseDef" path="/WEB-INF/jsp/tiles/template.jsp">
<put name="title" value="Base Template Page"/>
<put name="header" value="/WEB-INF/jsp/tiles/header.jsp"/>
<put name="content" value="/WEB-INF/jsp/tiles/baseContent.jsp"/>
<put name="footer" value="/WEB-INF/jsp/tiles/footer.jsp"/>
</definition>
<definition name=".baseWithOneAd" extends=".baseDef">
<put name="adone" value="/WEB-INF/jsp/tiles/baseAd.jsp"/>
</definition>
. . .
</tiles-definitions>
The .baseWithOneAd Tiles definition looks very similar to the .baseDef definition. Both
Tiles definitions use a <definition> tag to declare the definition. Both definitions also declare
attributes the definitions are going to pass to the template via the <put> tag.
The key difference between the two different definitions is that the .baseWithOneAd
definition does not use the path attribute to declare the path of the JSP file it is going to pass
attribute values to. Instead, the .baseWithOneAd definition uses the extends attribute to indi-
cate that it is inheriting all of the attributes from the .baseDef definition:
<definition id=".baseWithOneAd" extends=".baseDef">
CHAPTER 6 ■ BUILDING FLEXIBLE FRONT-ENDS WITH THE TILES FRAMEWORK246
Ch06_7389_CMP3 9/27/06 11:30 AM Page 246
By using the extends attribute, the .baseWithOneAd definition automatically inherits all of
the attribute parameters declared in the .baseDef definition. The .baseWithOneAd definition
can choose to override the attribute values declared in the .baseDef definition by redeclaring
them via a <put> tag. Also remember, additional attributes can be added that are specific to
the child definition.
The .baseWithOneAd Tiles definition has declared that it is going to pass a value to the

adone attribute in the template.jsp file:
<tiles:put name="adone" value="/WEB-INF/jsp/tiles/baseAd.jsp"/>
Since the adone attribute is being declared in the .baseWithOneAd definition, only the
JavaEdge pages using this definition will have the ad content included on the page.
Modifying the .homePage Definition
At this point, the .homePage definition can now be modified to use the .baseWithOneAd instead
of the .baseDef Tiles definition:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration//EN"
" /><tiles-definitions>
<definition name=".baseWithOneAd" extends=".baseDef">
. . .
</definition>
<definition name=".homePage" extends=".baseWithOneAd">
<put name="title" value="Todays Top Stories"/>
<put name="content" value="/WEB-INF/jsp/tiles/homePageContent.jsp"/>
<put name="adone" value="/WEB-INF/jsp/tiles/ad.jsp"/>
</definition>
. . .
</tiles-definitions>
Let’s look at the JavaEdge home page with the newly added ad at the top of the page. Bring
up a web browser and go to http://localhost:8080/JavaEdge/execute/tiles/homePageSetup.
You should see the advertisement at the top of the screen immediately below the menu
bar, as shown in Figure 6-6. The home page will be the only application that has this advertise-
ment on it. However, it would be extremely easy to add the advertisement to other pages in
the JavaEdge application. All that is required is to modify the extends attribute on the page to
which you want to add the advertisement to use the .baseWithOneAd definition rather than the
.baseDef definition.
CHAPTER 6 ■ BUILDING FLEXIBLE FRONT-ENDS WITH THE TILES FRAMEWORK 247

Ch06_7389_CMP3 9/27/06 11:30 AM Page 247
Figure 6-6. The JavaEdge application with an ad on the screen
The .homePage definition overrides attributes from the two different definitions, .baseDef
and .baseWithOneAd. However, the .homePage definition has no knowledge that the title and
content attributes come from the .baseDef definition.
Instead, as far as the .homePage definition is concerned, these attribute declarations are
derived only from the .baseWithOneAd definition. The reason for this is that the Tiles inheri-
tance model is a single-tree model. This means a definition can only inherit attributes from a
single parent definition.
The Tiles framework does not support multitree inheritance, in which a definition can
inherit from multiple definitions that have absolutely no relationship to one another. If you
want a tile to inherit attributes from multiple definitions, the definitions must be organized
into a hierarchical relationship where each definition inherits its attributes in a chain. The
bottom of this chain will be the definition that is going to inherit all of the attributes.
■Note It should be noted that while Tiles inheritance allows a great deal of flexibility in building the look
and feel of a page, overusing and developing complex and/or deep inheritance hierarchies can cause per-
formance problems and turn maintaining pages for the site into an absolute nightmare. We suggest never
having a Tiles inheritance hierarchy more than two or three levels deep.
If you find yourself creating overly deep inheritance hierarchies, you should consider cre-
ating multiple base definitions based on the different distinct page types in your application.
Mapping Tiles Definitions to Action Forwards
Placing the Tiles definitions in a single file eliminates almost half of the JSP pages needed to
build the JavaEdge application. However, the tiles-defs.xml file does not indicate how your
Struts applications are supposed to actually navigate to the individual JSP pages. The question
becomes, How does Struts map these Tiles definitions to JSP pages that can be reached from
the Struts framework?
CHAPTER 6 ■ BUILDING FLEXIBLE FRONT-ENDS WITH THE TILES FRAMEWORK248
Ch06_7389_CMP3 9/27/06 11:30 AM Page 248
Struts uses the Tiles plug-in to map an XML-based Tiles definition to a Struts Action
Forward defined in the application’s struts-config.xml file.

To map a Tiles definition to a Struts Action Forward, the developer needs to use the value
in the <definition> tag’s name attribute in the <forward> tag for the Action. For example, to
refactor the JavaEdge’s /homePageSetup action to redirect the end user to the .homePage Tiles
definition, the <action> tag for the /homePageSetup Struts action needs to be rewritten as
shown here:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"
" /><struts-config>
. . .
<action-mappings>
<action path="/homePageSetup"
type="com.apress.javaedge.struts.homepage.HomePageSetupAction"
unknown="true">
<forward name="homepage.success" path=".homePage"/>
</action>
. . .
</action-mappings>
</struts-config>
The key tag that needs to be modified is the <forward> tag. The path attribute in the
<forward> tag must point to the name of the Tiles definition the user is going to be redirected
to. With the Struts plug-in configured, the Struts framework will check to see if there is a Tiles
definition defined that maps to the name specified in the path attribute.
If the Struts framework finds a match, it will assemble the contents of the page from its
different component pieces and forward the user to the assembled page.
ON NAMING TILES
The “.” naming convention that is being used by all of the JavaEdge Tiles definitions is not mandatory. You
can still use “/” and a more traditional path structure to name the application’s individual Tiles definitions.
However, even the refactored JavaEdge application uses Action Forwards that are not Tiles-based. Many
of the Action Forwards in the JavaEdge application point to “pre-Actions” that do some work and then redi-

rect the user to a Tiles-based JSP page. Using the “.” notation for Tiles-based actions and the “/” notation for
Struts-based Actions helps keep the struts-config.xml file manageable and easy to follow.
The idea of using the “.” notation for Tiles-based Forwards is not ours. Cedric Dumoulin, one of the
authors of Struts in Action (Ted Husted et al., Manning Publications Company, ISBN: 1-930-011050-2), first
put forth this idea. We adopted it for our own Struts projects and have found it works well.
CHAPTER 6 ■ BUILDING FLEXIBLE FRONT-ENDS WITH THE TILES FRAMEWORK 249
Ch06_7389_CMP3 9/27/06 11:30 AM Page 249
The homePageSetup Struts action does not submit any form data. However, when using
XML-based Tiles definitions and Struts actions that submit form data, make sure that the
input parameter on the action is mapped to the name of the Tiles definition for the page
rather than the JSP where the data was entered.
For example, in the /login action shown here, the input parameter is set to the .homePage
definition:
<action path="/login"
input=".homePage"
name="loginForm"
scope="request"
validate="true"
type="com.apress.javaedge.struts.login.Login">
<forward name="login.success" path="/execute/homePageSetup"/>
</action>
It is easy to overlook setting the input parameter to point to the Tiles definition. Missing
this can result in hours of fun and debugging. Shown here are the rest of the JavaEdge action
mappings converted to use XML-based Tiles definitions:
<action path="/storyDetailSetup"
type="com.apress.javaedge.struts.storydetail.StoryDetailSetupAction">
<forward name="storydetail.success" path=".storyDetail"/>
</action>
<action path="/signUpSetup"
type="com.apress.javaedge.struts.signup.SignUpSetupAction"

name="signUpForm"
scope="request"
validate="false">
<forward name="signup.success" path=".signUp"/>
</action>
<action path="/signUp"
input=".signUp"
name="signUpForm"
scope="request"
validate="true"
type="com.apress.javaedge.struts.signup.SignUp">
<forward name="signup.success" path="/execute/homePageSetup"/>
</action>
<action path="/postStorySetup"
type="com.apress.javaedge.struts.poststory.PostStorySetupAction"
name="postStoryForm"
scope="request"
validate="false">
CHAPTER 6 ■ BUILDING FLEXIBLE FRONT-ENDS WITH THE TILES FRAMEWORK250
Ch06_7389_CMP3 9/27/06 11:30 AM Page 250
<forward name="poststory.success" path=".postStory"/>
</action>
<action path="/postStory"
input=".postStory"
name="postStoryForm"
scope="request"
validate="true"
type="com.apress.javaedge.struts.poststory.PostStory">
<forward name="poststory.success" path="/execute/homePageSetup"/>
</action>

<action path="/postCommentSetup"
type="com.apress.javaedge.struts.postcomment.PostCommentSetupAction"
name="postCommentForm"
scope="request"
validate="false">
<forward name="postcomment.success" path=".postComment"/>
</action>
<action path="/postComment"
type="com.apress.javaedge.struts.postcomment.PostComment"
name="postCommentForm"
scope="request"
validate="false">
<forward name="postcomment.success" path="/execute/homePageSetup"/>
</action>
<action path="/SearchSetup"
type="com.apress.javaedge.struts.search.SearchFormSetupAction"
name="searchForm"
scope="request"
validate="false">
<forward name="search.success" path=".searchForm"/>
</action>
<action path="/Search"
type="com.apress.javaedge.struts.search.Search"
input=".searchForm"
name="searchForm"
scope="request"
validate="false">
<forward name="search.success" path=".searchForm"/>
</action>
CHAPTER 6 ■ BUILDING FLEXIBLE FRONT-ENDS WITH THE TILES FRAMEWORK 251

Ch06_7389_CMP3 9/27/06 11:30 AM Page 251
Summary
This chapter went through the basics of using the Tiles framework to build a flexible presenta-
tion tier that can easily be modified as the business needs of your organization evolve. Some
of the material that was covered in this chapter includes the following:
•Configuring Struts to use the Tiles framework:
•Configuring the struts-config.xml file (or in the examples for this chapter, the
struts-config-tiles.xml file) to activate the Tiles plug-in
•Configuring the Tiles plug-in to recognize where the tiles-defs.xml file is located
and what level of debugging the Tiles plug-in should carry out
• Laying out the skeleton tiles-defs.xml file.
•Writing a simple JSP page to leverage the <tiles> tag libraries.
•Exploring how Tiles definitions can be used to build the JavaEdge applications. This
chapter looked at the two different types of Tiles definitions (JSP-based and XML-
based) and how they could be used to do the following:
•Group together related page attributes into unique entities that could be reused
across multiple screens
•Demonstrate how to override individual attributes in a definition to allow cus-
tomization of the basic look and feel of an individual page or tile
•Use XML-based Tiles definitions to centralize all of the screen layouts into a single
file that can be easily managed and modified
• Leverage the inheritance and extensibility features of XML-based Tiles definitions
to add new elements to an existing application screen
•Modifying the struts-config-tiles.xml file so that XML-based Tiles definitions can be
used in a Struts Action Forward rather than the traditional JSP page.
Our advice is not to get caught up in the pure mechanics of the Tiles framework. It is more
important to understand how the Tiles framework can help a development team avoid the
Tight-Skins and Hardwired antipatterns. The Tiles framework allows the development team
to break the presentation layer into discrete and reusable components that can be managed
from a single location.

Let’s quickly review some of the changes made to the JavaEdge application during the
course of this chapter. It should become pretty apparent that we made some fundamental
changes to the application’s structure without having to rewrite a great deal of code.
• Reconfigured the application to use a completely different set of JSP files without having
to modify any of the existing files: By modifying the JavaEdge’s web.xml file to point to a
new Struts configuration file (struts-config-tiles.xml file), you could move the JavaEdge
application to a new presentation framework (Tiles) without having to touch any of the
existing application source code.
CHAPTER 6 ■ BUILDING FLEXIBLE FRONT-ENDS WITH THE TILES FRAMEWORK252
Ch06_7389_CMP3 9/27/06 11:30 AM Page 252
• Centralized all template and layout information into a single configuration file: All tem-
plate and layout information was centralized into the tiles-defs.xml file. By centralizing
all of the template information into one file, you could eliminate half of the original JSP
pages needed in the application.
• Added the capability to modify or add new screen elements without visiting each appli-
cation page: By leveraging the Tiles framework’s inheritance, overriding, and extension
features, you could add new screen elements (for example, the ad on the JavaEdge
home page) without having to actually go in and modify each screen.
Normally, to take an existing application that is not built around a framework and under-
take the changes enumerated by the preceding three bullet points would require a
tremendous amount of rework. However, by using Struts and Tiles, these tasks become trivial.
When a development team first begins using the Tiles framework, there can be a steep
learning curve. The development team will have to design the initial application templates
and write each of the Tiles definitions that are going to be used in the application. However,
once this work has been completed, it becomes extremely easy to modify the core structure of
the application, while minimizing the risk that changes to the code will break the application.
This chapter shows that you can easily move existing applications to Tiles, but it also
shows that if you do that, you aren’t using the full power of Tiles. Don’t get me wrong: It’ll still
work and it’s a good investment for the future, but it’s like using Windows 2000 on a FAT file
system. You get the power of a new system, but you’re still dragging old limitations with you.

Also, if you add Tiles to a sizable non-Tiles project, you can easily break existing pages.
Because Tiles tags can insert output of any action into the target tile, your presentation
layer is not limited to only JSP pages. You can just as easily use Velocity, for example (see
Chapter 11).
CHAPTER 6 ■ BUILDING FLEXIBLE FRONT-ENDS WITH THE TILES FRAMEWORK 253
Ch06_7389_CMP3 9/27/06 11:30 AM Page 253
Ch06_7389_CMP3 9/27/06 11:30 AM Page 254
Dynamic Forms and the
Struts Validator Framework
The current stable release of Struts, version 1.2.9, provides powerful development metaphors
for capturing, processing, and validating form data submitted by an end user. ActionForm
classes allow us to capture and validate data in a uniform manner. However, in medium-
to-large projects, it quickly becomes apparent that writing the ActionForm classes for the
application is a tedious, repetitive, and error-prone process.
The reason for this is twofold. First, the ActionForm class is used to scrape data submitted
by the user from the HttpServletRequest object and place it into a more developer-friendly
and type-safe Java object. However, the majority of the work involved with writing the
ActionForm class is writing get()/set() methods for each of the attributes being captured
from a submitted form. The hand coding of these get()/set() methods is usually reserved
as punishment for the individual whose code broke the build for the previous evening.
Secondly, in most applications, when data is collected from the end user, the same valida-
tion rules are usually applied against the data. For example, in the real world, just about every
HTML form submitted has some fields that are required to be populated. In addition, some
fields, like an e-mail address or a credit card number, are going to be required to be a certain
length and/or have a very specific format.
Using the ActionForm class and the validate() method requires the developer to invoke
these rules over and over against the individual form attributes in this class. The smart devel-
oper will often break the validation rules out into a set of classes that can be reused over and
over again. However, even by breaking the validation rules out into a set of reusable classes,
the developer writing the ActionForm class must still write Java code to invoke the rules.

Ultimately, the issue here is such developers are repeating the same type of code over and
over again throughout their application. Dave Thomas and Andrew Hunt, in their book The
Pragmatic Programmer (Addison-Wesley, ISBN: 0-201-61622-X), articulated this problem and
devised a simple yet powerful principle to battle it. This principle is called the D.R.Y. principle,
or “Don’t Repeat Yourself.”
The main point of the D.R.Y. principle is that “every piece of knowledge must have a single,
unambiguous, authoritative representation within a system.”
Dave Thomas and Andrew Hunt are big proponents of using code generators and devel-
opment frameworks to enforce the D.R.Y. principle. Starting with Struts version 1.1 and higher,
you can use two new features of the framework to solve the problems of repetitive code in
implementing ActionForm classes and validating the data contained within them.
255
CHAPTER 7
■ ■ ■
Ch07_7389_CMP3 9/27/06 1:10 PM Page 255
These two features are Dynamic Forms and the Jakarta Commons Validator framework.
This chapter is going to explore the functionality and capabilities of these exciting new addi-
tions to the Struts framework. Specifically, we will be discussing the following:
• An introduction to Dynamic Forms
•Declaring Dynamic Forms within the struts-config.xml file
•Using Dynamic Forms within your Struts Action classes
• The Jakarta Commons Validator framework
•Configuring Struts to use the Validator framework
• The different validation rules available in the framework
•Declaring validation rules in applications using Dynamic Forms
•Implementing validation rules in applications using traditional Struts ActionForm
classes
•Writing your own validation rules for use within the Validator framework
Let’s begin our discussion by looking at how you can use Struts Dynamic Forms to refac-
tor the PostStoryForm.java Action class.

Introducing Dynamic Forms
Dynamic Forms allow a development team to declaratively define the attributes of an applica-
tion’s ActionForm classes in the struts-config.xml file. By declaring the attributes for an
ActionForm class in a file, the development team can significantly reduce the amount of repeti-
tive and tedious code that needs to be written. Furthermore, new fields can be added to the
other form without having to rewrite the existing ActionForm classes.
To use Dynamic Forms within a Struts version 1.2x application, you need to perform two
steps:
1. Define a <form-bean> tag for each Dynamic Form that is going to be used inside the
application’s struts-config.xml file.
2. Rewrite your ActionForm classes to extend the DynaActionForm class instead of the
ActionForm class.
We will begin this discussion by looking at how you would define the Struts postStoryForm
as a Dynamic Form.
Defining the postStoryForm Struts Form Bean
A <form-bean> tag for a Dynamic Form, like a <form-bean> tag for the more traditional Struts
form, is declared in the struts-config-validator.xml.
1
However, unlike the more traditional
CHAPTER 7 ■ DYNAMIC FORMS AND THE STRUTS VALIDATOR FRAMEWORK256
1. As in the previous chapter, we have opted to break out the Validator configuration into a separate
struts-config.xml file. This file is called struts-config-validator.xml. Please remember to modify the
parameters for the
ActionServlet in your web.xml file.
Ch07_7389_CMP3 9/27/06 1:10 PM Page 256
Struts ActionForms that declare attributes via get()/set() methods inside the specific Java
class implementations, the individual form attributes for a Dynamic Form are declared as
<form-property> tags inside the <form-bean> tag.
Shown here is the <form-bean> entry used to define the Dynamic Form postStoryForm
used in the Post a Story page:

<struts-config>
<form-beans
<form-bean name="postStoryForm"
type="com.apress.javaedge.poststory.PostStoryDynaForm">
<form-property name="storyIntro" type="java.lang.String"/>
<form-property name="storyBody" type="java.lang.String"/>
<form-property name="storyTitle" type="java.lang.String"/>
</form-bean>
<! Rest of the ActionForm definitions >
</form-beans>
<! Rest of the struts-config.xml file >
</struts-config>
Once you have defined the postStoryForm <form-bean> tag, you need to define the indi-
vidual properties on the <form-bean>. This is the equivalent of writing a get()/set() method
on a nondynamic ActionForm class:
<form-property name="storyIntro" type="java.lang.String"/>
<form-property name="storyBody" type="java.lang.String"/>
<form-property name="storyTitle" type="java.lang.String"/>
Just like the nondynamic example shown earlier in Chapter 3, this dynamic post
StoryForm ActionForm definition has three properties: storyIntro, storyBody, and
storyTitle. Each of these properties has a corresponding <form-property> tag.
A <form-property> tag can have three attributes in it. We are only using two of the three
attributes for the example declaration shown earlier, as listed in Table 7-1.
Table 7-1. Attributes of the <form-property> Tag
Attribute Name Attribute Description
name The name attribute is the name of the property and is the value that will be
referenced by the Struts HTML tag libraries when accessing and setting form
data. This is a mandatory attribute.
type The type attribute is the fully qualified Java class name of the attribute being
set. This is a mandatory attribute.

Now that the postStoryForm has been declared as a Dynamic Form, let’s take a look at the
actual implementation of the PostStoryDynaForm class.
CHAPTER 7 ■ DYNAMIC FORMS AND THE STRUTS VALIDATOR FRAMEWORK 257
Ch07_7389_CMP3 9/27/06 1:10 PM Page 257
Writing the PostStoryDynaForm.java Implementation
At face value, the PostStoryDynaForm class looks very similar to the PostStoryForm class shown
earlier. However, subtle differences exist between the implementations. Shown here is the
code for the PostStoryDynaForm class:
package com.apress.javaedge.struts.poststory;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.DynaActionForm;
import org.apache.struts.action.ActionForward;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionServlet;
import org.apache.struts.util.MessageResources;
import com.apress.javaedge.common.VulgarityFilter;
public class PostStoryDynaForm extends DynaActionForm {
//Checks to make sure field being checked is not null.
private void checkForEmpty(String fieldName,
String fieldKey, String value,
ActionErrors errors){
if (value.trim().length()==0){
ActionError error =
new ActionError("error.poststory.field.null",
fieldName);
errors.add(fieldKey, error);
}

}
//Checks to make sure the field being checked
//does not violate our vulgarity list.
private void checkForVulgarities(String fieldName,
String fieldKey,
String value,
ActionErrors errors){
VulgarityFilter filter = VulgarityFilter.getInstance();
if (filter.isOffensive(value)){
ActionError error =
new ActionError("error.poststory.field.vulgar",
fieldName);
errors.add(fieldKey, error);
}
}
CHAPTER 7 ■ DYNAMIC FORMS AND THE STRUTS VALIDATOR FRAMEWORK258
Ch07_7389_CMP3 9/27/06 1:10 PM Page 258
//Checks to make sure the field in question
//does not exceed a maximum length.
private void checkForLength(String fieldName,
String fieldKey,
String value,
int maxLength,
ActionErrors errors){
if (value.trim().length()>maxLength){
ActionError error =
new ActionError("error.poststory.field.length",
fieldName);
errors.add(fieldKey, error);
}

}
public ActionErrors validate(ActionMapping mapping,
HttpServletRequest request) {
ActionErrors errors = new ActionErrors();
checkForEmpty("Story Title",
"error.storytitle.empty",
(String) super.get("storyTitle"),errors);
checkForEmpty("Story Intro",
"error.storyintro.empty",
(String) super.get("storyIntro"), errors);
checkForEmpty("Story Body",
"error.storybody.empty",
(String) super.get("storyBody"), errors);
checkForVulgarities("Story Title",
"error.storytitle.vulgarity",
(String) super.get("storyTitle"),errors);
checkForVulgarities("Story Intro",
"error.storyintro.vulgarity",
(String) super.get("storyIntro"),
errors);
checkForVulgarities("Story Body",
"error.storybody.vulgarity",
(String) super.get("storyBody"),
errors);
checkForLength("Story Title",
"error.storytitle.length",
(String) super.get("storyTitle"), 100,
errors);
checkForLength("Story Intro", "error.storyintro.length",
CHAPTER 7 ■ DYNAMIC FORMS AND THE STRUTS VALIDATOR FRAMEWORK 259

Ch07_7389_CMP3 9/27/06 1:10 PM Page 259
(String) super.get("storyIntro"), 2048,
errors);
checkForLength("Story Body",
"error.storybody.length",
(String) super.get("storyBody"),
10000,
errors);
return errors;
}
public void reset(ActionMapping mapping,
HttpServletRequest request) {
ActionServlet servlet = super.getServlet();
MessageResources messageResources = servlet.getResources();
super.set("storyTitle",
messageResources.getMessage("javaedge.poststory.title.instructions"));
super.set("storyIntro",
messageResources.getMessage("javaedge.poststory.intro.instructions"));
super.set("storyBody",
messageResources.getMessage("javaedge.poststory.body.instructions"));
}
}
The first thing to notice about the PostStoryDynaForm class is that it extends the Struts
import org.apache.struts.action.DynaActionForm class rather than the Struts org.apache.
struts.action.ActionForm class:
public class PostStoryDynaForm extends DynaActionForm {
. . .
}
The DynaActionForm class extends the ActionForm. The DynaActionForm class is used to tell
the Struts framework that the class is a Dynamic Form and that its attributes need to be read

from the struts-config.xml file.
Because the DynaActionForm class extends the ActionForm class, it inherits the validate()
and reset() method from the parent. These methods are invoked by the Struts framework in
the exact same manner as their nondynamic ActionForm counterparts.
The PostStoryDynaForm’s validate() and reset() implementation looks almost
exactly like the PostStoryForm’s implementation. The only difference is how these methods
retrieve and set attributes when executing. Take a look at a code fragment from the
PostStoryDynaForm’s validate() method:
public ActionErrors validate(ActionMapping mapping,
HttpServletRequest request) {
ActionErrors errors = new ActionErrors();
checkForEmpty("Story Title", "error.storytitle.empty",
(String) super.get("storyTitle"),errors);
CHAPTER 7 ■ DYNAMIC FORMS AND THE STRUTS VALIDATOR FRAMEWORK260
Ch07_7389_CMP3 9/27/06 1:10 PM Page 260
checkForEmpty("Story Intro", "error.storyintro.empty",
(String) super.get("storyIntro"), errors);
checkForEmpty("Story Body", "error.storybody.empty",
(String) super.get("storyBody"), errors);
. . . . .
return errors;
}
When using Dynamic Forms, individual attributes are accessed by calling the
DynaActionForm’s get() method and passing in the name of the attribute that is to be retrieved:
checkForEmpty("Story Title",
"error.storytitle.empty",
(String) super.get("storyTitle"),
errors);
The DynaActionForm’s get() method will retrieve all values as objects. It is the responsibility
of the developer to cast the returned object to its appropriate type. If the get() method cannot

find the property requested, it will throw a NullPointerException.
To set an attribute on a Dynamic Form, you need to call the set() method on the
DynamicActionForm, passing in the name of the attribute to be set, along with the value to be
associated with that attribute. The set() method will accept only objects and not primitive
values.
The reset() method demonstrates how to set an attribute for a form:
public void reset(ActionMapping mapping,
HttpServletRequest request) {
ActionServlet servlet = super.getServlet();
MessageResources messageResources = servlet.getResources();
super.set("storyTitle",
messageResources.getMessage("javaedge.poststory.title.instructions"));

}
The DynaActionForm’s set() method does do type checking on the value being passed into
it. It does this by looking at the type attribute defined in the form attribute’s <form-property>
tag. If the set() finds that the object passed in does not match the type declared in the
<form-property>, it will throw an org.apache.commons.beanutils.ConversionException.
A ConversionException is an unchecked Java exception and does not have to be explicitly
caught within a try{} catch{} block.
■Note Dynamic Forms allow the developer to quickly build forms without having to write extraneous Java
code. The only disadvantage with them is that the attributes are not type safe. A careless keystroke by a
developer can result in many hours of trying to debug this rather nefarious and difficult problem. However,
careful unit testing can help mitigate this risk. A great reference for unit testing is Java Development with
Ant by Erik Hatcher and Steve Loughran (Manning Publications, ISBN: 1-930-11058-8).While the book is
not entirely on unit testing, it does have some great chapters covering the topic.
CHAPTER 7 ■ DYNAMIC FORMS AND THE STRUTS VALIDATOR FRAMEWORK 261
Ch07_7389_CMP3 9/27/06 1:10 PM Page 261
Once a Dynamic Form’s <form-bean> tag and its corresponding <form-property> tags
have been declared, you’ve done all the work you need to do to tell Struts to use a dynamic

ActionForm class. The postStoryContent.jsp page that pulls data from the postStoryForm form
bean does not have to be modified. It does not care if you are using a nondynamic or dynamic
ActionForm.
Shown here is the rewritten PostStory Action class, pulling data from the dynamic
postStoryForm form bean defined earlier:
package com.apress.javaedge.struts.poststory;
import java.util.Vector;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
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.DynaActionForm;
import com.apress.javaedge.common.ApplicationException;
import com.apress.javaedge.member.MemberVO;
import com.apress.javaedge.story.StoryManagerBD;
import com.apress.javaedge.story.StoryVO;
import com.apress.javaedge.story.dao.StoryDAO;
public class PostStory extends Action {
public ActionForward perform(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response){
if (this.isCancelled(request)){
return (mapping.findForward("poststory.success"));
}
DynaActionForm postStoryForm = (DynaActionForm) form;
HttpSession session = request.getSession();

MemberVO memberVO = (MemberVO) session.getAttribute("memberVO");
try{
CHAPTER 7 ■ DYNAMIC FORMS AND THE STRUTS VALIDATOR FRAMEWORK262
Ch07_7389_CMP3 9/27/06 1:10 PM Page 262
StoryVO storyVO = new StoryVO();
storyVO.setStoryIntro((String)postStoryForm.get("storyIntro"));
storyVO.setStoryTitle((String)postStoryForm.get("storyTitle"));
storyVO.setStoryBody((String)postStoryForm.get("storyBody"));
storyVO.setStoryAuthor(memberVO);
storyVO.setSubmissionDate(new
java.sql.Date(System.currentTimeMillis()));
storyVO.setComments(new Vector());
StoryManagerBD storyManager = new StoryManagerBD();
storyManager.addStory(storyVO);
}
catch(Exception e){
System.err.println("An application exception" +
" has been raised in PostStory.perform(): " +
e.toString());
return (mapping.findForward("system.failure"));
}
return (mapping.findForward("poststory.success"));
}
}
There are really two differences between the PostStory class and the earlier PostStory
implementation shown in Chapter 3. First, the PostStory class just shown no longer casts the
ActionForm being passed into the perform() method on the class to the PostStoryForm class
shown earlier in the chapter. Instead, it casts the incoming ActionForm parameter to be of type
DynaActionForm:
DynaActionForm postStoryForm = (DynaActionForm) form;

Second, just like the validate() and reset() methods shown earlier, the PostStoryForm.
java implementation just shown does not call an individual getXXX() method for each prop-
erty on the ActionForm. Instead, it invokes the get() method on the class, passing in the name
of the property it wants to retrieve.
Some Thoughts About BeanUtils and the Preceding Code
You might be wondering why we did not use the BeanUtils.copyProperties() method to
populate the StoryVO class, as was shown in Chapter 4. In the example, we wanted to explicitly
demonstrate how to access Dynamic Form properties.
However, BeanUtils.copyProperties() does allow you to copy the properties from a Map
class into a JavaBean. The copyProperties() method will use the key of each value stored in
the Map object and try to match that to a get()/set() method on a JavaBean for copying. The
Map object that is to be copied must be passed in as the second parameter, the origin parame-
ter, on the BeanUtils.copyProperties() method.
CHAPTER 7 ■ DYNAMIC FORMS AND THE STRUTS VALIDATOR FRAMEWORK 263
Ch07_7389_CMP3 9/27/06 1:10 PM Page 263
So if you wanted to be consistent with what was shown in Chapter 4, you would rewrite
the buildStoryVO() method on the PostStoryForm class to look like this:
public StoryVO buildStoryVO(HttpServletRequest request)
throws ApplicationException{
HttpSession session = request.getSession();
MemberVO memberVO =
(MemberVO) session.getAttribute("memberVO");
StoryVO storyVO = new StoryVO();
try{
BeanUtils.copyProperties(storyVO, this.getMap());
}
catch(IllegalAccessException e) {
throw new ApplicationException("IllegalAccessException" +
" in PostStoryForm.buildStoryVO()",e);
}

catch(InvocationTargetException e){
throw new ApplicationException("InvocationTargetException " +
" in PostStoryForm.buildStoryVO()",e);
}
storyVO.setStoryAuthor(memberVO);
storyVO.setSubmissionDate(new java.sql.Date(System.currentTimeMillis()));
storyVO.setComments(new Vector());
return storyVO;
}
The only difference between the buildStoryVO() method shown here and the
buildStoryVO() method shown in Chapter 4 is
BeanUtils.copyProperties(storyVO, this.getMap());
The Struts DynamicActionForm lets you access the underlying Map that stores its properties
through the getMap() method. Otherwise the code is exactly the same.
Dynamic Form beans are a powerful feature of Struts. They allow you to easily implement
and change form beans without having to write a single line of code. This new feature keeps
very much in line with the philosophy of Struts: Let the framework do as much of the work as
possible, while allowing the developer to focus on building business code rather than infra-
structure code.
Dynamic Forms allow you to greatly reduce the amount of Java code that needs to be
written for a Struts form bean. However, while the Struts form beans are great for collecting
data, one of their biggest strengths lies in the fact that they allow the developer to easily sepa-
rate the validation data for the data being collected from the actual business rules being
executed on the data. However, as anyone with any web development experience can attest
to, writing validation logic for form data is a tedious undertaking. Oftentimes it involves exe-
cuting such basic tasks as checking to see if a web form field has been filled in by the end user,
whether it is the proper data type or the proper format, and so on.
CHAPTER 7 ■ DYNAMIC FORMS AND THE STRUTS VALIDATOR FRAMEWORK264
Ch07_7389_CMP3 9/27/06 1:10 PM Page 264
USING DYNAMIC FORMS TO DO RAPID PROTOTYPING

The introduction of Dynamic Forms in Struts version 1.1x has been incredibly useful for rapidly building
prototypes. Ideally, when building an application prototype, you want to be able to write the shell of the
application quickly. Then if you can get away with it, you want to use as much of the prototype code as
possible in the application.
Unfortunately, most development teams rarely get the time needed to really build a quality application
skeleton. The emphasis on speed to deliver the prototype often results in shoddy code that looks usable on
the surface, but from an architectural perspective is a “train wreck.”
Before Dynamic Forms, if the development team wanted to “shell” out the look and feel and navigation
for an application using Struts, the team still needed to write out the individual ActionForm classes needed
for each screen. This was a requirement because if you wanted to use the Struts tag libraries to mock up a
web form, the development team needed to have a fully implemented ActionForm class with each of the
get()/set() methods that were going to be displayed on the screen.
Development teams “under the gun” to build a prototype are not going to go through all of this extra
work. Instead, they usually end up using a combination of HTML and JSP scriptlets to throw the prototype
together. They end up meeting their goals, but then find themselves with little reusable code. Worse, their
management has now seen a “working” prototype and as is often the case jumps to the conclusion that
the application is almost done.
With Dynamic Forms, a development team can declare the form in the struts-config.xml file. However,
rather than extending and writing its own DynaActionForm class implementation and then declaring it
in the <form-bean> tag’s type attribute, the team can simply use org.apache.struts.action.
DynaActionForm for the type attribute’s value.
Thus, they do not have to write a Java class implementation. For example, if you just wanted to quickly
throw together a mock-up of the Post a Story screen using Struts, you could declare the following in the
application’s strut-config.xml file:
<struts-config>
<form-beans>
<form-bean name="postStoryForm"
type="org.apache.struts.validator.DynaActionForm">
. . .
</form-bean>

. . .
</struts-config>
Once this <form-bean> has been declared using DynaActionForm, it can be immediately used
throughout the prototype without having an actual class written. When the application is actually being built
out, a Java class can be written for the postStoryForm action and the type attribute can be modified to
point to the new class.
Fortunately, since Struts version 1.1x, a data validation framework called the Jakarta
Commons Validator framework has been integrated as a plug-in. The Validator framework
gives developers a rich library of common form validation tasks to immediately use in their
applications. The Validator framework is also extensible so that development teams can easily
add their own validation rules to the framework.
CHAPTER 7 ■ DYNAMIC FORMS AND THE STRUTS VALIDATOR FRAMEWORK 265
Ch07_7389_CMP3 9/27/06 1:10 PM Page 265
The Jakarta Commons Validator Framework
The Validator framework used in Struts did not originate in the Struts project. Rather, the
Validator framework is part of the Apache Jakarta Group’s Commons project (http://jakarta.
apache.org/commons). The Struts development team chose to integrate the Jakarta Commons
Validator as a Struts plug-in. The team also extended the Validator framework to make the
generic validation rules used in the Validator framework fit smoothly into the Struts validation
infrastructure.
The Struts developers did this because they found that after implementing several Struts-
based applications, they were performing the same types of validation over and over again.
Some of these common validations included
• Checking to see if the user has entered all required fields
• Checking to see if data is within a minimum or maximum size
• Checking to see if the data entered is of the right type
In the next several sections, we are going to show you how to use the Jakarta Commons
Validator framework and the PostStoryDynaForm implementation we examined earlier to
collect the Post a Story pages form data and apply the following validation rules:
• Check to make sure that the Story Title, Story Body, and Story Introduction fields on the

Post a Story page are filled in by the end user.
•Validate that each field entered by the user does not exceed a certain character length.
• Check to see if there is any vulgarity present in the user’s story.
The first two validation rules will be enforced using “out-of-the-box” validation rules
that come with the Validator framework. The last validation rule, checking for vulgarity, will
be implemented as a custom validation rule that is going to be added to the existing Struts
framework. To use the Validator framework in the JavaEdge application, you must take the
following steps:
1. Configure Struts to use the Validator plug-in.
2. Define a <formset> tag in the validations.xml file that lists the validation rules that are
going to be fired off when validating the PostStoryForm.
3. Write a new JavaEdge validation rule that checks for vulgarity.
4. Add a new entry to the validator-rules.xml file to tell the Validator framework about the
new vulgarity rule.
Let’s begin our discussion by looking at how the Validator framework is set up and
configured.
Validator Framework Setup
The Validator framework requires a modification to the struts-config.xml file and the addition
of two new configuration files: validator-rules.xml and validation.xml. Struts version 1.1 now
CHAPTER 7 ■ DYNAMIC FORMS AND THE STRUTS VALIDATOR FRAMEWORK266
Ch07_7389_CMP3 9/27/06 1:10 PM Page 266
allows new functionality to be added the framework via a plug-in. The Validator framework is
one such plug-in.
To make Struts aware of the Validator framework, you need to add the following entry to
the end of the JavaEdge struts-config.xml file:
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property property="pathnames" value="/WEB-INF/validator-rules.xml,
/WEB-INF/validation.xml"/>
</plug-in>
The <plug-in> tag goes at the end of your struts-config.xml file. It defines the fully quali-

fied Java class that represents the plug-in point between Struts and the third-party software.
The <set-property> tag is used to set a plug-in–specific property. In the preceding example,
the pathnames property contains a comma-separated list telling the Validator framework
where to find the validator-rules.xml file and the validation.xml file.
The validator-rules.xml file contains individual XML tags that describe the rules that
come with the Validator framework. The validator-rules.xml file that comes with the Struts
distribution will contain descriptions of all of the predefined validation rules that come as
part of the Validator framework. A partial listing of the validation rules defined in the valida-
tor-rules.xml file is shown in Table 7-2.
Table 7-2. Validator Rules
Rule Name Rule Description
required Validates that the field has been filled in by the end user
requiredif Deprecated; use validwhen
validwhen Checks one field with another
minlength Checks to ensure that the value entered is of a minimum length
maxlength Checks to ensure that the value entered is of a maximum length
range Deprecated; use intRange, doubleRange, or floatRange
mask Validates that the field entered is of a particular format
byte Validates that the field entered is of type byte
short Validates that the field entered is of type short
integer Validates that the field entered is of type integer
long Validates that the field entered is of type long
float Validates that the field entered is of type float
double Validates that the field entered is of type double
date Validates that the field entered is of type date
creditcard Validates a credit card format
email Validates that the field entered is a properly formatted e-mail address
intRange Validates whether an integer is within a particular range
floatRange Validates whether a float is within a particular range
doubleRange Validates whether a double is within a particular range

CHAPTER 7 ■ DYNAMIC FORMS AND THE STRUTS VALIDATOR FRAMEWORK 267
Ch07_7389_CMP3 9/27/06 1:10 PM Page 267

×