Struts Survival Guide
Struts Survival Guide
Struts Survival Guide
Survival Guide
Basics to Best Practices
Covers Struts 1.1
Srikanth Shenoy
Austin
ObjectSource LLC books are available for bulk purchases for corporations and other organizations.
The publisher offers discounts when ordered in bulk. For more information please contact:
Sales Department
ObjectSource LLC.
2811 La Frontera Blvd., Suite 517
Austin, TX 78728
Email: sales@objectsource.com
First corrected reprint Copyright 2004,2005 ObjectSource LLC. All rights reserved.
All rights reserved. No part of this publication may be reproduced, stored in a retrieval system or
transmitted in any form or by any means electronic, mechanical, photocopying, recording
or otherwise, without the prior written permission of the publisher.
Many of the designations used by manufacturers and sellers to distinguish their products are claimed
as trademarks. Where those designations appear in this book, and ObjectSource LLC, was aware of a
trademark claim, the designations have been printed in initial capital letters.
The author and the publisher have taken care in preparation of this book, but make no express
or implied warranty of any kind and assume no responsibility for errors or omissions. In no event
shall the ObjectSource LLC or the authors be liable for any direct, indirect, incidental, special, exemplary
or consequential damages (including, but not limited to, procurement of substitute goods or
services; loss of use, data, or profits; or business interruption) however caused and on any theory
of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any
way out of use of the information or programs contained herein.
Published by
ObjectSource LLC
2811 La Frontera Blvd., Suite 517,
Austin TX 78728
Printing
RJ Communications
nd
51 East 42 Street, Suite 1202,
NewYork NY 10017
Cover Design
Matt Pramschufer
Budget Media Design
Pleasantville, New York
Library of Congress Catalog Number: 2004100026
Table of Contents
Chapter 1. Getting Started .......................................................................................... 15
J2EE Platform
15
J2EE web application
16
JSPs
Error! Bookmark not defined.
1.1 Model 1 Architecture
20
Problems with Model 1 Architecture
20
1.2 Model 2 Architecture - MVC
21
Advantages of Model 2 Architecture
22
Controller gone bad Fat Controller
23
1.3 MVC with configurable controller
23
1.4 First look at Struts
25
1.5 Tomcat and Struts installation
28
1.6 Summary
28
Chapter 2. Struts Framework Components ............................................................... 30
2.1 Struts request lifecycle
31
ActionServlet
31
RequestProcessor and ActionMapping
33
ActionForm
34
Action
35
ActionForward
36
ActionErrors and ActionError
37
2.2 Struts Configuration File struts-config.xml
39
2.3 View Components
42
How FormTag works
43
How ErrorsTag works
45
2.4 Summary
47
Chapter 3. Your first Struts application .................................................................... 48
3.1 Introduction
48
3.2 Hello World step by step
49
3.3 Lights, Camera, Action!
61
3.4 Handling multiple buttons in HTML Form
63
3.5 Value replacement in Message Resource Bundle
65
3.6 Summary
67
Chapter 4. All about Actions ....................................................................................... 68
4.1 ForwardAction
68
MVC compliant usage of LinkTag
69
Using LinkTags action attribute
70
Using LinkTags forward attribute
70
Using ForwardAction for Integration
71
ForwardAction Hands-on
72
4.2 Protecting JSPs from direct access
72
4.3 IncludeAction
75
4.4 DispatchAction
76
4.5 LookupDispatchAction
80
4.6 Configuring multiple application modules
82
4.7 Roll your own Base Action and Form
85
4.8 Handling Duplicate Form Submissions
88
4.9 What goes into Action (and what doesnt)
91
4.10 When to use Action chaining (and when not to)
93
6
4.11 Actions for complex transitions
94
Wiring the handlers
94
State aware Forms
95
4.12 Managing struts-config.xml
96
Struts-GUI
96
Struts Console
96
XDoclet
97
4.13 Guidelines for Struts Application Development
98
4.14 Summary
99
Chapter 5. Form Validation ...................................................................................... 101
5.1 Using Commons Validator with Struts
102
The twin XML files
102
validation-rules.xml The global rules file
103
validation.xml The application specific rules file
104
More validation.xml features
106
Using the ValidationForm
108
Configuring the Validator
108
Steps to use Commons Validator in Struts
109
5.2 DynaActionForm The Dynamic ActionForm
109
DynaValidatorForm
113
5.3 Validating multi-page forms
113
5.4 Validating form hierarchy
116
5.5 Summary
117
Chapter 6. Struts Tag Libraries ................................................................................ 119
6.1 Struts HTML Tags
120
Modifying the Base Tag
120
Form Tag
122
FileTag
122
Smart Checkbox The state aware checkbox
123
Using CSS with Struts HTML Tags
125
Enhancing the error display with customized TextTag
125
The recommended way to use ImgTag
129
6.2 Using Images for Form submissions
130
ImageButton and JavaScript
133
6.3 Struts Bean Tags
134
Message Tag and Multiple Resource Bundles
134
Write Tag
135
6.4 Struts Logic Tags
135
Nested Logic Tags
136
Iterate Tag
137
6.5 A crash course on JSTL
138
JSTL Binaries Whos who
141
6.6 Struts-EL
141
Struts-EL hands-on
142
Practical uses for Struts-EL
143
6.7 List based Forms
143
6.8 Multi-page Lists and Page Traversal frameworks
147
Pager Taglib
148
DisplayTag and HtmlTable frameworks
149
Creating the Model for iteration
150
7
6.9 Summary
153
Chapter 7. Struts and Tiles ........................................................................................ 154
7.1 What is Tiles
154
7.2 Your first Tiles application
157
Step 1: Creating the Layout
158
Step 2: Creating the XML Tile definition file
159
Step 3: Modifying the forwards in struts-config.xml
160
Step 4: Using TilesRequestProcessor
161
Step 5: Configuring the TilesPlugIn
161
7.3 Tiles and multiple modules
163
7.4 Summary
163
Chapter 8. Struts and I18N........................................................................................ 164
Terminology
164
What can be localized?
165
8.1 The Java I18N and L10N API
166
Accessing Locale in Servlet Container
167
8.2 Internationalizing Struts Applications
171
8.3 Internationalizing Tiles Applications
173
8.4 Processing Localized Input
174
8.5 Character encodings
175
Struts and character encoding
177
native2ascii conversion
178
8.6 Summary
179
Chapter 9. Struts and Exception Handling .............................................................. 181
9.1 Exception Handling Basics
182
9.2 Log4J crash course
183
9.3 Principles of Exception Handling
184
9.4 The cost of exception handling
187
9.5 JDK 1.4 and exception handling
188
9.6 Exception handling in Servlet and JSP specifications
189
9.7 Exception handling Struts way
191
Declarative exception handling
191
Using the ExceptionHandler
193
When not to use declarative exception handling
194
Exception handling and I18N
196
9.8 Logging Exceptions
196
9.9 Strategies for centralized logging
202
9.10 Reporting exceptions
206
9.11 Summary
208
Chapter 10. Effectively extending Struts.................................................................. 209
Customizing the action mapping
211
10.1 A rudimentary page flow controller
213
10.2 Controlling the validation
215
10.3 Controlling duplicate form submissions
218
10.4 DispatchAction for Image Button form submissions
222
10.5 Summary
224
I started using Struts in late 2000. I was immediately drawn to its power and ease
of use. In early 2001, I landed in a multi-year J2EE project, a large project by any
measures. Struts 1.0 was chosen as the framework for the web tier in that project.
Recently that project upgraded to Struts 1.1. I did the upgrade over a day.
It cannot get any easier!
This book makes no assumptions about your Struts knowledge. It starts with
the basics of Struts, teaches you what is important in Struts from a usage
perspective and covers a lot of practical issues all in a short 200-page book. No
unnecessary explanations. Concise, Clear and straight to the topic.
I am a consultant, not an author by profession. Hence my writing also tends
to reflect my mindset that got shaped by the various assignments I have
undertaken in the past. Large projects and their inbuilt complexities excite me. In
large projects, decoupling layers is a big thing. Also minor decisions during
architecting and designing (probably without the complete knowledge of the
framework used) can impact the project in a big way down the line.
Clearly understanding the strengths and shortcomings of a framework
and minor customizations to the framework go a long way in making the
applications cleaner. In that perspective, I have attempted to give a practical face
to this book based on my experience. Chapters 4, 5, 6, 9 and 10 will be extremely
valuable to all those wishing to use Struts effectively in J2EE projects.
Chapter 9 is based on my article originally published in IBM
developerWorks in May 2002 on Best practices in EJB Exception handling
(http://www-106.ibm.com/developerworks/java/library/j-ejbexcept.html).
This
chapter borrows some of the core concepts from that article and extends
and improvises them to specifically suit the needs of a Struts web application.
I have enjoyed a lot writing this book. Even though I knew Struts well, there
were some crevices that I had not explored and have made me that much better.
If you are a beginner, this book is your fastest track to master Struts. There are a
lot of best practices and strategies related to Struts that makes this book valuable
to even experienced developers and architects.
Srikanth Shenoy
January 2004
10
A good book is the end result of the efforts of many. For the first edition,
Sandeep Nayak helped by agreeing to be a beta reader and pointing out the
problems in the initial two chapters. The first edition also owes debt to
Fulco Houkes from Neuchtel, Switzerland who reviewed the book and tried
parts of the source code to ensure it is working.
Likewise I am indebted to booksjustbooks.com, for making their book
on Publishing basics available freely online without which this book wouldnt
have been a reality. Thanks to RJ Communications for printing this book.
Many thanks to Matt Pramschufer from Budget Media Design for the cover design.
I would like to thank Amazon.com for being a small publisher friendly
retailer. It is impossible to sell the books without having the reach as
Amazon does. This books comes from a independent small publisher
lacking
the distribution network that big publishers possess. Hadn't it for
Amazon, the first edition of this book would still be lying in my warehouse and
I would not have been able to offer this ebook free of cost.
I owe thanks to my wife for patiently putting up with me when I was
working evenings and weekends on the book and also editing the book. I
also owe thanks and gratitude to my parents who showed the right path as
I was growing up and instilled in me the passion for perfection, courage to
achieve anything and never give up.
Finally thanks to God through whom all things are made possible.
Srikanth Shenoy
March 2005
11
12
13
41
15
Chapter 1
Getting Started
In this chapter:
1. You will learn about Model 1 and Model 2 (MVC) and their differences.
2. You will understand the shortcomings of Model 1.
3. You will understand the problems with Model 2 The Fat Controller Antipattern.
4. You will learn how to overcome the problems in Model 2 by using Model
2 with a configurable controller.
5. You will see how Struts fill the gap by providing the configurable
controller and much more to boost developer productivity.
6. You will look at installing Tomcat and Struts on your computer.
What is Struts?
Struts is a Java MVC framework for building web applications on the J2EE
platform.
Thats it! As you can see, whole lot of buzzwords appear in the above sentence.
This chapter analyzes the above definition word-by-word and presents a big
picture of Struts. It also shows how Struts makes it easy to develop web
applications for the J2EE platform. But first, I will start with a quick overview of
the J2EE platform followed by basics of J2EE web application development
before looking at various strategies and frameworks that are available today for
developing the J2EE presentation tier.
J2EE Platform
As you might be already knowing, J2EE is a platform for executing server
side Java applications. Before J2EE was born, server side Java applications were
written using vendor specific APIs. Each vendor had unique APIs and
architectures. This resulted in a huge learning curve for Java developers and
architects to learn and program with each of these API sets and higher costs for
the companies. Development community could not reuse the lessons learnt in the
61
There are actually three types of applications that can be developed and deployed on
J2EE app servers. The third one is a application conforming to J2EE Connector
Architecture (J2CA). However I will leave this out for simplicity.
2
There are various kinds of Listeners that you can declare in web.xml. You can
also declare Tag Library Definitions (TLD) in web.xml. More details can be found
in the Servlet Specification. Again, I am leaving this out for simplicity.
17
one web.xml file. The web application is deployed into the servlet container by
bundling it in zipped archive called Web ARchive commonly referred to
as WAR file.
Listing 1.1 Sample doGet() method in a Servlet handling HttpRequest
public class MyServlet extends HttpServlet {
public void doGet(HttpServletRequest httpRequest,
HttpServletResponse httpResponse)
throws ServletException, IOException {
//Extract data from Http Request Parameters
//Business Logic goes here
//Now write output HTML
OutputStream os = httpResponse.getOutputStream();
os.println(<html><body>);
//Write formatted data to output stream
os.println(</body></html>);
os.flush();
os.close();
}
}
A servlet is the most basic J2EE web component. It is managed by the servlet
container. All servlets implement the Servlet interface directly or indirectly.
In general terms, a servlet is the endpoint for requests adhering to a
protocol. However, the Servlet specification mandates implementation for
servlets that handle HTTP requests only. But you should know that it is possible to
implement the servlet and the container to handle other protocols such as
FTP too. When writing Servlets for handling HTTP requests, you generally
subclass HttpServlet class. HTTP has six methods of request submission GET,
POST, PUT, HEAD and DELETE. Of these, GET and POST are the only forms of
request submission relevant to application developers. Hence your subclass of
HttpServlet should implement two methods doGet() and doPost() to handle
GET and POST respectively. Listing 1.1 shows a doGet() method from a typical
Servlet.
With this background, let us now dive straight into presentation
tier strategies. This coverage of presentation tier strategies will kick start
your thought process on how and where Struts fits in the big picture.
Technologies used for the presentation tier can be roughly classified into
three categories:
81
19
contains html and custom tags to render the page and as less logic as possible.
A rule of thumb is the dumber the JSP gets, the easier it is to maintain.
In reality however, some of the presentation logic percolates to the actual
JSP making it tough to draw a line between the two.
We just said JSPs are server side pages. Server side pages in other languages
are parsed every time they are accessed and hence expensive. In J2EE, the
expensive parsing is replaced by generating Java class from the JSP. The
first time a JSP is accessed, its contents are parsed and equivalent Java
class is generated and subsequent accesses are fast as a snap. Here is some
twist to the story. The Java classes that are generated by parsing JSPs are
nothing but Servlets! In other words, every JSP is parsed at runtime (or
precompiled) to generate Servlet classes.
Template based Transformation
In Template based transformation, a template engine uses a pre-defined template
to transform a given data model into desired output.
XSLT is a perfect example for template based transformation. XSLT stands
for XML Stylesheet Language Transformation. XSLT is used to transform
an XML document in one format into another XML document in another
format. Since HTML is a type of XML, XSLT can be used for generating
HTML from XML. In a J2EE application, J2EE components can
generate XML that represents
the
data.
Then
the
XML
is
transformed into HTML
(presentation/view) by using a stylesheet written using the XML Stylesheet
Language (XSL).
Velocity is another fantastic example of template based transformation
mechanism to generate the view. In fact Velocity is a general purpose Templating
framework that can be used to generate almost anything, not just a replacement
for
JSPs.
For
more
information
on
Velocity
check
out http://jakarta.apache.org/velocity. Velocity with Struts is not covered in
this edition of the book.
Rich Content in Rich Internet Applications (RIA)
Rich content delivery over the internet to the good old browser is not an entirely
new paradigm, but something thats drawing a lot of attention lately. The
traditional browsers presentation capabilities are fairly limited even with the
addition of DHTML and JavaScript. In addition, the browser
incompatibilities cause a lot of headache for developing rich application with
just DHTML and JavaScript.
Enter, Macromedia Flash, a freely available plugin for all the popular
browsers that can render the rich content uniformly across all browsers and
operating systems. This strategy can be of interest to Struts developers because
02
21
of Java code embedded in the JSP in the form of scriptlets. This is ugly
and maintenance nightmare even for experienced Java developers. In
large applications, JSPs are developed and maintained by page authors.
The intermingled scriptlets and markup results in unclear definition of roles
and is very problematic.
Application control is decentralized in Model 1 architecture since the
next page to be displayed is determined by the logic embedded in the current
page. Decentralized navigation control can cause headaches. All this leads us to
Model
2 architecture of designing JSP pages.
The Model 2 architecture for designing JSP pages is in reality, Model View
Controller (MVC) applied to web applications. Hence the two terms can be used
interchangeably in the web world. MVC originated in SmallTalk and has since
made its way into Java community. Model 2 architecure and its derivatives are
the cornerstones for all serious and industrial strength web applications designed
in the real world. Hence it is essential for you understand this paradigm
thoroughly. Figure 1.2 shows the Model 2 (MVC) architecture.
The main difference between Model 1 and Model 2 is that in Model 2,
a controller handles the user request instead of another JSP. The controller is
implemented as a Servlet. The following steps are executed when the user
submits the request.
1.
The Controller Servlet handles the users request. (This means the hyperlink
in the JSP should point to the controller servlet).
2.
3.
22
The Controller sets the resultant JavaBeans (either same or a new one) in one
of the following contexts request, session or application.
5.
The controller then dispatches the request to the next view based on
the request URL.
6.
The View uses the resultant JavaBeans from Step 4 to display data.
Note that there is no presentation logic in the JSP. The sole function of the
JSP in Model 2 architecture is to display the data from the JavaBeans set in the
request, session or application scopes.
23
logic for all the above tasks in all those places. A single controller servlet for the
web application lets you centralize all the tasks in a single place. Elegant code
and easier to maintain.
Web applications based on Model 2 architecture are easier to maintain and
extend since the views do not refer to each other and there is no
presentation logic in the views. It also allows you to clearly define
the roles and responsibilities in large projects thus allowing better
coordination among team members.
Controller gone bad Fat Controller
If MVC is all that great, why do we need Struts after all? The answer lies in
the difficulties associated in applying bare bone MVC to real world complexities.
In medium to large applications, centralized control and processing logic in the
servlet the greatest plus of MVC is also its weakness. Consider a
mediocre application with 15 JSPs. Assume that each page has five
hyperlinks (or five form submissions). The total number of user requests to
be handled in the application is 75. Since we are using MVC framework, a
centralized controller servlet handles every user request. For each type of
incoming request there is if block in the doGet method of the controller
Servlet to process the request and dispatch to the next view. For this
mediocre application of ours, the controller Servlet has 75 if blocks. Even if
you assume that each if block delegates the request handling to helper classes
it is still no good. You can only imagine how bad it gets for a complex
enterprise web application. So, we have a problem at hand. The Controller
Servlet that started out as the greatest thing next to sliced bread has gone bad. It
has put on a lot of weight to become a Fat Controller.
!
You must be wondering what went wrong with MVC. When application
gets large you cannot stick to bare bone MVC. You have to extend it somehow to
deal with these complexities. One mechanism of extending MVC that has
found widespread adoption is based on a configurable controller Servlet. The
MVC with configurable controller servlet is shown in Figure 1.3.
When the HTTP request arrives from the client, the Controller Servlet looks
up in a properties file to decide on the right Handler class for the HTTP request.
This Handler class is referred to as the Request Handler. The Request Handler
contains the presentation logic for that HTTP request including business
logic invocation. In other words, the Request Handler does everything that is
needed to handle the HTTP request. The only difference so far from the bare bone
MVC is that the controller servlet looks up in a properties file to instantiate the
Handler instead of calling it directly.
42
25
At this point you might be wondering how the controller servlet would know
to instantiate the appropriate Handler. The answer is simple. Two different HTTP
requests cannot have the same URL. Hence you can be certain that the
URL uniquely identifies each HTTP request on the server side and hence each
URL needs a unique Handler. In simpler terms, there is a one-to-one mapping
between the URL and the Handler class. This information is stored as key-value
pairs in the properties file. The Controller Servlet loads the properties file on
startup to find the appropriate Request Handler for each incoming URL request.
The controller servlet uses Java Reflection to instantiate the Request Handler.
However there must be some sort of commonality between the Request Handlers
for the servlet to generically instantiate the Request Handler. The commonality is
that all Request Handler classes implement a common interface. Let us call this
common interface as Handler Interface. In its simplest form, the Handler
Interface has one method say, execute(). The controller servlet reads the
properties file to instantiate the Request Handler as shown in Listing 1.1.
The Controller Servlet instantiates the Request Handler in the doGet()
method and invokes the execute() method on it using Java Reflection.
The execute() method invokes appropriate business logic from the middle tier
and then selects the next view to be presented to the user. The controller
servlet forwards the request to the selected JSP view. All this happens in the
doGet() method of the controller servlet. The doGet() method lifecycle never
changes. What changes is the Request Handlers execute() method. You may
not have realized it, but you just saw how Struts works in a nutshell! Struts is a
controller servlet based configurable MVC framework that executes predefined
methods in the handler objects. Instead of using a properties file like we did in
this example, Struts uses XML to store more useful information.
"
In the last section, you have seen the underlying principle behind Struts
framework. Now let us look closely at the Struts terminology for controller
servlet and Handler objects that we mentioned and understand Figure 1.4. Figure
1.4 is a rehash of Figure 1.3 by using Struts terminology. Since this is your first
look at Struts, we will not get into every detail of the HTTP request
handling lifecycle in Struts framework. Chapter 2 will get you there. For
now, let us concentrate on the basics.
Listing 1.2 Sample ActionForm
public class MyForm extends ActionForm {
private String firstName;
62
In Struts, there is only one controller servlet for the entire web application.
This controller servlet is called ActionServlet and resides in the package
org.apache.struts.action. It intercepts every client request and populates
an ActionForm from the HTTP request parameters. ActionForm is a normal
JavaBeans class. It has several attributes corresponding to the HTTP request
parameters and getter, setter methods for those attributes. You have to create
your own ActionForm for every HTTP request handled through the Struts
framework by extending the org.apache.struts.action.ActionForm
class. Consider the following HTTP request for App1 web application
http://localhost:8080/App1/create.do?firstName=John&lastName=Doe.
The ActionForm class for this HTTP request is shown in Listing 1.2. The
class MyForm extends the org.apache.struts.action.ActionForm class
and contains two attributes firstName and lastName. It also has getter and
setter methods for these attributes. For the lack of better terminology, let us coin a
term to describe the classes such as ActionForm View Data Transfer Object.
View Data Transfer Object is an object that holds the data from html page
and transfers it around in the web tier framework and application classes.
The ActionServlet then instantiates a Handler. The Handler class name is
obtained from an XML file based on the URL path information. This XML file is
referred to as Struts configuration file and by default named as struts-config.xml.
27
The Handler is called Action in the Struts terminology. And you guessed it right!
This
class
is
created
by
extending
the
Action
class
in
org.apache.struts.action package. The Action class is abstract and
defines a single method called execute(). You override this method in
your own Actions and invoke the business logic in this method. The
execute() method returns the name of next view (JSP) to be shown to
the user. The ActionServlet forwards to the selected view.
Now, that was Struts in a nutshell. Struts is of-course more than just this. It is
a full-fledged presentation framework. Throughout the development of the
application, both the page author and the developer need to coordinate and ensure
that any changes to one area are appropriately handled in the other. It aids
in rapid development of web applications by separating the concerns in
projects. For instance, it has custom tags for JSPs. The page author can
concentrate on developing the JSPs using custom tags that are specified by the
framework. The application developer works on creating the server side
representation of the data and its interaction with a back end data repository.
Further it offers a consistent way of handling user input and processing it. It
also has extension points for customizing the framework and much more. In
this section, you got a birds eye view of how Struts works. More details await
you in the chapters ahead. But you have to install Tomcat and Struts on your
machine to better understand the chapters ahead. Hence we will cover Tomcat
and Struts installation briefly in the next section.
82
#
We will use Windows environment to develop Struts application and Tomcat
servlet container to deploy and test Struts applications. Precisely we will use
Tomcat-5.0.14 Beta, the latest milestone release of Tomcat. You can download
Tomcat 5.0.14 from http://jakarta.apache.org/tomcat and follow the link to
download. There are several binaries available several variations of tar, exe and
zip files. Choose the 5.0.14.zip file and unzip it. A folder called jakarta-tomcat5.0.14 is created automatically. This is the TOMCAT_HOME directory. Under
the TOMCAT_HOME, there are a lot of folders of which two are important bin
and webapps folders. The bin folder contains two batch files - startup.bat, used
to start the Tomcat and shutdown.bat, used to stop the Tomcat. All the WAR files
are dropped in the webapps directory and get deployed automatically.
Installing
Struts
is
very
easy.
In
the
Struts
web
site, http://jakarta.apache.org/struts, go to download section and select the 1.1
Release Build. This is the latest production quality build available. Once you
download the zipped archive of Struts 1.1 release, unzip the file to a convenient
location. It automatically creates a folder named jakarta-struts-1.1. It has
three sub- folders. The lib sub-folder contains the struts.jar the core library that
you want to use and other jars on which the Struts depends. You would
normally copy most of these jars into the WEB-INF/lib of your web application.
The webapps sub-folder contains a lot of WAR files that can just dropped
into any J2EE application server and tested.
You can test your Tomcat installation and also study Struts at the same time.
Start Tomcat using startup.bat and then drop the struts-documentation.war from
your Struts webapps folder into Tomcats webapps folder. The WAR is
immediately deployed. You can access the Struts documentation at the URL
http://localhost:8080/struts-documentation. You should also download the Struts
1.1 source and refer to it and probably study it to get more insights about
its internals. However be sure to read through this book before you dive
into the Struts source code.
29
03
Chapter 2
Struts Framework Components
In this chapter:
1. You will learn more about Struts components and their categories
Controller and View
2. You will understand the sequence of events in Struts request handling
lifecycle
3.
4. You will also learn about the role of Struts Tags as View components in
rendering the response
5. You will understand the various elements of Struts configuration file
struts-config.xml
In the last chapter, you had a cursory glance at the Struts framework. In
this chapter you will dive deeper and cover various Struts Framework
Components. Here is something to remember all the time.
1. All the core components of Struts framework belong to Controller
category.
2. Struts has no components in the Model category.
3.
31
&
In this section you will learn about the Struts controller classes
ActionServlet,
RequestProcessor,
ActionForm,
Action, ActionMapping
and
ActionForward
all
residing
in org.apache.struts.action package and struts-config.xml
the Struts Configuration file. Instead of the traditional Here is the class
go use it approach, you will study the function of each component in the
context of HTTP request handling lifecycle in Struts.
ActionServlet
The central component of the Struts Controller is the ActionServlet. It is
a concrete class and extends the javax.servlet.HttpServlet. It
performs two important things.
1. On startup, its reads the Struts Configuration file and loads it into memory in
the init() method.
2.
23
In the above snippet, the Struts Config file is present in the WEB-INF/config
directory and is named myconfig.xml. The ActionServlet takes the
Struts Config file name as an init-param. At startup, in the init()
method, the ActionServlet reads the Struts Config file and creates
appropriate Struts configuration objects (data structures) into memory. You will
learn more about the Struts configuration objects in Chapter 7. For now,
assume that the Struts Config file is loaded into a set of objects in memory,
much like a properties file loaded into a java.util.Properties class.
Like any other servlet, ActionServlet invokes the init() method when
it receives the first HTTP request from the caller. Loading Struts Config file into
configuration objects is a time consuming task. If the Struts configuration objects
were to be created on the first call from the caller, it will adversely affect
performance by delaying the response for the first user. The alternative is to
specify load-on-startup in web.xml as shown above. By specifying load-onstartup to be 1, you are telling the servlet container to call the init() method
immediately on startup of the servlet container.
The second task that the ActionServlet performs is to intercept HTTP
requests based on the URL pattern and handles them appropriately. The
URL pattern can be either path or suffix. This is specified using the servletmapping in web.xml. An example of suffix mapping is as follows.
<servletmapping>
<servletname>action</servletname>
<urlpattern>*.do</urlpattern>
</servletmapping>
33
43
path="/submitDetailForm"
type="mybank.example.CustomerAction"
name="CustomerForm"
scope="request"
validate="true"
input="CustomerDetailForm.jsp">
<forward name="success"
path="ThankYou.jsp"
redirect=true/>
<forward name="failure"
path="Failure.jsp"
/>
</action>
Step 2: The RequestProcessor looks up the configuration file for the URL
pattern /submitDetailForm. (i.e. URL path without the suffix do) and finds
the XML block (ActionMapping) shown above. The type attribute tells Struts
which Action class has to be instantiated. The XML block also contains several
other attributes. Together these constitute the JavaBeans properties of
the ActionMapping instance for the path /submitDetailForm. The
above ActionMapping tells Struts to map the URL request with
the path
/submitDetailForm to the class mybank.example.CustomerAction. The
Action class is explained in the steps ahead. For now think of the Action as your
own class containing the business logic and invoked by Struts. This also tells us
one more important thing.
Since each HTTP request is distinguished from the other only by the
path, there should be one and only one ActionMapping for every path
attribute. Otherwise Struts overwrites the former ActionMapping with the latter.
ActionForm
Another attribute in the ActionMapping that you should know right away is
name. It is the logical name of the ActionForm to be populated by
the RequestProcessor.
After
selecting
the
ActionMapping,
the RequestProcessor instantiates the ActionForm. However it has to know
the fully qualified class name of the ActionForm to do so. This is where the
name attribute of ActionMapping comes in handy. The name attribute is the
logical
35
name="CustomerForm"
type="mybank.example.CustomerForm"/>
either
session
or
request.
The
RequestProcessor determines the appropriate scope by looking at the
scope attribute in the same ActionMapping.
Step 4: Next, RequestProcessor iterates through the HTTP request parameters
and populates the CustomerForm properties of the same name as the
HTTP request parameters using Java Introspection. (Java Introspection is a special
form of Reflection using the JavaBeans properties. Instead of directly using
the reflection to set the field values, it uses the setter method to set the field
value and getter method to retrieve the field value.)
Step 5: Next, the RequestProcessor checks for the validate attribute in the
ActionMapping. If the validate is set to true, the RequestProcessor invokes
the validate() method on the CustomerForm instance. This is the method
where you can put all the html form data validations. For now, let us pretend that
there were no errors in the validate() method and continue. We will come
back later and revisit the scenario when there are errors in the
validate() method.
Action
63
class, you are letting the javax.servlet.* classes proliferate into your
business logic. This limits the reuse of the business logic, say for a pure
Java client. The second reason is that if you ever decide to replace the
Struts framework with some other presentation framework (although we know
this will not happen), you dont have to go through the pain of modifying
the business logic. The execute() method should preferably contain only the
presentation logic and be the starting point in the web tier to invoke the business
logic. The business logic can be present either in protocol independent Java
classes or the Session EJBs.
The RequestProcessor creates an instance of the Action
(CustomerAction), if one does not exist already. There is only one instance
of Action class in the application. Because of this you must ensure that the
Action class and its attributes if any are thread-safe. General rules that apply to
Servlets hold good. The Action class should not have any writable attributes
that can be changed by the users in the execute() method.
ActionForward
The execute() method returns the next view shown to the user. If you are
wondering what ActionForward is, you just have found the answer.
ActionForward is the class that encapsulates the next view information.
Struts, being the good framework it is, encourages you not to hardcode the JSP
names for the next view. Rather you should associate a logical name for the next
JSP page. This association of the logical name and the physical JSP page
is encapsulated in the ActionForward instance returned from the execute
method. The ActionForward can be local or global. Look again at the
good old ActionMapping XML block in Listing 2.1. It contained sub
elements called forwards with three attributes name, path and redirect as shown
below.
The name attribute is the logical name of the physical JSP as specified in the
path attribute. These forward elements are local to the ActionMapping in Listing
2.1. Hence they can be accessed only from this ActionMapping argument in
the CustomerActions execute() method and nowhere else. On the other
hand, when the forwards are declared in the global forwards section of the strutsconfig.xml, they are accessible from any ActionMapping. (In the next section,
you will look closely at Struts Config file.) Either ways, the findForward()
method on the ActionMapping instance retrieves the ActionForward
as follows.
ActionForward forward = mapping.findForward(success);
The logical name of the page (success) is passed as the keyword to the
findForward() method. The findForward() method searches for the
forward with the name success, first within the ActionMapping and then in the
global-forwards section. The CustomerActions execute() method returns
the ActionForward and the RequestProcessor returns the physical JSP
37
to the user. In J2EE terms, this is referred to as dispatching the view to the user.
The dispatch can be either HTTP Forward or HTTP Redirect. For instance, the
dispatch to the success is a HTTP Redirect whereas the dispatch to failure is a
HTTP Redirect.
Diference between HTTP Forward and HTTP Redirect
HTTP Forward is the process of simply displaying a page when requested
by the user. The user asks for a resource (page) by clicking on a hyperlink or
submitting a form and the next page is rendered as the response. In
Servlet Container, HTTP Forward is achieved by invoking the following.
RequestDispatcher dispatcher =
httpServletRequest.getRequestDispatcher(url);
Dispatcher.forward(httpServletRequest, httpServletResponse);
2.
Server time and resources are precious since they are shared. Spending too
much of servers time and resources on a request, that we know is going to
fail eventually is a waste of server resources.
It has a negative impact on the code quality. Since one has to prepare for the
possibility of having null data, appropriate checks have to be put (or
83
Generally business logic is the toughest code of the system and contains
enough if-else blocks as such. More if-else blocks for null checks can only
mean two things bad code and maintenance nightmare. Not an elegant
programming to say the least. If only you could verify the validity of the user
data as close to the user, then the rest of the code only has to deal
with business logic and not invalid data.
Listing 2.2 validate() method in the CustomerForm
public ActionErrors validate(ActionMapping mapping,
HttpServletRequest request)
{
// Perform validator framework validations
ActionErrors errors = super.validate(mapping, request);
// Only need crossfield validations here
if (parent == null) {
errors.add(GLOBAL_ERROR,
new ActionError("error.custform"));
}
if (firstName == null) {
errors.add("firstName",
new ActionError("error.firstName.null"));
}
return errors;
}
39
"
'
As you learnt in Chapter 1, the configurable controller is the answer to the Fat
controller problem. In a Fat Controller, the programmers can code if blocks on
need basis. Not so with the configurable controllers. The expressive
and configuration capability is limited to what the built-in controller can support.
In Struts, the built-in controller supports a variety of cases that can arise
while developing web applications. It even provides points to extend the
configuration capabilities. These points known as Extension points, take the
configuration capability to the next dimension. We will deal with extending Struts
in Chapter 7. In this section, we will just look at the normal facilities offered
by the struts- config.xml.
04
3.
4.
Scope
Validate
Input
Forward
Description
The URL path (either path mapping or suffix mapping) for which this
Action Mapping is used. The path should be unique
The fully qualified class name of the Action
The logical name of the Form bean. The actual ActionForm associated
with this Action Mapping is found by looking in the Form-bean definition
section for a form-bean with the matching name. This informs the
Struts application which action mappings should use which
ActionForms.
Scope of the Form bean Can be session or request
Can be true or false. When true, the Form bean is validated on
submission. If false, the validation is skipped.
The physical page (or another ActionMapping) to which control should
be forwarded when validation errors exist in the form bean.
The physical page (or another ActionMapping) to which the control
should be forwarded when the ActionForward with this name is
selected in the execute method of the Action class.
The ActionMapping section contains the mapping from URL path to an Action
class (and also associates a Form bean with the path). The type attribute is the
fully qualified class name of the associated Action. Each action entry in the
action-mappings should have a unique path. This follows from the fact that each
41
<strutsconfig>
<formbeans>
<formbean
name="CustomerForm"
type="mybank.example.CustomerForm"/>
<formbean
name="LogonForm"
type="mybank.example.LogonForm"/>
</formbeans>
<globalforwards>
<forward
name="logon"
path="/logon.jsp"/>
<forward
name="logoff" path="/logoff.do"/>
</globalforwards>
Action Mappings
<actionmappings>
<action
path="/submitDetailForm"
type="mybank.example.CustomerAction"
name="CustomerForm"
scope="request"
validate="true"
input="/CustomerDetailForm.jsp">
<forward name="success"
path="/ThankYou.jsp"
redirect=true />
<forward name="failure"
path="/Failure.jsp"
/>
</action>
<action path=/logoff parameter=/logoff.jsp
type=org.apache.struts.action.ForwardAction />
</actionmappings>
<controller
Controller Configuration
processorClass="org.apache.struts.action.RequestProcessor" />
<messageresources parameter="mybank.ApplicationResources"/>
24
</strutsconfig>
In the ActionMapping there are two forwards. Those forwards are local
forwards which means those forwards can be accessed only within the
ActionMapping. On the other hand, the forwards defined in the Global Forward
section are accessible from any ActionMapping. As you have seen earlier, a
forward has a name and a path. The name attribute is the logical name assigned.
The path attribute is the resource to which the control is to be forwarded. This
resource can be an actual page name as in
<forward
name="logon"
path="/logon.jsp"/>
name="logoff"
path="/logoff.do "/>
)
In Struts, View components are nothing but six custom tag libraries for JSP
views HTML, Bean, Logic, Template, Nested, and Tiles tag libraries. Each one
caters to a different purpose and can be used individually or in combination with
others. For other kinds of views (For instance, Template based presentation) you
43
are on your own. As it turns out, majority of the developers using Struts tend to
use JSPs. You can extend the Struts tags and also build your own tags and mix
and match them.
You already know that the ActionForm is populated on its way in by the
RequestProcessor class using Java Introspection. In this section you will
learn how Struts tags interact with the controller and its helper classes to display
the JSP using two simple scenarios how FormTag displays the data on
the way out and how the ErrorsTag displays the error messages. We will
not cover every tag in Struts though. That is done in Chapter 6.
What is a custom tag?
Custom Tags are Java classes written by Java developers and can be used
in the JSP using XML markup. Think of them as view helper beans that can be
used without the need for scriptlets. Scriptlets are Java code
snippets intermingled with JSP markup. You need a Java developer to
write such scriptlets. JSP pages are normally developed and tweaked by
page authors, They cannot interpret the scriptlets. Moreover this blurs the
separation of duties in a project. Custom Tags are the answer to this
problem. They are XML based and like any markup language and can be
easily mastered by the page authors. You can get more information on
Custom Tags in Chapter 6. There are also numerous books written about JSP
fundamentals that cover this topic very well.
Listing 2.4 CustomerDetails JSP
<html>
<head>
<html:base/>
</head>
<body>
<html:form action="/submitDetailForm">
<html:text property="firstName" />
<html:text property="lastName" />
<html:submit>Continue</html:submit>
</html:form>
</body>
</html>
44
Struts html tags and shown in Listing 2.4. The <html:form> represents the
org.apache.struts.taglib.html.FormTag class, a body tag. The
<html:text> represents the org.apache.struts.taglib.html.TextTag
class, a normal tag. The resulting HTML is shown in Listing 2.5.
The FormTag can contain other tags in its body. SubmitTag generates the
Submit button at runtime. The TextTag <html:text> generates html textbox at
runtime as follows.
<input name=firstName type=text value= />
The FormTag has an attribute called action. Notice that the value of
the action attribute is /submitDetailForm in the JSP snippet shown above.
This represents the ActionMapping. The generated HTML <form>
has action=/submitDetailForm.do in its place. The servlet container
parses the JSP and renders the HTML.
Listing 2.5 Generated HTML from CustomerDetails JSP
<html>
<head>
<html:base/>
</head>
<body>
<form name=CustomerForm action=/submitDetailForm.do>
<input type=text name=firstName value= />
<input type=text name=lastName value= />
<input type=submit name=Submit value= />
</form>
</body>
</html>
3.
If it does not find one, it creates a new one and puts it in the
specified context. Otherwise it uses the existing one. It also makes the
Form name available in the page context.
4.
The form field tags (e.g. TextTag) access the ActionForm by its name
from the PageContext and retrieve values from the
ActionForm
45
container. Let us now see what facilities the framework provides to display those
errors in the JSP. Struts provides the ErrorsTag to display the errors in the JSP.
When the ActionForm returns the ActionErrors, the RequestProcessor
sets it in the request scope with a pre-defined and well-known name (within the
Struts framework) and then renders the input page. The ErrorsTag iterates over
the ActionErrors in the request scope and writes out each raw error text to the
HTML output.
You can put the ErrorsTag by adding <html:errors /> in the JSP. The tag
does not have any attributes. Neither does it have a body. It displays the errors
exactly in the location where you put the tag. The ErrorsTag prints three
elements to the HTML output header, body and footer. The error body consists
of the list of raw error text written out to by the tag. A sample error display from
struts-example.war (available with Struts 1.1 download) is shown in Figure 2.2.
You can configure the error header and footer through the Message Resource
Bundle. The ErrorsTag looks for predefined keys named errors.header and
errors.footer in the default (or specified) Message Resource Bundle and their
values are also written out AS IS. In the struts-example.war, these are set
as
follows:
64
For each ActionError, the ErrorsTag also looks for predefined keys
errors.prefix and errors.suffix in the default (or specified) Message
Resource Bundle. By setting errors.prefix=<li> and errors.suffix =
</li>, the generated HTML looks like follows and appears in the browser
as shown in Figure 2.2.
<h3><font color="red">Validation Error</font></h3>
You must correct the following error(s) before proceeding:
<ul>
<li>From Address is required.</li>
<li>Full Name is required.</li>
<li>Username is required</li>
</ul>
Note that all the formatting information is added as html markup into these
values. The bold red header, the line breaks and the horizontal rule is the result of
html markup in the errors.header and errors.footer respectively.
Tip: A common problem while developing Struts applications is that
<html:errors/> does not seem to display the error messages This
generally means one of the following:
The properties file could not be located or the key is not found. Set the
47
Another reason for not seeing the error messages has got to do with
the positioning of the tag itself. If you added the tag itself in the
<tr> instead of a <td>, the html browser cannot display the
messages even though the tag worked properly by writing out the errors
to the response stream.
The View Components was the last piece of the puzzle to be sorted out. As it
turns out, all the work is performed in the controller part of the framework. The
View Tags look for information in the request or session scope and render it as
HTML. Now, that is how a view should be as simple as possible and
yet elegant. Struts lets you do that, easy and fast.
%
In this chapter you learnt the Struts request lifecycle in quite a bit of detail. You
also got a good picture of Struts framework components when we covered
the controller and view components. You also got to know relevant sections
of struts-config.xml the Struts configuration file. Armed with this knowledge
we will build a Hello World web application using Struts framework in the
next chapter.
84
Chapter 3
Your first Struts application
In this chapter:
1. You will build your first Struts web application step by step
2. You will build a Web ARchive (WAR) and deploy the web application in
Tomcat
In the last two chapters you have learnt a lot about Struts. In this chapter will take
you step by step in building your first Struts application and deploying it onto
Tomcat.
*
You
can
access
the
sample
application
by
typing http://localhost:8080/App1/index.jsp in the browser. The index.jsp
contains
a
single
hyperlink.
The
link
is
http://localhost:8080/App1/CustomerDetails.jsp.
On clicking the link,
CustomerDetails.jsp is displayed. CustomerDetails.jsp contains an HTML Form
with two buttons Submit and Cancel. When the user submits the Form by
clicking Submit, Success.jsp is shown if the form validations go through. If
the validations fail, the same page is shown back to the user with the errors. If the
user clicks Cancel on the form, the index.jsp is shown to the user.
Figure 3.1 The JSP flow diagram for the Hello World Struts application.
49
'
) !%
2.
a.
b.
c.
b.
c.
d.
05
e.
4.
5.
6.
For every <bean:message> tag in the JSP, add key value pairs to the
Message Resource Bundle (properties file) created in Step 3b
7.
8.
9.
(continued..)
51
Step 1. As you already know from Chapter 2, the first step in writing a Struts
application is to add the ActionServlet entry in web.xml and also map
the servlet to the url-pattern *.do. This is shown in Listing 3.1. You already know
the meaning of the initialization parameter named config. Here we will introduce
two more initialization parameters. They are debug and detail.
The debug initialization parameter lets you set the level of detail in the debug
log. A lower number means lesser details and a higher number implies detailed
logging. It is absolutely essential that you use this logging feature especially in
the beginning and also while setting up Struts application for the first time. The
debug messages give you enough insight to resolve any configuration related
issues. Use them to their fullest capability. In Listing 3.1, we have set the value
of debug to 3.
The detail initialization parameter lets you set the level of detail in the
digester log. Digester is the component that parses the Struts Config file
and loads them into Java objects. Some of the errors can be traced by looking at
the log created by the Digester as it parses the XML file.
Later in this chapter, you will also use two of the Struts Tag libraries
to construct the JSP. Hence the relevant tag library definition files strutshtml.tld and struts-bean.tld are also declared in web.xml.
25
Step 2. Select a blank template for struts-config.xml and add the following
Listing 3.2 struts-config.xml with all entries for App1
<?xml version="1.0" encoding="ISO88591" ?>
<!DOCTYPE strutsconfig PUBLIC
"//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/strutsconfig_1_1.dtd">
<strutsconfig>
<formbeans>
<formbean
name="CustomerForm"
type="mybank.app1.CustomerForm"/>
</formbeans>
<globalforwards>
<forward name="mainpage"
path="index.jsp"
/>
</globalforwards>
<actionmappings>
<action
path="/submitCustomerForm"
type="mybank.app1.CustomerAction"
name="CustomerForm" scope="request"
validate="true"
input="CustomerDetails.jsp">
<forward name="success"
path="Success.jsp"
/>
<forward name="failure"
path="Failure.jsp"
/>
</action>
</actionmappings>
<controller
processorClass="org.apache.struts.action.RequestProcessor"/>
<messageresources parameter="mybank.app1.App1Messages"/>
</strutsconfig>
Step 2a. Declare the controller element in Struts Config file. The
<controller>
element
tells
the
Struts
framework
to
use
org.apache.struts.action.RequestProcessor for this application. For
53
Step 2b. Create a properties file under mybank.app1 java package and name it
as App1Messages.properties. You will later add key value pairs into this
file. Instead of hard coding field names in the JSP, you will use key names from
this file to access them. In this way, the actual name can be changed outside the
JSP. For now, add the following entry into the Struts Config file.
<messageresources parameter="mybank.app1.App1Messages"/>
Step 2c.Define the form bean by adding a form-bean entry in the formbeans section.
<formbean
name="CustomerForm"
type="mybank.app1.CustomerForm"/>
path="/submitCustomerForm"
type="mybank.app1.CustomerAction"
name="CustomerForm"
scope="request"
validate="true"
input="CustomerDetails.jsp">
</action>
path="Success.jsp"
/>
<forward name="failure"
path="Failure.jsp"
/>
At this point, the struts-config.xml looks as shown in Listing 3.3. All entries
in bold are added for App1.
Step
45
Form, the Struts controller populates the Form bean with data from HTML Form
by calling setter method on the Form bean instance.
Step
4. Next, create the Action bean by extending the
org.apache.struts.action.Action class. Let us call it CustomerAction.
Every class that extends Action implements the execute() method. As
you saw earlier in Chapter 2, the RequestProcessor calls the execute()
method after populating and validating the ActionForm. In this method you
typically implement logic to access middle-tier and return the next page to be
displayed to the user. Listing 3.4 shows the execute() method in
CustomerAction. In this method, an operation is performed to check is the
Cancel button was pressed. If so, the mainpage (Global Forward for
index.jsp) is shown to the user. The isCancelled() method is defined in the
parent Action class. If the operation requested is not Cancel, then the normal
flow commences and the user sees Success.jsp.
Listing 3.3 CustomerForm Form Bean for App1
public class CustomerForm extends ActionForm {
private String firstName;
private String lastName;
public CustomerForm() {
firstName = ;
lastName = ;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String s) {
this.firstName = s;
}
public String getLastName() {
return lastName;
}
public void setLastName(String s) {
this.lastName = s;
}
}
Step 5. Create the JSP using Struts html and bean tags.
55
All Struts html tags including the FormTag are defined in strutshtml.tld. These tags generate appropriate html at runtime. The TLD file strutshtml.tld and struts-bean.tld are declared at the top of JSP and associated with
logical names
html and bean respectively. The JSP then uses the tags with the prefix of
html: and bean: instead of the actual tag class name. Listing 3.5 shows the
CustomerDetails.jsp. Let us start from the top of this Listing.
Listing 3.4 CustomerAction Action Bean for App1
public class CustomerAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form, HttpServletRequest request,
HttpServletResponse response) throws Exception
{
if (isCancelled(request)) {
System.out.println(Cancel Operation Performed);
return mapping.findForward(mainpage);
}
CustomerForm custForm = (CustomerForm) form;
String firstName = custForm.getFirstName();
String lastName = custForm.getLastName();
System.out.println(Customer First name is + firstName);
System.out.println(Customer Last name is + lastName);
ActionForward forward = mapping.findForward(success);
return forward;
}
}
the real advantage of this tag is when the browser has to render the HTML based
on the locale. For instance, when the users locale is set to Russia, the tag
generates <html lang=ru> instead of the plain old <html>, so that the
browser can attempt to render the Russian characters (if any) in the best possible
manner. Setting <html:html locale="true"> tells Struts to look for the
locale specific resource bundle (More on this later).
<html:base>: As you might be already aware of, one of the best practices
in authoring pages is to use relative URLs instead of absolute ones. In order to
use relative URLs in HTML, you need to declare the page context root with the
declaration <base href=> tag. All URLs (not starting with /) are
65
assumed to be relative to the base href. This is exactly what the <html:base/>
tag generates.
It has one mandatory attribute action. The action attribute represents the
ActionMapping for this form. For instance, the action attribute in Listing 3.5 is
/submitCustomerForm. Note that the FormTag converts this into a HTML
equivalent as follows:
<form name=CustomerForm action=/App1/submitCustomerForm.do>
57
Cancel button. This must have started a though process in your mind now. Why
do I need a <html:cancel> when I already have <html:submit>? Well, this
is because of the special meaning of Cancel in everyday form processing.
Pressing a Cancel button also results in Form submission. You already know that
when validate is set to true, the form submission results in a validation. However
it is absurd to validate the form when form processing is cancelled. Struts
addresses this problem by assigning a unique name to the Cancel button itself.
Accordingly, a JSP tag <html:cancel>Cancel Me</html:cancel> will
generate equivalent HTML as follows:
<input type="submit"
name="org.apache.struts.taglib.html.CANCEL"
value="Cancel Me">
Just before the RequestProcessor begins the Form validation, it checks if the
button name was org.apache.struts.taglib.html.CANCEL. If so, it
abandons the validation and proceeds further. And thats why <html:cancel>
is different from <html:submit>.
<html:errors>: This tag displays the errors from the ActionForm
validation method. You already looked at its working in the last chapter.
85
In the generated html, you might notice that the <html:errors/> tag did
not translate into any meaningful HTML. When the form is displayed for the first
time, the validate() method in CustomerForm hasnt been executed
yet and hence there are no errors. Consequently the <html:errors/> tag
does not output HTML response.
There is another tag used in Listing 3.5 called <bean:message> for which
we did not provide any explanation yet. The <bean:message> tags in the JSP
generate regular text output in the HTML (See Listing 3.6). The
<bean:message> tag has one attribute named key. This is the key to
the Message Resource Bundle. Using the key, the <bean:message> looks up
the properties file for appropriate values. Hence our next task is to add
some key value pairs to the properties file created in Step 3b.
Listing 3.6 Generated HTML for CustomerDetails.jsp
<html lang=en>
<head>
<base
href=http://localhost:8080/App1/CustomerDetails.jsp />
</head>
<body>
<form name=CustomerForm
action=/App1/submitCustomerForm.do>
First Name:
<input type=text name=firstName value= />
<BR>
Last Name:
<input type=text name=lastName value= />
<BR>
<input type=submit value=Save Me/>
<input type="submit"
name="org.apache.struts.taglib.html.CANCEL"
value="Cancel Me">
</form>
<body>
</html>
Step 6. For every <bean:message> tag in the JSP, add key value pairs to the
Message Resource Bundle (App1Messages.properties) created in Step 3b. This is
pretty straightforward. Listing 3.7 shows the App1Messages.properties. We will
add more contents into this file in Step 9. But for now, this is all we have in the
Message Resource Bundle.
59
According to the business requirements set for this application, first name has
to exist all the time. Hence the validate() method checks to see if the
first name is null or if the first name is all spaces. If either of this condition is
met, then it is an error and according an ActionError object is created and added to
the ActionErrors. Think of the ActionErrors as a container for holding
individual ActionError objects. In Listing 3.8, the ActionError instance is
created by supplying a key error.cust.firstname.null to the
ActionError constructor. This key is used to look up the Message
Resource Bundle. In the next step, the keys used for error messages are added
to the Message Resource Bundle.
Listing 3.8 validate() method in CustomerForm
public ActionErrors validate(ActionMapping mapping,
HttpServletRequest request) {
ActionErrors errors = new ActionErrors();
if (firstName == null || firstName.trim().equals()) {
errors.add("firstName",
new ActionError("error.cust.firstname.null"));
}
return errors;
}
Step 8. In Step 7, validate() method was provided with the error messages
identified by keys. In this step, the error message keys are added to the same old
App1Messages.properties. The modified App1Messages.properties is shown in
Listing 3.9. The new entry is shown in bold. Note that we have used a
prefix
error for the error message entries, a prefix of button for button labels and a
prefix of prompt for regular HTML text. There is no hard and fast rule and it is
only a matter of preference. You can name the keys anyway you want.
06
Step 9. In the previous steps, you created most of the artifacts needed for
the Struts application. There are two more left. They are index.jsp and
Success.jsp. These two JSPs are pretty simple and are shown in Listing 3.10 and
Listing 3.11 respectively.
Listing 3.9 Updated App1Messages.properties
prompt.customer.firstname=First Name
prompt.customer.lastname=Last Name
button.save=Save Me
button.cancel=Cancel Me
error.cust.firstname.null=First Name is required
Now, thats a painful and laborious thing to do for every link in your
application. In addition, you are unnecessarily introducing a scriptlet for
every encoding. The good news is that the <html:link> does that
automatically. For instance,
61
In the previous steps, you completed all the coding that was required. Now you
should compile the Java classes, create the WAR artifact and deploy onto
Tomcat. Compiling the classes is a no-brainer. Just set the right classpath
and invoke javac. The classpath should consist of the servlet-api.jar from
Tomcat
(This jar can be found in <TOMCAT_HOME>/common/lib, where
TOMCAT_HOME is the Tomcat installation directory.) and all the JAR files
26
6. Create a directory lib under WEB-INF and copy all the JAR files from Struts
distribution into the lib folder. These JAR files are required by your web
application at runtime.
7. Copy struts-bean.tld and struts-html.tld from Struts distribution into the WEBINF directory.
8. Zip (or jar) the temp directory into a file named App1.war. You WAR is ready
now. Drop it into the webapps sub-directory in Tomcat. Start Tomcat and test it
out!
63
Congratulations! You have successfully developed and deployed your first Struts
application. However we are not done yet. Let us look at some practical issues
that need to be addressed.
-"
This worked okay for us since there was only one button with real Form
submission (The other one was a Cancel button). Hence it sufficed for us
to straight away process the request in CustomerAction. You will frequently
face situations where there are more than one or two buttons submitting the form.
You would want to execute different code based on the buttons clicked. If
you are thinking, No problem. I will have different ActionMapping (and hence
different Actions) for different buttons, you are out of luck! Clicking any of the
buttons in a HTML Form always submits the same Form, with the same URL.
The Form submission URL is found in the action attribute of the form tag as:
<formname=CustomFormaction=/App1/submitCustomerForm.do/>
and is unique to the Form. You have to use a variation of the <html:submit>
as shown below to tackle this problem.
<html:submit property=step>
<bean:message key=button.save/>
</html:submit>
The above SubmitTag, has an additional attribute named property whose value is
step. The meaning of the property attribute is similar to that in <html:text> - It
represents a JavaBeans property in the ActionForm and generates the name of the
Form input element. This tag generates a HTML as follows
<input type="submit" name=step value="Save Me">
The generated HTML submit button has a name associated with it. You have
to now add a JavaBeans property to your ActionForm whose name matches the
46
submit button name. In other words an instance variable with a getter and setter
are required. If you were to make this change in the application just developed,
you have to add a variable named step in the CustomerForm and then add
two methods getStep() and setStep(). The Struts Framework sets the value
of the step by Introspection, just like it does on the other fields. In the
CustomerAction, the logic corresponding to the Save Me button is
executed after performing a check for the Save Me button. Listing 3.12
shows the modified execute() method from CustomerAction. The changes
are shown in bold. When the Save Me button is pressed, the
custForm.getStep() method returns a value of Save Me and the
corresponding code block is executed.
Listing 3.12 CustomerAction modified for mutltiple button Forms
public class CustomerAction extends Action
{
public ActionForward execute(ActionMapping mapping,
ActionForm form, HttpServletRequest request,
HttpServletResponse response) throws Exception
{
if (isCancelled(request)) {
System.out.println(Cancel Operation Performed);
return mapping.findForward(mainpage);
}
CustomerForm custForm = (CustomerForm) form;
ActionForward forward = null;
if ( Save Me.equals(custForm.getStep()) ) {
System.out.println(Save Me Button Clicked);
String firstName = custForm.getFirstName();
String lastName = custForm.getLastName();
System.out.println(Customer First name is +
firstName);
System.out.println(Customer Last name is +
lastName);
forward = mapping.findForward(success);
}
return forward;
}
}
65
Using the HTML Button Label to distinguish the buttons works for most of
the cases except when you have a internationalized Struts web application.
Consider the HTML rendered for a Spanish user. By virtue of the Message
Resource Bundles (<bean:message> tag), the Spanish user will see a label of
Excepto M instead of Save Me. However the CustomerAction class is still
looking for the hard coded Save Me. Consequently the code block meant for
Save Me button never gets executed. In Chapter 4, you will see how a
specialized subclass of the Action called LookupDispatchAction solves this
problem.
When you constructed the web application, earlier in this chapter, you used static
messages in the Resource Bundle. However consider this: You have a dozen
fields in the form. The only validation rule is that all fields are required. Hence
the error messages for each field differs from another only by the field
name. First name is required, Last name is required, Age is required and so
on. It would be ideal if there were a field name replacement mechanism into
a fixed error message template. The good news is that it already exists. In the
resource bundle file, you can define a template for the above error message as:
errors.required={0} is required.
66
The first
The second
values. The
replacement.
follows:
67
template for your applications. It has all the tlds and jars included in the WAR.
Plus it provides the web.xml and struts-config.xml ready to be used
as placeholders with default values.
In this chapter you applied all that you have learnt so far and built a Hello World
Struts application. This application although simple, illustrates the basic steps in
building a Struts application. In the coming chapters we will go beyond the
basics and learn other features in Struts and effectively apply them to tackle real
life scenarios.
86
Chapter 4
All about Actions
In this chapter:
1.
You will learn about all the built-in Struts Actions ForwardAction,
IncludeAction, DispatchAction, LookupDispatchAction and SwitchAction
2. You will learn about multiple sub application support in Struts and using
SwitchAction to transparently navigate between them
3. You will see examples of effectively using the built-in Actions
4. You will learn of ways to prevent direct JSP access by the users
In Chapter 2, you understood the basics of the Struts framework. In Chapter 3,
you applied those basics to build a simple web application using Struts and got a
clear picture of the basics. In this chapter we take you beyond the basics as you
explore Struts Controller components that are interesting and timesaving that
prepare you to handle realistic scenarios.
Action classes are where your presentation logic resides. In Chapter 2, you
saw how to write your own Action. Struts 1.1 provides some types of Action outof-the-box, so you dont have to build them from the scratch. The Actions
provided by Struts are ForwardAction, IncludeAction, DispatchAction,
LookupDispatchAction and SwitchAction. All these classes are defined in
org.apache.struts.actions package. These built-in actions are very
helpful to address some problems faced in day to day programming.
Understanding them is the key to using them effectively. All of these the Actions
are frequently used, except for IncludeAction.
"
ForwardAction is the one of the most frequently used built-in Action classes.
The primary reason behind this is that ForwardAction allows you to adhere to
MVC paradigm when designing JSP navigation. Most of the times you will
perform some processing when you navigate from one page to another. In Struts,
69
this processing is encapsulated in the Action instances. There are times however
when all you want to do is navigate from one page to another without performing
any processing. You would be tempted to add a hyperlink on the first page for
direct navigation to the second. Watch out! In Model 2 paradigm, a straight JSP
invocation from another JSP is discouraged, although not prohibited.
For instance, suppose you want to go from PageA.jsp to PageB.jsp in your
Struts application. The easy way of achieving this is to add a hyperlink in
PageA.jsp as follows:
<a href=PageB.jsp>Go
to Page B</a>
to Page B</html:link>
to Page B</html:link>
07
to Page B</html:link>
When you use the action attribute instead of the page attribute in
<html:link>, you need not specify the .do explicitly.
to Page B</html:link>
When used in this manner, the <html:link> gets transformed into the
following HTML Link.
<a href=App1/PageB.jsp>Go to Page B</a>
Oops, that doesnt seem right. The HTML Link is now displaying the actual
JSP name directly in the browser. Ideally you would love to hide the JSP name
71
from the user. And with a slight twist you can! First, define an ActionMapping as
follows:
<action path=/gotoPageB
parameter=/PageB.jsp
type=org.apache.struts.actions.ForwardAction />
Next, modify the global forward itself to point to the above ActionMapping.
<globalforwards>
<forward name=pageBForward path=/gotoPageB.do />
</globalforwards>
to Page B</a>
There you go! The generated HTML is not displaying the JSP name
anymore. From a design perspective this seems to be the best way of
using the
<html:link> tag since the link is completely decoupled from the associated
ActionMapping, thanks to the global-forward.
The <html:link> points to the global-forward and the global-forward
points to the ForwardAction. The extra level of indirection, although looks
confusing in the beginning, is a good design decision due to the following
reason:
As is true with any application, requirements change and it might just
become necessary to do some processing during the navigation from PageA
to PageB. A conversion from ForwardAction to a custom Action will be easier
to manage with the extra level of indirection.
Using ForwardAction for Integration
In general, the ForwardActions parameter attribute specifies the resource
to be forwarded to. It can be the physical page like PageB.jsp or it can be a URL
pattern handled by another controller, maybe somewhere outside Struts. For
instance, consider the following ForwardAction.
<action path=/gotoPageB
parameter=/xoom/AppB
type=org.apache.struts.actions.ForwardAction />
In the snippet above, the value of the parameter is not a physical page. It is a
logical resource that might be mapped to another Servlet totally outside the
control of Struts. Yet from PageAs perspective, you are still dealing with a
Struts URL. This is the second use of ForwardAction. You can integrate
Struts applications transparently with already existing non-Struts applications.
NOTE: Even with the ForwardAction, you cannot prevent a nosy user
from accessing the JSP directly. See the section Protecting JSPs from direct
access for techniques to protect your JSPs from direct access.
27
ForwardAction Hands-on
In the last chapter, we modeled the navigation from index.jsp to
CustomerDetails.jsp with a direct link. Let us correct the mistake we made
by applying the knowledge we have gained so far. Think of index.jsp as PageA
and CustomerDetails.jsp as PageB. The <html:link> in index.jsp will look
as follows: <html:link forward=CustomerDetailsPage>Customer Form</a>
The following Global Forward and ForwardAction are added to the Struts
Config file.
<globalforwards>
..
<forward name="CustomerDetailsPage"
path="/gotoCustomerDetails.do"
/>
</globalforwards>
<actionmappings>
..
<action path=/gotoCustomerDetails
parameter=/CustomerDetails.jsp
type=org.apache.struts.actions.ForwardAction />
</actionmappings>
And now, we have an application that strictly adheres to MVC. What a relief!
2
According to the Model 2 paradigm, the view is always served by the controller
and should not be requested explicitly from any other view. In reality a JSP can
always navigate to another JSP when the JSPs are placed anywhere in a WAR
other than the WEB-INF directory (or its sub-directories). Similarly a user
can type in the name of the JSP in the URL bar and invoke the JSP. The
web application specification does not disallow such access. Actually this
makes sense. The specification should not prevent anybody from coding
using the Model 1 paradigm. Consequently your JSPs are exposed to the external
world for nosy users to cause unnecessary problems, for hackers to
exploit any vulnerability in the system. If you are wondering what the
problem is with allowing direct access to JSPs, well, here are some.
A nosy user might attempt to guess the JSP name by the operation performed in
that page or request parameters or worse if the page author used html comment
tag for SCM and code comments instead of the JSP comments. Armed with this
information, the user attempts to access the JSPs directly. A JSP as you know is a
view and it displays information based on model objects stored in one of the four
scopes page, request, session or application, the first three being the most
73
common. These objects are created by the back end presentation and
business logic and made available for the JSP to act upon. When the JSP is
accessed out of context or out of order, the required model objects may
not exist in the appropriate scope and consequently almost always leads to
the exceptional situations in the JSP code.
It is not common to perform null checks in every bit of code in the JSP tags,
scriptlets and other helper classes. These checks are generally limited
to interfaces and boundaries between modules and not later on. For instance, in
a typical Model 2 scenario, when the model object cannot be created for
some reason, the controller instead takes alternate route and displays an alternate
view corresponding to the null model object. This assumption of model objects
being not null in the main path of the presentation logic and view highly
simplifies the coding. In fact when the system is accessed as intended,
everything works smoothly. However whenever somebody tries to access the
views out of order, all hell breaks lose. Every view starts throwing
NullPointerExceptions, IllegalArgumentExceptions
and
other
unchecked and checked exceptions depending on how the JSP page and its tags
and scriptlets are authored. This is exactly what a nosy user is trying out.
The implications are even more serious when a malicious user tries to find weak
points in the design to bring the system down to its knees. The first thing that
might occur is to put checks for nulls and unintended access in the system.
Invariably, this is nothing but a collection of if-else blocks in every part of the
JSP page making it messy and buggy to maintain.
Two prominent alternatives exist. Let us look the easiest one first. As
we glossed over earlier, the servlet specification explicitly states that
contents located in the WEB-INF and its sub-directories are protected from outside
access. Let us take a simple example to illustrate this. All contents located
in a WAR belong to the same protection domain. A protection domain is a
set of entities known (or assumed) to trust each other. Consequently any
resource within a WAR can access resources located under WEB-INF
directory without restrictions. JSP is also a resource and thus any class within
the same WAR can forward to a JSP under WEB-INF. (This part is not
explicitly stated in the specification) However when the request originates
outside the container, it does not belong to the protection domain (at least not
until it is authenticated) and hence cannot access the protected resource
under WEB-INF. Thus putting all JSPs under the WEB-INF directly or as
sub-directories if needed is the easiest and also the best way of protecting direct
access to JSPs. What if the hyperlink in one of your page wants to really just
forward to another JSP? Is that disallowed as well? Yeah! You cannot have
different rules in your system right? However there is a way around.
47
75
First, all the url patterns ending with suffix .jsp are associated with a Role
named Denied. Any user who wants to access the JSP pages directly should be
in that role. We further ensure that no user of the system is in that Role. Role and
user association is done depending on your implementation of authentication and
authorization. For instance, if you are using LDAP as the user persistence
mechanism, then the users, their passwords and Roles are stored in LDAP. If you
ensure nobody gets the Denied role, then you have effectively
prevented everyone from directly accessing the JSPs. You will still have to
have the ForwardAction as shown earlier in this section if you have situation
when page A needs to just navigate to page B. The internal forwards to
other JSPs using RequestDispatcher are okay because the container does
not intercept and cross check internal forwards even though the url-pattern
matches the ones in web.xml.
NOTE: The default pagePattern and forwardPattern values for
<controller> element in struts-config.xml are $M$P, where $M is replaced
with the module prefix and the $P is replaced with the path attribute of the
selected forward. If you place your JSP files under WEB-INF for access
protection, you have to set the pagePattern and forwardPattern attributes
of the <controller> element in the struts-config.xml to /WEBINF/$M$P
to tell Struts to construct the paths correctly.
*
IncludeAction is much like ForwardAction except that the resulting
It is very clear from the value of the page attribute that it is a nonStruts resource. Wouldnt it be better to have a <jsp:include> that pretends as
if the resource exists in the current Struts application? It would be ideal if
the page include looked as follows:
<jsp:include page=/App1/legacyA.do />
67
servlet container cannot process the rest of the JSP and include its response in the
OutputStream. Consequently it throws a IllegalStateException with a
message that Response is already committed. IncludeAction addresses this
problem. Instead of forwarding to the specified resource, it includes the resource
in the current response. Consequently the output of the LegacyServletA is
displayed in the same HTML as that of the Struts application. You have to add
the following ActionMapping in the Struts Config file:
<action path=/legacyA
parameter=/xoom/LegacyServletA
type=org.apache.struts.actions.IncludeAction />
The parameter attribute indicates the actual resource that has to be included
in the response.
As mentioned earlier, the use of IncludeAction is limited to including
responses from existing Servlet in the current page. This requires the use of
<jsp:include> in the page. If you web application is aggregating response
from legacy servlet applications, portlets seems to be the way to go. Portlet API
JSR 168 has been finalized and it is matter of time before you can develop
standardized portals aggregating contents from disparate web applications. Tiles
framework is the way to go if you are on a short-term project that wants
to aggregate information now (From different applications or may be from
various Actions in the same Struts application). Tiles provides a robust alternative
to the primitive <jsp:include>. Chapter 7 provides an in-depth coverage of
Tiles in conjunction with Struts.
use it as is. You will have to extend it to provide your own implementation. An
example will make things clear. Consider an online credit card application.
Customers fill the credit card application online. The bank personnel get a List
screen as shown in Figure 4.1 and they can act in one of four ways - Approve,
Reject or Add Comment. Consequently there are three images each being a
<html:link>.
One way of dealing with this situation is to create three different Actions
ApproveAction, RejectAction and AddCommentAction. This is a valid
approach, although not elegant since there might be duplication of code across
the Actions since they are related. DispatchAction is the answer to this
problem. With DispatchAction, you can combine all three Actions into one.
The DispatchAction provides the implementation for the execute()
method, but still is declared as abstract class. You start by sub-classing
77
You might be wondering why all the three methods take the same four
arguments ActionMapping, ActionForm, HttpServletRequest,
HttpServletResponse. Dont worry, you will find the answer soon.
For a moment, look at the four URLs submitted when the bank staff perform
the three actions as mentioned before. They would look something like this.
http://localhost:8080/bank/screen-credit-app.do?step=reject&id=2
http://localhost:8080/ bank/screen-credit-app.do?step=approve&id=2
http://localhost:8080/bank/screen-credit-app.do?step=addComment&id=2
Figure 4.1 Screen Credit Applications page as seen by the bank staf.
An interesting thing to notice is that the value of the HTTP request parameter
named step is same as the four method names in CreditAppAction. This is no
coincidence. DispatchAction (the parent class of CreditAppAction) uses
the value of the HTTP request parameter step to determine which method
in CreditAppAction has to be invoked. In the execute()
method, DispatchAction uses reflection to invoke the appropriate
method in CreditAppAction. For this reason, the arguments on all the three
methods in CreditAppAction are fixed and have to be ActionMapping,
ActionForm, HttpServletRequest, and HttpServletResponse in that
order. Otherwise the method invocation by Reflection fails.
Okay, so part of the puzzle is solved. But how does DispatchAction know
to look for the HTTP request parameter specifically named step in the URL? The
simple answer is that it doesnt. You will have to tell it explicitly. And this
is done in the ActionMapping for /screen-credit-app.do. The ActionMapping
for
87
79
1.
2.
Identify the related actions and create a method for each of the logical
actions. Verify that the methods have the fixed method signature shown
earlier.
3.
Identify the request parameter that will uniquely identify all actions.
4.
5.
Set your JSP so that the previously identified request parameter (Step 3)
takes on DispatchAction subclass method names as its values.
Design Tip: Use DispatchAction when a set of actions is closely related and
separating them into multiple Actions would result in duplication of code or
usage of external helper classes to refactor the duplicated code. In the above
example DispatchAction was used handle hyperlinks. DispatchAction is a
good choice when there are form submissions using the regular buttons (not
08
the image buttons). Just name all the buttons same. For instance,
<html:submit property=step>Update</html:submit>
<html:submit property=step>Delete</html:submit>
and so on. Image buttons is a different ball game. Image button usage for form
submission and DispatchAction are exclusive. You have to choose one. See
Chapter 6 on Struts tags for details on Image buttons.
In the above example we used the DispatchAction and used methods that
has ActionForm as one of its arguments. As you learnt in the last chapter,
an ActionForm always existed in conjunction with the Action. Earlier in
this chapter, we dealt with ForwardAction and we neither developed our Action
or ActionForm. In that context we stated that having an ActionForm was
optional. That holds true even if the Action is a custom coded one
like the CreditAppAction. If the ActionMapping does not specify a form
bean, then the ActionForm argument has a null value. In the Listing 4.1,
all the four methods got a null ActionForm. But that did not matter since the
HTTP request parameters were used directly in the Action. You can have a Form
bean if there are a lot of HTTP parameters (and perhaps also require
validation). The HTTP parameters can then be accessed through the Form bean.
#-
)3
In Chapter 3 you were introduced to a Localization problem with the Action class
when the form has multiple buttons. Using LookupDispatchAction is one
way of addressing the problem when regular buttons are used. Chapter 6 presents
another alternative that works irrespective of whether an Image or a grey button
is used to submit the Form. One has to choose the most appropriate
solution under the given circumstances.
Figure 4.2 Modified Screen Credit Applications page as seen by the bank staf.
81
before, but with one twist. Each row in the list is a HTML Form and the images
are now replaced with grey buttons to submit the Form. Figure 4.2 shows
the modified application list as seen by the bank personnel.
A LookupDispatchAction for this example is created by following these
steps.
1. Create a subclass of LookupDispatchAction.
2.
Identify the related actions and create a method for each of the logical
actions. Verify that the methods have the fixed method signature as similar to
DispatchAction methods in Listing 4.1.
3.
Identify the request parameter that will uniquely identify all actions.
4.
5.
28
map.put(button.approve, approve);
map.put(button.reject, reject);
map.put(button.comment, addComment);
}
}
6.
Next, create the buttons in the JSP by using <bean:message> for their
names. This is very important. If you hardcode the button names you will not
get benefit of the LookupDispatchAction. For instance, the JSP snippet
for Approve and Add Comment button are:
<html:submit property=step>
<bean:message key=button.approve/>
</html:submit>
<html:submit property=step>
<bean:message key=button.comment/>
</html:submit>
))
So far we have covered several important built-in Actions with examples. There
is one more feature that is very important and useful addition in 1.1 Multiple
Application module support. In Struts1.0 (and earlier), a single config file
was supported. This file, normally called struts-config.xml, was specified in
web.xml as an initialization parameter for the ActionServlet as follows:
83
<servlet>
<servletname>mybank</servletname>
<servletclass>org.apache.struts.action.ActionServlet
</servletclass>
<initparam>
<paramname>config</paramname>
<paramvalue>/WEBINF/strutsconfig.xml</paramvalue>
</initparam>
</servlet>
48
85
<html:link forward="gotoloanModule">
Go to Loan Module
</html:link>
path="/listloans"
type="mybank.app1.ListLoanAction">
</action>
40
"
You have looked at different types of Actions offered by Struts. Now, let us look
at some recommended practices in using Action. When it comes to using
Actions, the brute force approach is to extend the actions directly from the
org.apache.struts.action.Action. But a careful look at your web
application will certainly reveal behavior that needs to be centralized. Sooner or
later you will discover functionality common to all the actions. While it is
68
impossible to predict the exact purposes of why you might need the base Action,
here are some samples:
You might like to perform logging in Action classes for debugging purposes
or otherwise to track the user behavior or for security audit purposes.
You might want to retrieve the users profile from application specific
database to check if the user has access to your application and
act appropriately.
Whatever the purpose, there is always something done always in web
applications warranting a parent Action class. Start with a common parent Action
class. Let us call it MybankBaseAction. Depending on the complexities of the
web application, you can further create child classes for specific purposes.
For instance, an Action subclass for dealing with form submissions and another
for dealing with hyperlink-based navigation is a logical choice if the Action
classes handling hyperlink dont need an ActionForm. You might want to filter out
some words typed in the form fields.
In conjunction with the base Action, you can also roll a base Form extending
the org.apache.struts.action.ActionForm. Let us call this class
MybankBaseForm. The base form fits well into the base action strategy. In
chapter 2, we introduced the term View Data Transfer Object to refer an
ActionForm. This isnt without a reason. Data Transfer Object is a Core
J2EE pattern name. It is typically used between tiers to exchange data.
The ActionForm serves similar purpose in a Struts application and you use to its
very best. Typical uses of a base form would be:
Add attributes to the base form that are needed frequently in the web
application. Consider a case when every Action in your web application
needs to reference an attribute in the request or session. Instead of adding the
code to access this attribute as request.getAttribute(attribName)
everywhere, you can set this as an ActionForm attribute and access it in
a type-safe manner in the application.
Retrieving the users profile from application specific database and then set
it as a form attribute on every call to MybankBaseActions
execute() method.
Listing 4.5 shows the MybankBaseAction using the MybankBaseForm. It
implemented the execute() method and adds audit logging for entry and exit
points. Further down the line, it retrieves the application specific profile for the
user. This is helpful if you have a portal with a single sign-on and the user rights
and profiles differ from one application to another. Then it casts the
ActionForm to MybankBaseForm and assigns its variables with the values of
commonly accessed request and session attributes. MybankBaseAction defines
three abstract methods preprocess(), process() and postprocess().
These methods when implemented by the subclasses respectively perform
pre-
87
88
request.getAttribute(profile));
5+
3 )
"
89
in the URL bar. On the back end, Struts selects the action mapping
associated with submitCustomerForm and executes the action instance. When
you press refresh, the same URL is submitted and the same action instance
is executed again. The easy solution to this problem is to use HTTP redirect
after the form submission. Suppose that the CustomerForm submission results
in showing a page called Success.jsp. When HTTP redirect is used, the URL in
the
URL
bar
becomes
/App1/Success.jsp
instead
of
/App1/submitCustomerForm.do. When the page refreshed, it is the
Success.jsp that is loaded again instead of
/App1/submitCustomerForm.do. Hence the form is not submitted again. To
use the HTTP redirect feature, the forward is set as follows:
<forward name=success path=/Success.jsp redirect=true />
However there is one catch. With the above setting, the actual JSP name is
shown in the URL. Whenever the JSP name appears in the URL bar, it is
a candidate for ForwardAction. Hence change the above forward to be as follows:
<forward name=success path=/GotoSuccess.do redirect=true />
09
The method generates a random token using session id, current time and
a MessageDigest and
stores it in the session using a key
name org.apache.struts.action.TOKEN (This is the value of the static
variable TRANSACTION_TOKEN_KEY in org.apache.struts.Globals
class.
The Action class that renders the form invokes the saveToken() method to
create a session attribute with the above name. In the JSP, you have to use the
token as a hidden form field as follows:
<input type="hidden" name="<
%=org.apache.struts.taglib.html.Constants.TOKEN_KEY%>"
value="<bean:write name="<%=Globals.TRANSACTION_TOKEN_KEY%>"/>">
91
You can use the same approach for sensitive hyperlink navigations. Just set
the tranaction attribute in <html:link> to true and use the same logic in
the Action classes to track the duplicate hyperlink navigations.
The reset argument of the isTokenValid() is useful for multi-page form
scenario. Consider a form that spans across multiple pages. The form
is submitted every time the user traverses from one page to another. You
definitely want to validate token on every page submission. However you
also want to allow the user to traverse back and forth using the browser back
button until the point of final submission. If the token is reset on every page
submission, the possibility of back and forth traversal using the browser button is
ruled out. The solution is not disabling back button (using JavaScript hacks) but
to handle the token intelligently. This is where the reset argument is useful.
The token is initially set before showing the first page of the form. The
reset argument is false for all the isTokenValid() invocations except in the
Action for the last page. The last page uses a true value for the reset argument
and hence the token is reset in the isTokenValid() method. From this point
onwards you cannot use back button to traverse to the earlier form pages and
successfully submit the form.
6,
8 9
Dont even think twice Action classes should contain only the
presentation logic. If it is business logic it does not belong here.
What qualifies as presentation logic? The following do analyzing request
parameters and creating data transfer objects (for server side processing),
invoking business logic
(preferably through business delegates), creating view-models the model
JavaBeans for the JSPs, selecting the next view and converting exceptions into
appropriate action errors. Thats probably it.
The common mistake while coding the Action is stuffing the
execute() with a lot of things that dont belong there. By the time it is
noticed, the execute() method has intermingled request handling and
business logic beyond the point of separation without considerable effort.
The separation is tough
because,
when
there is no architectural
separation, the HttpServletRequest and HttpSession attributes will be
used all over the place and hence the code cannot be moved enmasse to the
server side to extract a class. The first resolution you have to make for a
cleaner and better design is to avoid this temptation.
A preferred way of splitting the code in Actions execute() method
(or rather MybankBaseActions process() method is by layering.
The functionality in process() method can be divided into three distinctive
steps.
29
1.
2.
3.
Transfer Object Assembly: The next step is creating serializable data transfer
objects (DTO) that are independent of the HttpServletRequest and
HttpServletResponse (and the entire javax.servlet.http package). This involves
copying the ActionForm attributes into a regular serializable JavaBeans. The
formal term used to describe this copying process is Transfer Object Assembly.
The class that assembles the transfer object is called Transfer Object Assembler.
Every tier uses object assemblers when transferring objects across the tier
boundary. In general, the object assemblers used to send data from business tier
to presentation tier have some intelligence. However the object assemblers used
to send data from presentation tier to business tier are straightforward. They are
monotonous and dumb (It better be dumb. Otherwise you are coding
business logic here). You can take advantage of their straightforward nature
and easily develop a framework using Java Reflection API to perform the object
assembly. The framework thus developed takes the ActionForm-to-DTO
mapping information in a XML file and creates the DTOs.
To make life a bit easier, you can offload some of the conversions to
the BeanUtils class in Commons BeanUtils. This jar is packaged along with
Struts. You can use the BeanUtils.copyProperties(dest,
orig)
method to copy the properties with same names between the form bean and the
DTO. It also does the required data type conversions in the process.
Business Logic Invocation: The DTOs thus created are transferred to the business
tier as arguments while invoking the busiess logic methods. Consider how a Loan
Session EJB containing the business logic for loan management is invoked using
93
the standard Service Locator pattern. Service Locator is a core J2EE pattern that
is used widely to locate the business service in this case used to locate the EJB.
LoanMgmt loanmgmt = (LoanMgmt)
ServiceLocator.getInstance().lookup(LoanMgmtEJB);
The Business Delegate is another Core J2EE Pattern and decouples the web
tier from dependencies on the choice of business logic implementation. Typically
business delegate is a class with implementation for all the business
methods. Figure 4.3 shows the Business Delegate class. The client invokes the
methods on business delegate. The delegate, true to its name delegates the client
calls to the actual implementation. It uses the ServiceLocator to lookup the
Service, invoke methods on it and convert the implementation exceptions
into application exceptions thus reducing coupling.
:,
49
The
Perfectly reusable Actions are not a reality yet. Suppose that you have a common
page accessed from two different pages and what the page shows where the page
goes next depends on where you came from. You can never create totally
reusable Actions and chain them in this scenario.
Wiring the handlers
If the web application you are designing is entirely of the format where
you came from drives what to do and where to go next, then consider
using a different approach. Split the current request handling and presenting
next page into two different handler classes. Write atomic piece of dos as
Commands for each. In a separate XML, wire them up together as you would
like. The Action class serves very little purpose here other than to figure out
which handlers are wired together. In fact a single Action for the whole
application suffices. All that this Action does is to look up in the XML for
commands to be executed in a
95
An easy way to achieve this is to track where the user came from in session
and then accordingly act in the common action. This however makes the common
action less reusable. If a lot of your pages behave in this manner you
should consider developing a framework to abstract the complexities of the
transition. A simple approach is illustrated here. Start with an interface with two
methods as shown below:
public interface StateAware {
public String getPreviousState();
public String getNextState();
}
69
if (isCancelled(request)) {
return mapping.findForward(sw.getpreviousState());
}
//do common action here
//success
return mapping.findForward(sw.getNextState());
}
}
(
When the development begins, the struts-config.xml is always small
and manageable. But as time passes and features are added, the file continues to
grow to become a monster. Splitting the application into modules definitely helps,
but modules can be relatively large too. There are better ways to mange the
struts- config.xml than simply editing by hand or even an XML editor.
Some of the popular tools to manage struts-config.xml are described below.
Struts-GUI
Struts-GUI
is
a
Visio
Stencil
from
Alien
Factory
(http://www.alienfactory.co.uk/strutsgui/). It lets you visually edit the struts
config.xml as a Visio diagram and generate the xml file from it. One of the
biggest challenges in maintaining the struts-config.xml is understanding the flow
and tracking down what is going on. With Struts-GUI, you can follow the
visually trace the actions, their forwards and the web pages they lead to. You can
even trace action chaining. You can add documentation in the Visio diagram
reducing the maintenance hurdle even further. Struts-GUI is a commercial tool.
Struts Console
Struts Console is a Swing based editor to manage the struts-config.xml from
James Holmes (http://www.jamesholmes.com/struts/console/). It is not visually
driven as Struts GUI, but very intuitive. It has tree like navigation to traverse the
97
individual elements. It is much more flexible than Struts GUI in that it can
be used to maintain customized struts-config.xml (More about Struts
customization in Chapter 10). Wizards and drop downs are provided to
add inidividual elements, thus eliminating the chances of typo.
XDoclet
XDoclet based management of struts-config.xml is a entirely different concept.
The two tools cited earlier are based on maintaining the struts-config.xml, while
in XDoclet approach, there is no struts-config.xml at all! In the XDoclet
approach, there is no struts-config.xml at all! All the requisite information linked
to the <formbean> and <action> are specified in the Action and Form
classes using special XDoclet tags as follows:
* @struts.action name="custForm" path="/editCustomer"
*
scope="request" validate="false"
parameter="action" input="mainpage"
*
* @struts.actionforward name="showCustForm"
*
path="/ShowCustomerForm.jsp"
The above tags generate the Struts action mapping as follows in the strutsconfig.xml at build time.
<action path="/editCustomer"
type="mybank.app1.ShowCustomerAction"
name="custForm"
scope="request"
input="mainpage"
unknown="false" validate="false">
<forward name="showCustForm"
path="/ShowCustomerForm.jsp"
redirect="false"/>
</action>
89
big picture as you develop. Hence the struts-config.xml serves much like
whiteboarding - visualizing whats going on in its entirety. Providing this
information via piecemeal approach in different files using XDoclet tags defeats
the purpose. Providing this information via piecemeal approach in different files
using XDoclet tags defeats the purpose. Hence our advice is not to use the
XDoclet approach for auto-generating struts-config.xml.
))
3 <
2.
Decide how many forms are involved in the flow. Which comes when and so
on. This will tell you which form should be in request and session scope. (If
possible, try to maintain as many forms in request scope).
3.
4.
5.
Application developer designs the business logic for each page (not the
Action class) and unit tests them and page author develops and formats the
pages. Both tasks can occur in parallel.
6.
7.
Page author and developer integrate the pieces and unit test them with
99
StrutsTestCase (http://strutstestcase.sourceforge.net/).
%
Make the best use of the built-in Actions. Review the systems you build and see
how you can use ForwardAction to stick to MVC, how to
use DispatchAction and LookupDispatchAction to simplify things
and perhaps even internationalize your application. Split your application
into modules and create separate struts config files. Smaller files are easier
to comprehend and manage. Doing so will benefit you in the long run.
Define a base Form and Action in your application. You will be glad you
did. Handle duplicate form submissions using redirects and synchronizer tokens.
Use a tool to manage the Struts Config files and strictly follow the guidelines
about what goes into Action and what does not.
Authors note: Struts Action Forms tend to get really huge in big projects. The
data in Struts Forms need to be transferred to the middle tier and persisted to the
database. This problem of copying (mapping) data from ActionForms
to ValueObjects (which carry to the middle tier) has been traditionally done
by BeanUtils. When the structure of Value Objects and Action Forms
differ significantly, it is tough to use BeanUtils. Object To Object mapping
(OTOM) framework (http://otom.dev.java.net) is designed to solve this
problem. With OTOM, any Java Object can be mapped to another via a GUI.
Then, the mapping Java
code can
be
generated
from the GUI
or Ant Task.
100
101
Chapter 5
Form Validation
In this chapter:
1.
You will learn how to use Commons Validator with Struts via ValidatorForm
and ValidatorActionForm
2.
3.
Getting too close with JavaScript is one extreme and is not pragmatic
in everyday projects. On the other hand, postponing the validation until
it manifests as a business logic exception, runtime exception or a
database exception is unacceptable too. Another option is to
programmatically validate the HTML Form data using helper classes in the
web tier or code the validation right in the validate() method of the
ActionForm itself which is what we did in Chapter 3.
The third option is to externalize the validation into a XML file that confirms
to the Commons Validator syntax and integrate it into Struts. This approach
works very well for trivial checks, which is a case in approximately 50% of
the projects. Examples of trivial checks are: Null Checks - Checking if a field
is null, Number Check checking if the field value is numeric or not, Range
Check checking if a numeric values lies within a range. These validations
depend just on the fields being validated and nothing else.
Validator is a part of the Jakarta Commons project and depends on the
following Commons Projects - BeanUtils, Logging, Collections, Digester and
also on Jakarta ORO library. All of these are shipped with the Struts 1.1. You can
find them in the lib directory of the Struts distribution.
102
The interoperation of Commons Validator and Struts is like a jigsaw puzzle with
several pieces. It is not possible to explain one piece in entirety and move on to
the next since they are all interconnected. Hence our approach is to explain part
of a puzzle before moving on to the next. And then several half-baked pieces are
joined together. You might have read through this section twice to get a
clear picture.
The twin XML files
In Struts, the XML based validations are located in two files validationrules.xml and validation.xml. The validation-rules.xml file contains the global set
of rules that are ready to use (Henceforth referred to as global rules file).
It is shipped along with the Struts distribution in the lib directory. The second
file validation.xml is application specific. It associates the rules from the global
rules file with individual fields of your ActionForm. Suppose there is a
generic rule named required in the global rules file that checks if a field is
empty. You can use this rule to check if any field is empty including the
firstName field in the CustomerForm by adding the following declaration in
validation.xml:
<form name="CustomerForm">
<field property="firstName"
depends="required">
..
..
</field>
<field .. ..
..
</field>
..
</form>
The above xml contains a XML block with a <form> element, which stands
for an ActionForm named CustomerForm. All the rules associations for the
CustomerForm fields exist inside this <form> block. One such validation the
validation for the firstName field is also shown in a <field> element.
The
<field> has an attribute named depends that lists the set of rules (comma
separated) on which the field is dependent upon. In other words, the
103
The
basic
validator
class
in
Commons
Validator
is
104
depends="required"
msg="errors.minlength">
</validator>
105
Then, the replacement value for {0} is First Name. This value is used
to replace {0} and the resulting error message is First Name is required. Notice
that the Resource bundle is looked up twice once using the arg0 key and
then during the rendering of the ActionError itself.
You might be wondering why arg1 is needed. The answer is when the
minlength rule fails; it looks for an error message with a predefined key called
errors.minlength. The errors.minlength requires two replacement
values arg0 and arg1. arg0 was also used by the errors.required key.
The errors.minlength needs arg1 in addition to arg0. I can hear you are
saying All that is fine. But how will I know what predefined error keys should
106
be added to the resource bundle. It is simple actually. Just open the validationrules.xml and you will find all the error message keys are provided. They are:
errors.required={0} is required.
errors.minlength={0} can not be less than {1} characters.
errors.maxlength={0} can not be greater than {1} characters.
errors.invalid={0} is invalid.
errors.byte={0} must be a byte.
errors.short={0} must be a short.
errors.integer={0} must be an integer.
errors.long={0} must be a long.
errors.float={0} must be a float.
errors.double={0} must be a double.
errors.date={0} is not a date.
errors.range={0} is not in the range {1} through {2}.
errors.creditcard={0} is an invalid credit card number.
errors.email={0} is an invalid email address.
As you can see, every error message key needs arg0. The
errors.minlength, errors.maxlength and errors.range need arg1. In
addition, the errors.range also needs arg2.
In Listing 5.2, the arg1 has an attribute called resource and it set to
false. The resource=false implies that there is no need to lookup
the message resource bundle for arg1 (as was done with arg0 key
customerform.firstname).
More validation.xml features
Let us investigate some more interesting validator features. Listing 5.3 shows the
same CustomerForm validation rules with some additions and modifications.
Those are highlighted in bold.
The first addition is the <global> block to <formvalidation>. The
<global> can hold as many <constant>s. A <constant> is much like a Java
constant. Declare it once and use wherever needed. In this case, a constant called
nameMask is declared and a regular expression ^[AZaz]*$ is assigned to it.
This regular expression is interpreted as: The field can have any number
of characters as long as each of them is between A-Z and a-z. This constant is
used to define the mask rule for CustomerForm in two steps as follows:
1.
First, a variable <var> called mask is created and the value of nameMask is
assigned to it. This is done by setting the <varvalue> to be
${nameMask}. [Any variable within the ${ and } blocks is evaluated. You
will find the same convention in JSTL too.] The <var> scope is limited to
107
<global>
<constant>
<constantname>nameMask</constantname>
<constantvalue>^[AZaz]*$</constantvalue>
</constant>
</global>
<formset>
<form name="CustomerForm">
<field property="firstName"
depends="required,minlength,mask">
<arg0 key="customerform.firstname"/>
<arg1 name="len" key="${var:minlen}"
resource="false"/>
<var>
<varname>minlen</varname>
<varvalue>1</varvalue>
</var>
<var>
<varname>mask</varname>
<varvalue>${nameMask}</varvalue>
</var>
</field>
</form>
</formset>
</formvalidation>
The second new feature in Listing 5.3 is the use of variable for arg1. arg1
as you know, represents the minimum length of the first name. In Listing 5.2, the
arg1 key was hard coded. A bit of flexibility is added this time round by
108
109
Add the corresponding <form> element with <field> sub-element for every
form field that needs validation.
3.
4.
For every rule, add the error message with predefined name to the message
bundle.
5.
For every rule, supply the argNs either as inline keys or keys to the resource
bundle.
6.
If the rules in validation-rules.xml do not meet your needs, add new rules and
follow the steps above for the new rules. Be sure to have the classes
executing the rules are available in the appropriate class path.
3%
"
'
3%
"
Struts 1.0 mandated that every HTML form in the JSPs have an associated
ActionForm. Struts 1.1 changed all that with the introduction of
DynaActionForm dynamic ActionForm as the name suggests.
DynaActionForm is defined in the struts-config.xml as a form-bean. A sample
DynaActionForm is shown in Listing 5.4.
Listing 5.4 Sample DynaActionForm
<formbean
name="CustomerForm"
type="org.apache.struts.action.DynaActionForm">
110
1.
2.
A regular ActionForm is developed in Java and declared in the strutsconfig.xml. The JavaBeans properties of a regular ActionForm are created by
first defining the instance variable and then adding a getter and setter for that
instance variable. A DynaActionForm has no associated Java class. Its
JavaBeans properties are created by adding the <formproperty> tag in
Struts Config file (and also declaring its Java type). In Listing
5.4, CustomerForm is declared as a DynaActionForm with two
JavaBeans properties firstName and lastName. The type attribute of the
<form property> is the fully qualified Java class name for that
JavaBeans property; it cannot be a primitive. For instance int is not
allowed. Instead you should use java.lang.Integer. You can also
initialize the form- property, so that the html form shows up with an initial
value.
111
The page author constantly pesters the Java application developer to modify
the ActionForm.
2.
While the former hampers the developer productivity, the latter leads to
overlap of responsibilities and headaches. Both options are not ideal. Struts 1.1
has solved this problem by introducing DynaActionForm. Although originally
designed for developers ease of use, it has been serving the purpose of
role separation in a project very well. One can envision an ideal project
development as follows.
A page author can be isolated from the Java application development by
having a application server environment available for page design. He develops
112
the JSPs as JSPs using the Struts (and other) custom tags, not just HTML
prototypes. He also creates DynaActionForms using XML instead of relying
on the application developer to create the Java ActionForms. In other words,
the page author is isolated from the nitty-grittys of the build, deploy and all that
chaos accompanying it at least in the prototype phase.
The page author designs the page Navigation as plain forwards instead
of Form submissions; In other words he uses <html:link> to
prototype navigation instead of <html:submit>s. In case you are
wondering why anybody would go this route, here is the answer: In Struts
framework, the presentation logic resides in the Action classes. It is highly
unlikely that the presentation logic (Action) for the ActionForm will be
ready even before the prototype is ready. Hence the page author uses
the <html:link> and ForwardAction to model the navigation. Once the
prototype is approved, the application developer works on the presentation logic
by developing the Action classes.
When
doing
so,
the
application
developer
creates
equivalent ActionForms
for
the
existing
DynaActionForms, one form at a time. The application developer also
replaces the forwards in the JSP with form submissions and adds the
glue code in Action classes to handle the form submissions.
Okay, so DynaActionForms are great, why replace them with ActionForms
anyway? In my opinion, DynaActionForms are good only in the prototyping
stage. Once past that stage, it is always better to have strongly
typed ActionForms. Here are some more downsides of using DynaActionForms
1. The DynaActionForm bloats up the Struts config file with the xml
based definition. This gets annoying as the Struts Config file grow larger.
2.
3.
4.
5.
6.
113
setters in the IDE than fixing your Action code and redeploying your
web application)
That said, DynaActionForms have an important role to play in the
project lifecycle as described earlier, which they do best and let us limit them to
just that. Use them with caution, only when you absolutely need them.
DynaValidatorForm
An application specific form can take advantage of XML based validation
by virtue of sub classing the ValidatorForm. The XML based dynamic forms
can also avail this feature by specifying the type of the form
to be DynaValidatorForm as follows:
<formbean
name="CustomerForm"
type="org.apache.struts.validator.DynaValidatorForm">
When large amount of data is collected from the user, it is customary to split the
form into multiple pages. The pages follow a wizard like fashion. However the
ActionForm would still exists as a single Java class. Moreover at any point, the
data validation should be limited to only those pages that have been submitted.
Fortunately, this feature is already built into the Validator. However it requires
some setup from your side. There are two alternatives the first uses a
single action mapping and the second uses multiple action mappings. The
struts- validator.war provided with the Struts distribution adopts the first
approach, while we recommend the latter.
Both approaches require the use of an optional hidden variable called page.
Consider an html form split into two JSPs PageA.jsp and PageB.jsp. Since both
JSPs will have the hidden variable mentioned earlier, it is sent as a request
parameter from both form submissions. The hidden variable is assigned the value
of 1 in PageA and 2 in PageB. The ValidatorForm already has a JavaBeans
property named page of type int. All validation for any field on a page less than
114
or equal to the current page is performed on the server side. This will of course
require that each rule defined for the field in the validation.xml should have
a page attribute as follows:
<form name="CustomerForm">
<field property="firstName" page=1
depends="required">
<arg0 key="customerform.firstname"/>
</field>
<field property="fieldX" page=2
depends="required">
<arg0 key="customerform.fieldX"/>
</field>
</form>
With this background, we will first explain the single action mapping
approach. The html forms in both pages have the same action <html:form action=/submitForm>.
In the struts config file, set validate=false for the /submitForm action
mapping and add forwards for each of the pages as follows:
<action
path="/submitForm"
type="mybank.example.CustomerAction"
name="CustomerForm"
scope="request"
validate="false">
<forward name="success"
path="/Success.jsp"/>
<forward name="cancel"
path="/Cancelled.jsp"/>
<forward name="input1"
<forward name="input2"
path="/PageA.jsp"/>
path="/PageB.jsp"/>
</action>
Since validate is set to false, the execute() method in Action gets control
immediately after the RequestProcessor populates the form. You have to now
explicitly call the form.validate() in the execute() method (Since the
CustomerForm extends from ValidatorForm, the validate() is already
implemented). After that you have to forward to the appropriate page depending
on the current page and whether there are ActionErrors in the current page. For
instance, if PageA is submitted and there are no ActionErrors, then PageB
is displayed to the user. However if there were ActionErrors in PageA, then it
is displayed back to the user. The code is shown below.
public ActionForward execute(.. ..) throws Exception {
CustomerForm info = (CustomerForm)form;
115
path="/submitPageA"
type="mybank.example.CustomerAction"
name="CustomerForm"
scope="request"
validate="true"
input=/PageA.jsp>
<forward name="success" path="/PageB.jsp"/>
<forward name="cancel"
path="/Cancelled.jsp"/>
</action>
116
path="/submitPageB"
<action
type="mybank.example.CustomerAction"
name="CustomerForm"
scope="request"
validate="true"
input=/PageB.jsp>
<forward name="success" path="/Success.jsp"/>
<forward name="cancel"
path="/Cancelled.jsp"/>
</action>
Both action mappings define an input value. When the form is validated by
the RequestProcessor and there are errors, the mapping.getInput() page
is shown to the user. Similarly the mapping.findForward(success) page
is shown when there are no ActionErrors. Any business logic invocation
happens only after the PageB data is collected. The code below shows the
execute() method.
public ActionForward execute(.. ..) throws Exception {
CustomerForm info = (CustomerForm)form;
// Was this transaction cancelled?
if (isCancelled(request)) {
// Add code here to remove Form Bean from appropriate scope
return (mapping.findForward("cancel"));
}
if (info.getPage() == 2) {
//Data collection completed. Invoke Business Logic here
}
return mapping.findForward("success");
}
#
There
%
are
still
two
more
validation
related
Form
classes
117
will resolve some of the confusion arising out of plethora of Form classes. Figure
5.1 shows the relationship between these classes. ActionForm and
DynaActionForm reside at the top of the figure as the root class for two
branches. ValidatorForm and DynaValidatorForm are their immediate
siblings. Each of them has a subclass ValidatorActionForm and
DynaValidatorActionForm.
The last two classes deserve some
explanation. Suppose that you have a Form and want to reuse it in various
scenarios. Each scenario has its own validation. However with the XML based
validation, a set of rules are associated with the form name, not where it is
invoked
from.
Both
the
ValidatorActionForm
and
DynaValidatorActionForm match the action mapping instead of the form
name. The name attribute is used to match the action mapping and thus multiple
rules can be defined for the same form based on the action mapping.
##
In this chapter, you learnt about using Commons Validator with Struts this is
probably the approach you will adopt in your project too. You also understood
the importance of DynaActionForm and its role in projects. You also learnt the
best approach to handle validation in multi page forms.
118
119
Chapter 6
Struts Tag Libraries
In this chapter:
1. You will learn about frequently used Html, Bean and Logic tags
2. We will customize these Html tags base, text, checkbox, errors & image
3. You will learn about JSTL and Expression Language
4. You will understand how to use Struts-EL Tags and which of the
Struts tags should be replaced with JSTL and Struts-EL
5. You will see how various Struts tags, their derivatives and other
related tags can work together to create multi-page lists and editable lists.
Custom Tags were introduced in JSP 1.1 specification. They are
elegant replacement for the scriptlets. Without the custom tags, the edge of the
system where the decisions in presentation logic based on middle tier models
would be exposed to the JSP page author as Java scriptlets. While not only
causing confusions and headaches to the page author, scriptlets also
required the involvement of the Java developer in the page authoring. Custom
Tags changed all that. The application developer now provides the custom tags
written as a Java class with a pre-defined structure and hierarchy. The page
author independently designs the pages and decides on the contents using the
custom tags and their formatting using general HTML and CSS.
Struts ships with these Tag libraries Html, Bean, Logic, Template, Nested,
Tiles. We will deal with the first three tag libraries in this chapter. The TLD file
for each of these libraries is included in the Struts distribution. For instance, the
Html Tags are defined in struts-html.tld. The Bean tags are defined in
struts- bean.tld and so on. These tags are like any other custom tags. You
have to include the TLD declarations in the web.xml and also the JSP. For e.g.,
you have to add the following lines in the web.xml to use the Html Tag library:
<taglib>
<tagliburi>/WEBINF/strutshtml.tld</tagliburi>
<tagliblocation>/WEBINF/strutshtml.tld</tagliblocation>
</taglib>
120
Struts HTML tags are useful for generating HTML markup. The
tag library defines tags for generating HTML forms, textboxes,
drop downs, radio buttons, submit buttons and so on. You
used some of these in Chapter 3. We will look at other important
covered there.
Struts HTML
check boxes,
have already
html tags not
If the JSP is moved from one folder to another (which is not uncommon),
121
every URL in the page should be inspected and changed if needed. Not
a great idea.
3.
4.
The solution is to modify the Base Tag itself so that the output is:
<base href=http://localhost:8080/App1 />
Now, the URL of the image is always a constant no matter which JSP it is
used in. Another advantage of this arrangement is that a directory named App1
can be created on the web server to contain the static resources and the images
with no impact on the image URLs. With this background let us get started on
modifying the BaseTag.
Consider a URL http://localhost:8080/App1/cust/CustomerDetail.jsp. This is
generated as the output of the BaseTag. It can be dissected into:
122
request.getScheme() (http://),
request.getServerName() (localhost),
request.getServerPort() (8080) and
request.getRequestURI() (App1/customer/CustomerDetails.jsp).
The desired output for the BaseTag is http://localhost:8080/App1. This can be
dissected into
request.getScheme() (http://),
request.getServerName() (localhost),
request.getServerPort() (8080) and
request.getContextPath() (App1).
There you go! This is what we want to output from our version of BaseTag. Let
us call this MyBaseTag. Listing 6.1 shows doStartTag() method from
MyBaseTag.
Form Tag
Another Tag that deserves extra attention is the FormTag. You have learnt about
the working of this tag in Chapter 2 and used it in Chapter 3. At that point, we
looked at only one attribute of this tag the action attribute.
It also has a set of attributes based on JavaScript events. For instance,
the onreset and onsubmit attributes do exactly what their JavaScript
equivalents do; they invoke the corresponding JavaScript event handler
functions. The JavaScript event based attributes is not limited to just the
FormTag. In fact all the tags in HTML Tag library have similar features.
Another attribute of interest is the enctype. Normally you dont have to set
the enctype. When you are uploading files however, the value of enctype
should be set to multipart/formdata. More details await you in the section
on FileTag.
FileTag
FileTag lets you select file to upload from the HTML page. When you
are uploading files, the value of enctype (on the FormTag) should be set
to multipart/formdata. The FileTag in its simplest format, generates
an output of <input type=file name=xyz valueabc />. This
results in the rendering of a text field for entering the file name and a Browse
button as shown in the figure below.
On clicking the browse button a file selection dialog box appears. The
selected file is uploaded when the form is submitted. In the JSP, the FileTag is
used as <html:file property=uploadFile/>. The uploadFile is a
123
JavaBeans property in the ActionForm. Struts mandates the type of this property
to be org.apache.struts.upload.FormFile. FormFile is an interface
with methods to get the InputStream for the uploaded file. For more
details refer to the example web application named struts-upload.war in the
webapps directory of wherever you installed Struts.
Smart Checkbox The state aware checkbox
Consider a HTML form containing a checkbox in a JSP as follows:
<html:form action="/submitCustomerForm">
<html:text property="firstName" />
<html:checkbox property="agree" />
<html:submit>Submit</html:submit>
</html:form>
In addition to the usual text field, it has a checkbox that Customer checks
to indicate he agrees with the terms and conditions. Assume that the
associated ActionForm has validate() method checking if the firstName is
not null. If the first name is not present, then the user gets the same page back
with the error displayed. The user can then submit the form again by
correcting the errors. Further assume that the associated ActionForm is stored in
session scope. Now starts the fun.
1. First, submit the form by checking the checkbox but leaving the firstName
blank. The form submission request looks like this:
http://localhost:8080/App1/submitCustomer.do?
firstName=&agree=true
The ActionForm is created in the session with blank firstName and agree
attribute set to true (Checkbox is mapped to Boolean attributes in
ActionForm).
2. Since the firstName is blank, the user gets the same page back. Now fill in
the firstName but uncheck the agree checkbox. The form submission
request looks like this: http://localhost:8080/App1/submitCustomer.do?
firstName=John Note that the agree request parameter is missing. This
is nothing unusual. According to the HTTP specification, if a checkbox is
unchecked, then it is not submitted as request parameter. However since the
ActionForm is stored in the Session scope, we have landed in a problem.
In our case, Struts retrieves the ActionForm from Session and sets the
firstName to John. Now the ActionForm has the firstName=John and
agree=true, although you intended to set the agree to be false.
124
The Smart Checkbox we are about to present is the solution to this problem. This
solution uses JavaScript and it works as expected only if your target
audience enables JavaScript in their browser. The solution is as follows:
Define the ActionForm as usual with the Boolean property for checkbox.
Define a new class SmartCheckboxTag by extending the CheckboxTag
in org.apache.struts.taglib.html package and override the
doStartTag(). In the doStartTag(), do the following:
Render a checkbox with name agreeProxy, where agree is the name
of the boolean property in ActionForm.
Render a hidden field with the name agree.
Define an inline JavaScript within the <script> block as
follows. Substitute appropriate values into [property] and [formName].
<script>
function handle" + [property] + "Click(obj) {
if ( obj.checked == true) {
document.form.[formName]."
+ [property] + ".value = 'true';
} else {
document.form.[formName]."
+ [property] + ".value = 'false';
}
}
</script>
Invoke the above JavaScript function for the onclick event of the
checkbox.
The crux of this solution is to invoke a JavaScript function on clicking
(check or uncheck) the checkbox to appropriately set the value of a hidden field.
The hidden field is then mapped to the actual property in ActionForm. If you can
ensure that your target audience has JavaScript enabled, this solution works like a
charm!
Many might classify this solution as a hack, but the truth is there is no
elegant solution for this problem. Where applicable and feasible you can adopt
this solution. If you are unsure about your target audience or deploying the
application into the public domain, never use this solution. It is impossible
to predict the environment and behavior of an Internet user.
125
There are many ways to implement this. One simple way is to extend the
TextTag
class and override the doStartTag() method. The
doStartTag() from the Struts TextTag generates the <input type=text
name=.. >. The subclass of the Struts TextTag has to then add an
image next to it when
126
there is ActionError(s) associated with the input field. Listing 6.2 shows the
implementation with the above approach. The new tag called MyTextTag is used
in the JSP as follows:
<mytags:mytext property=. errorImageKey=img.error.alert />
The errorImageKey is the key to get the name of the error image from
the resource bundle. In the doStartTag() method, a check is performed if the
text field has any associated errors. If there are no errors, no extra processing is
done. However if there are errors, the errorImageKey is used to retrieve the
image source and a <img src= > markup is constructed alongside the
text tag. There are other ways of implementing this feature. One of them is to
develop a separate custom tag to generate the error indicator.
Listing 6.2 TextTag with built-in error indicator
public class MyTextTag extends TextTag {
private String errorImageKey;
public int doStartTag() throws JspException {
int returnValue = super.doStartTag();
ActionErrors errors = RequestUtils.getActionErrors(
pageContext, this.property);
if ((errors != null) && ! errors.isEmpty()) {
String imageSrc = RequestUtils.message(pageContext,
getBundle(),
getLocale(),
this.errorImageKey);
if (imageSrc != null) {
StringBuffer imageResults = new StringBuffer();
imageResults.append("<img src=\"");
imageResults.append(imageSrc);
imageResults.append("\"");
// Print the image to the output writer
ResponseUtils.write(pageContext,
imageResults.toString());
}
}
return returnValue;
}
...
127
...
public void release() {
super.release();
errorImageKey = null;
}
}
Second, create your own Errors tag by extending the ErrorsTag from
Struts. This JavaScript function is invoked repetitively from the
ErrorsTags doStartTag() method for every ActionError
in
ActionErrors. Listing
6.4 shows the doStartTag() method for the MyErrorsTag. As usual the
method first invokes the super.doStartTag() to write the ActionErrors as
locale specific error messages to the output stream. It then invokes the JavaScript
function addActionError() inline with the rest of HTML for every
128
129
results.append("addActionError(window,\"" +
formFieldName + "\",\"" +
message + "\");\n");
}
}
results.append("</script>");
ResponseUtils.write(pageContext, results.toString());
return returnValue;
}
...
}
aggregateErrMsg = aggregateErrMsg +
window.errorMessageArray[formFieldName][i];
}
alert(aggregateErrMsg);
}
If you are wondering why you need a Struts tag for such a simple HTML tag,
consider this. Sometimes, the images actually spell out the actual English word.
Users worldwide access your application. You want to localize the images
displayed to them. You also want the alt text on your images to be
internationalized. How do you do this without adding the big and ugly ifelse block in your JSPs? The answer is to use the ImgTag. With ImgTag, the
actual image (src) and the alternate text (alt) can be picked from the
Message Resources. You can easily setup different Resource Bundles for different
Locales and there you have it. Your images are internationalized without any extra
effort. Even if you are not internationalizing the effort is well worth it. JSPs can
remain untouched when the image is changed. The usage of the ImgTag is as
follows:
<html:img srcKey=image.main altKey=image.main.alttext />
There are many more attributes in ImgTag and you can find them in the
Struts documentation.
130
"
All along, you have submitted HTML forms with the grey lackluster
buttons. Life requires a bit more color and these days most of the web sites use
images for Form submission. The images add aesthetic feeling to the page
well. Struts provides <html:image> tag for Image based Form submission.
Although the ImageTag belongs to the HTML Tag library, it requires an indepth treatment and deserves a section by itself. Let us look at the ImageTag and
how it fits into the scheme of things. Consider an HtmlTag used in a JSP as
follows:
<html:image src=images/createButton.gif
property=createButton />
name=createButton
src=images/createButton.gif />.
When the Form is submitted by clicking on the image, the name is added to
the X and Y coordinates and sent to the server. In this case, two request
parameters createButton.x and createButton.y are sent. Suppose that the
HTML form has two or more images each with a different name. How do you
capture this information in the ActionForm and convey it to the Action?
The answer to this is ImageButtonBean in org.apache.struts.util
package. The ImageButtonBean has five methods getX(), setX(),
getY(), setY() and isSelected(). All you have to do is add JavaBeans
property of type ImageButtonBean to the ActionForm (Listing 6.6). For
instance, if the JSP has image buttons named and createButton and
updateButton, you have to add two ImageButtonBean properties to the
ActionForm with the same name. When the createButton image is clicked,
two request parameters createButton.x and createButton.y are sent to the
server. Struts interprets the dot separated names as nested JavaBeans
properties. For example, the
property reference:
<.. property=address.city/>
is translated into
getAddress().getCity()
while getting the property. The setters are called for setting the property as
follows:
getAddress().setCity()
For
createButton.x
and
createButton.y,
Struts
invokes
getCreateButton() on the ActionForm and then setX() and setY() on
131
}
Listing 6.6 CustomerForm using ImageButtonBean
public class CustomerForm extends ActionForm {
private String firstName;
..
..
private ImageButtonBean createButton;
private ImageButtonBean updateButton;
public CustomerForm() {
firstName = ;
lastName = ;
createButton = new ImageButtonBean();
updateButton = new ImageButtonBean();
}
132
return updateButton;
}
Compare this with the Action using grey buttons. It would look like:
custForm.getCreateButton().equals(Create). Obviously, changing
the grey button to image button on the JSP is not gone unnoticed in the Action.
The Action class has changed accordingly. The ActionForm has changed
too. Previously a String held on to the submit buttons name. Now
an ImageButtonBean has taken its place. You might be wondering if it is
possible to eliminate this coupling between the Action and the JSP? The good
news is that this can be achieved quite easily. Listing 6.7 shows HtmlButton
that extends the
ImageButtonBean,
but
overrides
the
isSelected()
method. ImageButtonBean has basically taken care of
handling the image button in isSelected() method. The extra functionality
in HtmlButton takes care of grey button submission. The attribute called name
is the name of the grey button submitting the form. The isSelected() method
now checks if the name is not null in addition to invoking the
super.isSelected(). Now you can use the HtmlButton for whatever
mode of JSP submission grey button or image button. The ActionForm
will use HtmlButton in both cases and never change when the JSP changes.
Neither does the Action change. Decoupling Nirvana indeed!
The Image button in JSP will look like:
<html:image property=createButton
src=images/createButton.gif />
133
The alt text and the image source for ImageTag can also be externalized into
the Message Resource Bundle much like the ImgTag. As it turns out the names
of the attribute for externalizing these are also the same. <html:image>
has srcKey to externalize the name of the image src and altKey to externalize
the alternate text (alt). In Chapter 10, we will develop a DispatchActionlike capability for HtmlButton by exploiting the Struts customization facility.
ImageButton and JavaScript
ImageButton is all cool and dandy as long as you dont have to execute
134
were able to assign methods in Action instance based on the button names in a
locale independent manner. The only pre-requisite was that, all the form
submission buttons have the same name. With the HtmlButton (or
ImageButtonBean for that matter), we started with different names for
different buttons from the outset. For this reason, DispatchAction and
LookupDispatchAction cannot be used in conjunction with image based
form submissions. They can be however used with html links using images.
Struts Bean tag library contains tags to access JavaBeans and resource bundles
among others. Two frequently used tags are MessageTag (bean:message) and
WriteTag (bean:write).
Message Tag and Multiple Resource Bundles
You have already used the message Tag for accessing externalized messages in
resource bundles using locale independent keys. In this section, we will go
further and investigate the applicability of multiple resource bundles. When the
application is small, a single resource bundle suffices. When the application gets
larger, the single properties file gets cluttered much like a single
struts- config.xml getting cluttered.
It is advisable to have multiple resource bundles based on the message category
from the outset. This saves the pain involved in splitting the single bundle into
pieces and updating all the resources accessing it.
The first step in using multiple resource bundles is to declare them in the strutsconfig.xml first. The semantic for declaring multiple resource bundle is as
follows:
<messageresources parameter="mybank.example.DefaultMsgResource"
null="false"/>
<messageresources parameter="mybank.example.AltMsgResource"
null="false" key="bundle.alt" />
<messageresources parameter="mybank.example.ErrorMsgResource"
null="false" key="bundle.error" />
The above snippet declares three resource bundles identified by a key. The
default resource bundle does not have a key. As the key suggests,
the AltMsgResource contains alternate messages and the ErrorMsgResource
contains error messages. The message tag accesses the default resource bundle as
follows:
<bean:message key=msg.key />
135
The key specified in the <bean:message> tag is the key used in the
properties file to identify the message.
The non-default resource bundles are accessed by specifying the bundle key
as declared in struts-config.xml (key=bundle.alt, key=bundle.error
etc.). For instance, a message tag accesses a message in AltMsgResource as
follows:
<bean:message key=msg.key bundle=bundle.alt />
You can also specify alternate bundles to the following tags in HTML Tag
library messages, image, img and option.
Write Tag
Write Tag is another frequently used tag. The usage of this tag is as follows:
<bean:write name=customer property=firstName />
It accesses the bean named customer in the page, request, session and
application scopes in that order (unless a scope attribute is specified) and then
retrieves the property named firstName and renders it to the current JspWriter. If
format attribute is specified, the value is formatted accordingly. The format can
be externalized to the resource bundle by using the formatKey attribute instead.
Alternate resource bundle can also be specified. This is handy when the display
format is locale specific.
Going further, <c:out> and other JSTL formatting tags are preferred over
write tag for formatting and output.
The frequently used tags in the Logic tag library are for logical comparison
between values and for iteration over collection. The important logical
comparison tags are: equal, notEqual, greaterEqual, greaterThan,
lessEqual and lessThan. The following are the important attributes are
common to these tags.
value The constant value against which comparison is made.
136
The above tag searches for a bean named customer in the 4 scopes and
checks if its firstName property is equal to John. You can also specify
the scope to restrict the search scope by using the scope attribute on these tags.
Another tag attribute is parameter. You have to specify only
one: parameter or (name and property). As the name suggests, the
parameter attribute looks for the specified request parameter and compares
it with the value attribute. In the example below, the request parameter named
firstName is compared with a value of John.
<logic:equal parameter=firstName value=John>
//do whatever when the request parameter firstName
//is equal to John
</logic:equal>
137
The c in the c:if stands for JSTLs core tag library TLD. There are other
tag libraries in JSTL such as formatting. Refer to the section A crash course on
JSTL for details.
Iterate Tag
The iterate tag is used to iterate over a collection (or a bean containing collection)
in any of the four scopes (page, request, session and application) and execute the
body content for every element in the collection. For instance, the following tag
iterates over the collection named customers.
<logic:iterate name=customers>
//execute for every element in the collection
</logic:iterate>
Another alternative is to use a bean and iterate over its property identified by the
attribute property. The following tag accesses the company bean from one of
the scope and then invokes getCustomers() on it to retrieves a collection and
iterates over it.
138
$#
139
Request
Parameters
Request headers
Cookies
Initialization
Parameters
Description
PageContext for the current page
requestScope
sessionScope
ApplicationScope
Param
ParamValues
Header
HeaderValues
Cookie
InitParams
140
NOTE: JSTL 1.0 works with JSP 1.2 containers only, such as Tomcat 4.x.
JSTL 1.1 works only with JSP 2.0 containers such as Tomcat 5.x. With JSP 1.2,
the expression language can be used only within JSTL tags. JSP 2.0 specification
defines a portable expression language. With JSP 2.0, the expression
language will become part of the specification and can be used even outside the
JSTL.
You have already seen an example of using JSTL Core library earlier in
conjunction with EL. Now, let us look at an example of formatting library tags.
Consider the case when you want to display the currency 12.37 in the
users Locale. You can use the formatNumber tag for this purpose. In the
following example the currency is formatted and displayed in a variable
called money. For the U.S. Locale, money will contain the value $12.37.
<fmt:formatNumber value="12.367" type="currency" var="money"/>
In JSTL, the Resource Bundle for the above tag can be specified in a number of
ways. Unless specified otherwise, JSTL looks for a servlet context parameter
named javax.servlet.jsp.jstl.fmt.localizationContext and uses
its value as the bundle name. You can also use the tag <fmt:setBundle
baseName=mybank.MyMessages> in JSP and the rest of the JSP uses
the specified bundle. You can scope a bundle by wrapping other tags
with
<fmt:bundle> tag as follows:
<fmt:bundle baseName=mybank.MySecondMessages>
<fmt:message key="firstName">
<fmt:message key="lastName">
</fmt:bundle>
141
$$
>-
This assumes that stringVar exists as a JSP scripting variable. This tag can
be rewritten with the Struts-EL version of the message tag as follows:
<beanel:message key=${stringVar} />
Although, not much exciting is going on in the above tag, it shows how easy
it is to port the existing Struts tags to Struts-EL. The real power of StrutsEL comes to the fore especially when the scriptlet deciding the attribute value
starts becoming complex.
Not all tags from Struts are ported to Struts-EL. In areas where there is
already a JSTL tag available, porting of the Struts tags will only
cause
142
redundancy. Hence those Struts tags are not ported. For e.g., the bean:write
tag can be implemented with the c:out JSTL tag. Similarly most of the
logic tags (such as equal, notEqual, lessThan etc.) are not ported since the
JSTL tag c:if can take any expression and evaluate it (with the test=$
{.} option). You have already seen how a logic:equal tag can be
replaced with c:if in the earlier section on Nested Logic Tags.
Struts-EL hands-on
Enough theory. Lets get down to business and use some Struts-EL tags to get the
feel. Here is the step-by-step process to do so.
You will need new jar files to use the Struts-EL in your application. Copy
the following jars from the Struts contrib folder into the WEB-INF/lib folder
of the web application jstl.jar, standard.jar (remember to use the Jakarta
Taglibs version, not the Sun reference implementation jar), struts-el.jar.
These jars are needed in addition to the already existing jars from
regular Struts.
From the Struts-EL/lib folder copy the following tlds to the WEB-INF of
your web application c.tld, struts-bean-el.tld, struts-html-el.tld and strutslogic-el.tld.
Add the <taglib> declaration for all the new tlds in web.xml as follows:
<taglib>
<tagliburi>/WEBINF/strutsbeanel</tagliburi>
<tagliblocation>/WEBINF/strutsbeanel.tld</tagliblocation>
</taglib>
<taglib>
<tagliburi>/WEBINF/strutshtmlel</tagliburi>
<tagliblocation>/WEBINF/strutshtmlel.tld</tagliblocation>
</taglib>
<taglib>
<tagliburi>/WEBINF/strutslogicel</tagliburi>
<tagliblocation>/WEBINF/strutslogicel.tld</tagliblocation>
</taglib>
<taglib>
<tagliburi>/WEBINF/c</tagliburi>
<tagliblocation>/WEBINF/c.tld</tagliblocation>
</taglib>
143
Thats it! Now you are ready to use the Struts-EL tags in conjunction with
JSTL tags to reap the benefits of expression language and make your applications
a little bit simpler and cleaner.
Practical uses for Struts-EL
When was the last time you wrestled to use a custom tag as the attribute value of
another tag and failed? Something like this:
<html:radio name=anotherbean
value=<bean:write name=mybean property=myattrib/> />
Nesting custom tag within a tag element is illegal by taglib standards. The
alternatives are no good. Thankfully now, with JSTL, you can solve this problem
in a clean way. In Struts tags, JSTL can be combined only with Struts-EL and the
problem can be solved as follows:
<htmlel:radio name=anotherbean value=${mybean.myattrib} />
Beautiful isnt it! Struts-EL provides you the best of both worlds, the
elegance of JSTL and the power of Struts.
$4-
"
All along you have seen how to handle regular Forms. Now let us see how
to handle list-based forms. List based forms are used for editing collections
of objects. Examples include weekly hours-of-operation, contacts etc.
Such collections may be limited to a single page or span across multiple
pages. We will deal with a collection limited to single page first. Techniques
for dealing with multi page lists are illustrated later.
144
Figure 6.1 Current and Future page layout for the banking application
145
hourOfOperationList.add(new HourOfOperation(6));
}
In Listing 6.9, the JSP displays a form containing the company name and the
hours of operation List. The <logic:iterate> is used inside
the
<html:form> tag to iterate over the hoursOfOperationList property in the
ListForm bean. Each hour of operation is exposed as a scripting variable named
timing. You may be able to relate now between the getTiming() method in
the ListForm and this scripting variable. The indexed=true setting on each of
the html tags makes the array index to be part of the text field name. For instance,
the following tag
<html:text name="timing" property="openingTime" indexed="true"/>
146
<BR>
<html:submit>Save</html:submit>
<html:cancel>Cancel</html:cancel>
</html:form>
Notice the relation between the Struts text tag and the generated input tag.
Each text field now has a unique name as the name is partly driven the
array index. This magic was done indexed=true setting. When the form is
edited and is submitted via POST, the request parameter names are
unique
(timing[0].openingTime, timing[1].openingTime etc.), thanks to the
array index being part of the text field names. The HTML is shown in Listing
6.10.
Upon form submission, when Struts sees the request parameter named
timing[1].openingTime, it calls the following method:
listForm.getTiming(1).setOpeningTime(...)
147
$5
<
148
1.
2.
displayTag (http://displaytag.sourceforge.net/)
3.
HtmlTable (http://sourceforge.net/projects/htmltable/)
Pager Taglib
Pager Taglib covers the display aspects of list traversal very well. Provide it the
minimal information such as rows per page and URL and it will control the entire
paging logic. You are in complete control of the iterating logic and table display.
(If using the IterateTag, offset and length attributes are not needed). Hence you
can completely customize the look and feel of the table using CSS. The
Pager taglib does not provide any assistance for the table display. Neither does it
handle editable list forms, sorting or grouping. If all you need is an easy and
elegant way to traverse list data, you should definitely consider using the Pager
taglib and you will be glad you did. Below we cover a short note on how to use the
Pager Taglib with Struts.
Start with an Action that creates the collection to iterate and put it in
HttpSession using results as the key name. Then forward to the JSP that uses
the Pager taglib. This JSP is shown in Listing 6.11. The resulting HTML is
shown in Figure 6.2. The pg:pager tag has two important attributes url and
maxPageItems. They specify the destination URL when any of the navigation
links are clicked and the number of items per page respectively. In Listing 6.11,
the url is traverse.do a simple ForwardAction that forwards to the same JSP.
The JSP uses the iterate tag to iterate the collection. Th pg:item defines each
displayable row. The pg:index, pg:prev, pg:pages and pg:next together
display the page numbers, previous and next links. These tags even provide you
the flexibility of using your own images instead of plain old hyperlinks. Using
pg:param (not shown in the listing), additional request parameters can also be
submitted with the url.
Figure 6.2 Traversing the multi page list using Pager Taglib from jsptags.com
Table 6.3 Feature Comparison between DisplayTag and HtmlTable
Feature
DisplayTag
HtmlTable
149
Display
Formatting
Column
Grouping
Yes
Yes
Nested Tables
Coding Style
Yes
The display model should be
created in advance, but the
formatting can be invoked from the
JSP using hooks called decorators
Does not require controller (such
as Struts or its Action). The JSP
itself can control the paging.
Customizable auto paging
Yes
No. Messages can be externalized
to a properties file but cannot be
localized as of 1.0b2. Full support
is expected soon.
No
No
The display model and its formatting
should be performed in advance (in a
Action). The paging is tied to Struts.
Needs a predefined Action called
ServeTableAction. Strictly MVC based.
Paging
Sorting
I18N
Editable column
Documentation
and examples
User community
Good
Relatively high
Less
150
151
logic to access the database in the specified format read-only EJB, direct JDBC
or O/R mapper, the latter two approaches being preferred. We recommend
designing the Value List Handler intelligently so that it fetches data in bulk
using read-ahead
(a.k.a pre-fetch) mechanism i.e. data to serve two to three pages is retrieved and
in advance if needed so that the delay in retrieval is minimized. The beauty of
this pattern is that you can expose the ValueListIterator to the
IterateTag and the tag will believe that it is traversing the original
Iterator, while you can intelligently serve the requested rows and keep
fetching in the background.
In this context it is advantageous to combine an O/R mapping
framework that allows you use SQLs to search the database. Most of the
O/R mapping frameworks provide caching mechanisms. Hence the overhead of
object creation after retrieval is eliminated since the object in already in the cache.
Moreover you can take advantage of the features provided in the RDBMS. For
instance, DB2 provides a feature called ROW_NEXT.
Suppose that the requirement is to display 10 rows per page. Here is strategy
for devising a responsive system while working with large data set. When
the query is first made, data for three pages (30 rows) is prefetched and
maintained in the HttpSession. When the user requests the third page, the
ValueListHandler realizes that the end of the cache is reached. It goes ahead
and serves the third
152
page (21-30 rows). After that it initiates an asynchronous request to fetch another
30 rows from the database (This will need a scheduling mechanism to
which individual valueListHandlers submit their pre-fetch requests). When the
next 30 rows are retrieved, it caches them along with the original 30 rows. Hence
a cache of 60 rows is maintained per user. (This is to prevent a cache-fault if
the user decides to go to previous page while on third page). Depdending on the
size of the displayed objects, you have to choose an optimal cache within the
Value List Handler. If the objects are 1K each, 60 objects means 60K of memory
consumed by the corresponding HttpSession. This is absolutely not recommended.
A rule of thumb is that HttpSession size should not exceed 20K per user.
[Another reason to make the display objects only as big as needed and no
bigger. Overcome the tendency to reuse bloated value objects and data transfer
objects from elsewhere.] Coming back to the database features for large result
sets. The following
SQL can be used to fetch the first 30 rows:
SELECT FIRST_NAME, LAST_NAME, ADDRESS
FROM CUSTOMER,
WHERE
ORDER BY FIRST_NAME
FETCH FIRST 30 ROWS ONLY
OPTIMIZED FOR READ ONLY
When the user reaches the third page, the ValueListHandler makes a prefetch
request for the next 30 rows (Rows 31 to 60). The following SQL can be used to
fetch them:
SELECT * FROM
(
SELECT FIRST_NAME, LAST_NAME, ADDRESS
FROM CUSTOMER,
WHERE
ORDER BY FIRST_NAME
)
AS CUST_TEMP WHERE
ROW_NEXT BETWEEN 31 AND 60
OPTIMIZED FOR READ ONLY
This SQL consists of two parts. The inner SQL is exactly the same as
the SQL issued earlier and can be thought to be fetching the data into a
temporary table. The ROW_NEXT in the outer SQL identifies the exact rows to
be returned from the retrieved result set. The values 31 and 60 can be
substituted dynamically. The proprietary SQL no doubt impacts the portability,
but almost
153
every database used in the enterprise today has this feature. The Java code still is
portable.
$6
In this chapter you got an overview of Struts tags and more importantly learnt to
customize these tags for your projects. In addition you looked at JSTL and
Struts-EL. Hopefully this chapter has prepared you to use Struts tags better.
154
Chapter 7
Struts and Tiles
In this chapter:
You will learn to use Tiles with Struts for web page design using Layouts
Consider a banking application whose current web page layout has a header,
body and footer as shown by the first layout in Figure 7.1. The
management recently decided that all pages in the application should confirm to
the corporate look and feel as shown in the second layout in Figure 7.1. The new
layout has a header, footer, a varying body and a navigation sidebar.
Figure 7.1 Current and Future page layout for the banking application
When the application was first designed, the development team had two
alternatives.
Use JSP based approach. In this approach each JSP page is coded
separately. Although the header and footer are common to every page,
the common JSP markup for header and footer was added into each JSP by
direct copy and paste. This quick and dirty solution is unacceptable even
for the
155
Title
Header
Body
Footer
Figure 7.2 shows a sample HTML page from the banking application. Listing
7.1 shows the (simplified) JSP for that page. The JSP contains the
<jsp:include> for the common header and footer, but has the entire
layout written in terms of html table with various tr and td. All JSPs
in the application also might have the same layout copied over and over. This is
copy and paste technology taken to the next dimension and exactly where
Tiles comes into picture.
156
The basic principle behind Tiles is to refactor the common layout out of
the individual JSPs to a higher level and then reuse it across JSPs.
157
If the management wants a new look and feel, so be it; you can change the
common layout JSP and the whole web application has the new look and feel!
Redundancy is out and Reuse is in. In OO parlance this is similar to refactoring
common functions from a set of classes into their parent class.
In Tiles, layouts represent the structure of the entire page. Layout is simply a
JSP. Think of it as a template with placeholders (or slots). You can place other
JSPs in these slots declaratively. For instance, you can create a layout with slots
for header, body and footer. In a separate XML file (called XML tile definition
file), you specify what JSPs go into these slots. At runtime, Tiles
framework composes the aggregate page by using the layout JSP and filling
its slots with individual JSPs.
In essence, Tiles is a document assembly framework that builds on the
"include" feature provided by the JSP specification for assembling presentation
pages from component parts. Each part (also called a tile, which is also a JSP)
can be reused as often as needed throughout the application. This reduces
the amount of markup that needs to be maintained and makes it easier to change
the look and feel of a website. Tiles framework uses a custom tag library
to implement the templates.
Comparing this approach with <jsp:include> will help you to understand
the Tiles better. In the <jsp:include> approach, all included JSPs (header,
footer etc.) become part of the core JSP before being rendered. In Tiles, all the
JSPs header, footer and the core become part of the Layout JSP before being
rendered. The outermost JSP rendered to the user is always the same; it is
the layout JSP. This approach reduces redundancy of HTML and makes
maximum reuse of formatting logic. The entire chapter deals with using Tiles for
effective design in conjunction with Struts. In the next section, you will see
how the banking application can be converted into a Tiles oriented design.
))
In this section, you will learn how to assemble a Tiles application. We will start
with the CustomerDetails.jsp in Listing 7.1 and change it to use Tiles. The
Customer Details page is first shown to the user. When the submit button in the
Customer Form is pressed, a Success page is shown. Note that we are not
referring to .jsp files any more. Instead they are being referred to as pages.
There is a reason for this. Strictly speaking, the only JSP file that the user gets
every time is the Layout JSP the aggregate page. Hence the several incarnations
of Layout.jsp that the user sees are distinguished by their core contents
Customer Details information, Success information and so on.
158
159
value="/CustomerDetail.jsp" />
</definition>
The Tiles definition shown above defines the JSPs that go into each of the
insert tag placeholders in the SiteLayout.jsp for the Customer Details page and
identify it them a unique name. Note that the name of each put in the definition
is same as the value of attribute in the insert tag. Similarly a XML
value="/Success.jsp" />
</definition>
Compare the above definition for the Customer Details Page definition
shown earlier. You will see that only the title and body differ between the two.
The header and footer remain the same. Tiles allows you to factor out
these common elements in the definition and create a base definition.
Individual definitions can then extend from the base definition, much like
concrete classes extend from an abstract base class. Factoring out, the common
elements of the two page definitions results in a base definition as:
<definition name="base.definition" path="/Sitelayout.jsp">
<put name="title" value="MyBank/>
<put name="header" value="/common/header.jsp" />
<put name="footer" value="/common/footer.jsp" />
<put name="body"
</definition>
value="" />
160
value="/CustomerDetails.jsp" />
</definition>
<definition name="/success.page" extends="base.definition">
<put name="title" value="MyBank Success/>
<put name="body"
value="/Success.jsp" />
</definition>
path="/submitCustomerForm"
type="mybank.app1.CustomerAction"
name="CustomerForm"
scope="request"
validate="true"
input="CustomerDetails.jsp">
<forward name="success"
path="Success.jsp"
/>
</action>
The above action mapping uses the JSP name directly. With Tiles, you have
to replace the JSP name with the tiles definition name. The resulting action
mapping is shown below. The changes are highlighted in bold.
<action
path="/submitCustomerForm"
161
type="mybank.app1.CustomerAction"
name="CustomerForm"
scope="request"
validate="true"
input="customer.page">
<forward name="success" path="success.page"
/>
</action>
162
<setproperty property="definitionsconfig"
value="/WEBINF/tilesdefs.xml" />
<setproperty property="moduleAware" value="true"/>
</plugin>
The classname attribute refers to the plugin class that will be used. In this
case org.apache.struts.tiles.TilesPlugin class is used.
NOTE: CSS or Cascading Style Sheets is a way to add formatting rules and
layout to existing HTML tags. CSS greatly simplifies changes to page
appearance by only having to make edits to the stylesheets. Tiles, as we saw in
the previous section deals with the organization of different parts of the JSP page
as against enhancing the look and feel of individual components. CSS deals more
with enhancing individual features of the components in each tile or area of the
page. Tiles and CSS are complementary and can be used together to improve the
look and feel of a JSP page.
In this section, you converted the Struts based Customer page and the
subsequent page to use Tiles. The complete working application can
be downloaded from the website (http://www.objectsource.com).
Rules of thumb
1. Although Tiles provides several ways to construct a page, some of them
dont provide much advantage over the <jsp:include> approach at all.
The approach we have illustrated above is usually the one used most. It is
in this approach the real strength of Tiles get expressed.
2.
Thanks to the multiple application module support in Struts 1.1, you dont
have to Tiles enable your entire application and watch it collapse. Start by
breaking the application into multiple modules. Test if the modules
are working as expected. Also test inter-module navigation. Then Tilesenable the modules one at a time. This provides you a rollback
mechanism, if something goes wrong.
3.
Never use the Tiles definition as the URL on the browser. This will
not work. Struts can forward to a Tiles definition only when the
control is within the TilesRequestProcessor, not when an
external request arrives. If you want to display an aggregate Tiles page on
clicking a link, define an action mapping for the URL (You can also use a
global-forward instead). Then create an action mapping for a
ForwardAction and set the parameter attribute to be the Tiles definition.
4. In the application shown earlier, JSPs were used as Tiles. You can also use
action mappings as page names.
<definition name="/customer.page" extends="base.definition">
<put name="body"
value="/custdet.do" />
163
</definition>
In this chapter you saw how to use Tiles along with Struts to build a maintainable
and cleaner page layout. By transitioning your Struts modules to Tiles, you will
see a boost in productivity for both developers and page authors.
164
Chapter 8
Struts and I18N
In this chapter:
1. You will understand the basics of I18N
2. You will learn the basics of Java I18N API
3. You will review the features in Struts for I18N
4. You will look how Tiles application is I18N enabled
5. You will understand how localized input is processed
The Internet has no boundaries and neither should your web application. People
all over the world access the net to browse web pages that are written in different
languages. A user in Japan can access the web and check her Yahoo! Email in
Japanese. How does Yahoo do it? Is it because the users machine has a
Japanese operating system or do web-based applications automatically adjust
according to the users region? This chapter answers these questions and shows
you how to internationalize and localize your Struts web applications.
Terminology
Before diving deep into the bliss of Internationalization and Localization,
coverage of some basic terminology is essential. Thats what we are doing in this
section.
Internationalization or I18n is the process of enabling your application
to cater to users from different countries and supporting different languages.
With I18n, software is made portable between languages or regions. For
example, the Yahoo! Web site supports users from English, Japanese and
Korean speaking countries, to name a few.
Localization or L10n on the other hand, is the process of customizing your
application to support a specific location. When you customize your web
application to a specific country say, Germany, you are localizing your
application. Localization involves establishing on-line information to support
a specific language or region.
A Locale is a term that is used to describe a certain region and
possibly a language for that region. In software terms, we generally refer to
applications as
165
supporting certain locales. For example, a web application that supports a locale
of fr_FR is enabling French-speaking users in France to navigate it. Similarly a
locale of en_US indicates an application supporting English-speaking users in
the US.
A ResourceBundle is a class that is used to hold locale specific information.
In Java applications, the developer creates an instance of a
ResourceBundle and populates it with information specific to each locale such
as text messages, labels, and also objects. There will be one ResourceBundle
object per Locale.
What can be localized?
When your application runs anywhere in the US, everyone, well almost everyone
speaks English and hence, they wont have any trouble trying to figure out what
your application is trying to say. Now, consider the same application being
accessed by a user in a country say Japan where English is not the mainstream
language. There is a good chance that the very same message might not
make much sense to a Japanese user. The point in context is very simple: Present
your web application to foreign users in a way they can comprehend it and
navigate freely without facing any language barriers.
Great, now you know where this is leading, right? Thats right, localization!
In order to localize your web application, you have to identify the key areas that
will have to change. There are three such key areas. From a Struts perspective,
you only have to deal with the first two.
a. The visible part of your application the User Interface. The user interface
specific changes could mean changes to text, date formats, currency formats
etc.
b.
Glue Layer Presentation Logic that links the UI to the business logic.
c.
166
7. Sounds
8. Page layouts thats right. Just like colors, page layouts can vary from
locale to locale based on the countrys cultural preferences.
9. Presentation Logic in Struts Action classes.
There are other properties that you might require to be localized, but the ones
mentioned are the commonly used ones. Struts provides mechanisms to address
some of these, but the actual I18N and L10N capabilities lie in the Java
API itself. You will see in the next section, a brief overview of the
Java Internationalization API and some examples on how to update some of
these fields dynamically based on Locale information.
2 < * 5@
- :@
The primary I18N and L10N Java APIs can be found in the java.util
and java.text packages. This section shows some of the commonly used
classes and their functions. Figure 8.1 shows the classes in the Java I18n API. If
you are already familiar with the Java Internationalization API, you can skip this
section and proceed to the next section.
167
java.util.Locale
The Locale class represents a specific geographical or cultural region.
It contains information about the region and its language and sometimes a
variant specific to the users system. The variant is vendor specific and can be
WIN for a Windows system, MAC for a Macintosh etc. The following examples
show you how to create a Locale object for different cases:
A Locale object that describes only a language (French):
Locale frenchSpeakingLocale = new Locale("fr", "");
A Locale object that describes both the spoken language and the country (French
Canada):
Locale canadaLocale = new Locale("fr", "CA");
A Locale object that describes the spoken language, country and a variant
representing the users operating system (French Canada and Windows
Operating system):
Locale canadaLocaleWithVariant = new Locale("fr", "CA", "WIN");
168
the locale specific resources like text-messages, icons and labels are stored
in subclasses of the ResourceBundle. There will be one instance of
the ResourceBundle per locale. The getBundle() method in this class
retrieves the appropriate ResourceBundle instance for a given locale. The
location of the right bundle is implemented using an algorithm explained later.
Listing 8.1 Extracting data from a ResourceBundle
Locale myLocale = new Locale("fr","FR");
// Get the resource bundle for myLocale
ResourceBundle mybankBundle = ResourceBundle.getBundle(
"MybankResources",
myLocale);
// Get the localized strings from this resource bundle
String myHeader = mybankBundle.getString("header.title");
System.out.println(myHeader);
Let us see how a resource bundle instance is retrieved with a simple example.
Consider a custom ResourceBundle subclass called MybankResources that
will contain data specific to your application. In this example, you will see how
to use PropertyResourceBundles assuming that all the resources to be
localized are strings. In order to use PropertyResourceBundle, you will have
to create Java Properties files that will hold the data in key = value format. The
file
name
itself
identifies
the
Locale.
For
instance,
if
MybankResources.properties contains strings to be localized for the language
English in the United States (en_US), then MybankResources_fr_FR..properties
contains strings to be localized for the language fr (French) and region of FR
(France). In order to use the data in these files, you have to get the
ResourceBundle instance as shown in Listing 8.1.
In order to understand Listing 8.1, assume that the English properties file,
MybankResources.properties contains a key value pair: header.title=My
169
Next
assume
that
the
French
properties
file,
MybankResources_fr_FR.properties also contains a key value pair:
header.title= Ma Banque. The code snippet in Listing 8.1 produces an
output
Ma
Banque.
What
happens
if
the
MybankResources_fr_FR.properties file was missing? Just to see what happens,
rename the file to something else and run the program again. This time the output
will be My Bank. But the locale was
fr_FR!
Heres what happened. Because the locale was fr_FR, the getBundle()
method looked up MybankResources_fr_FR.properties. When it did not find this
file, it looked for the next best match MybankResources_fr.properties. But this
file doesnt exist either. Finally the getBundle() found the
MybankResources.properties
file
and
returned
an
instance
of PropertiesResourceBundle for this file. Accordingly the
String myHeader is looked up using the header.title key from
the MybankResources.properties file and returned to the user. In general,
the algorithm for looking up a Properties file is:
Bank.
MybankResources_language_country_variant.properties
MybankResources_language_country.properties
MybankResources_language.properties
MybankResources.properties
Java Properties files are commonly used for web tier localization in
Struts web applications. Hence we have shown you how to use them for
localizing string data. If your requirement involves extracting locale specific
resources besides strings, you might want to use the ListResourceBundle class.
NOTE: When the above program runs from the command line, the properties
file is located and loaded by the default command line class loader the System
Classpath Class Loader. Similarly in a web application, the properties file should
be located where the web application class loader can find it.
java.text.NumberFormat
NumberFormat is an abstract base class that is used to format and parse
numeric data specific to a locale. This class is used primarily to format numbers
and currencies. A sample example that formats currencies is shown in listing 8.2.
A currency format for French Locale is first obtained. Then a double is formatted
and printed using the currency format for French Locale. The output is: Salary
is: 5 124,75
In the above example, the double amount was hard coded as a decimal
in en_US format and printed as in the French format. Sometime you will have to
do the reverse while processing user input in your web applications. For instance,
a user in France enters a currency in the French format into a text field in the
web application and you have to get the double amount for the business
logic to
170
process it. The NumberFormat class has the parse() method to do this.
Listing 8.3 shows this. The output of the program is: Salary
is:
5124.75
Listing 8.2 Formatting currencies using NumberFormat
Locale frLocale = new Locale ("fr","FR");
// get instance of NumberFormat
NumberFormat currencyFormat =
NumberFormat.getCurrencyInstance(frLocale);
double salaryAmount = 5124.75;
// Format the amount for the French locale
String salaryInFrench = currencyFormat.format(salaryAmount);
System.out.println ("Salary is: " + salaryInFrench);
java.text.DateFormat
DateFormat is an abstract class that is used to format dates and times. When a
locale is specified it formats the dates accordingly. The following code formats a
date independent of locale
Date now = new Date();
String dateString = DateFormat.getDateInstance().format(now);
java.text.MessageFormat
MessageFormat is used to create concatenated messages in a language neutral
way. It takes a set of input objects, formats them and inserts the formatted strings
171
into specific places in a given pattern. Listing 8.4 shows how to create a
meaningful message by inserting string objects into specific locations in the
already existing message. When you run the program, you will get the following
output: John Doe logged in at 8/28/03 2:57 PM
Listing 8.4 Using MessageFormat to create message
Object[] myObjects = { "John",
"Doe",
new java.util.Date(System.currentTimeMillis())
};
String messageToBeDisplayed = "{0} {1} logged in at {2}";
String message =
java.text.MessageFormat.format(messageToBeDisplayed, myObjects);
System.out.println(message);
))
The I18N features of Struts framework build upon the Java I18N features. The
I18N support in Struts applications is limited to the presentation of text and
images.
I18N features of Struts Resource Bundle
The Struts Resource Bundle is very similar to the Java ResourceBundle. Struts
has an abstract class called org.apache.struts.util.MessageResources
and a subclass org.apache.struts.util.PropertyMessageResources
which as the name suggests is based on property files. In spite of the
similar functionalities, the above Struts classes (surprisingly) do not inherit
from their java.util counterparts. However if you understand the
working of the java.util.ResourceBundle, you have more or less
understood how the Struts Resource Bundles work. In general, Struts
applications deal with internationalization in the following way:
1.
The application developer creates several properties files (one per
Locale) that contain the localized text for messages, labels and image file
names to be displayed to the user. The naming convention for the
Locale
specific
properties
files
is
same
as
that
of
java.util.ResourceBundle.
The
base properties
file name
(corresponding to the en_US) is configured in the Struts Config file (Refer
to Chapter 3). For other Locales, Struts figures out the names of the
properties file by the standard naming conventions.
2.
The properties file should be placed so that the web application class loader
can locate it. The classes in the WEB-INF/classes folder are loaded by
the web application class loader and is an ideal place to put the properties
file.
172
The actual ActionError constructed has the Locale dependent message for the
key error.firstname.required. Some of the commonly used constructors
are:
ActionError(String key)
ActionError(String key, Object value)
ActionError(String key, Object values[])
The second and the third constructor are used if any parameters need to be
passed in dynamically. These constructors take the key and an array of
strings containing the replacement parameters to be used in the
validation
error messages.
This is similar to the behavior of
java.text.MessageFormat. E.g.: The properties file contains a key value pair
as
validation.range.message={0} cannot be less than {1} characters
173
You have already used the MessageTag (<bean:message>), not in the context
of I18N but for externalizing the messages. We used this tag to retrieve messages
from the external properties file. Now that the same properties files are put to use
in internationalizing the web application, the MessageTag has donned the role
of providing Locale specific text in the JSP. This is one of the most frequently
used tags whether you are localizing the application or not. Since you already the
workings of this tag, we will not bore you with more verbosity. Instead we will
compare this Struts tag with the JSTL equivalents. As has been stated earlier in
Chapter 6, the word on the street is that the Struts tags should be
preferably replaced with JSTL equivalents.
I18N features of HTML Tag Library
Struts HTML tag library is the key to rendering JSPs as HTML and is filled with
tags offering I18N features. Look for the tag attributes whose name ends
with key. For instance, the <html:img> tag offers srcKey to look up the src of
the image and altKey to look up the alt text from message resource bundle.
I18N features of LookupDispatchAction
As you already know, LookupDispatchAction offers excellent capability
to handle the business logic in a locale independent manner. Certain
restrictions apply in that it can be used only with grey buttons or html links
and not with image buttons. More details are in Chapter 4.
))
In Chapter 7 you saw how to use Tiles to organize your JSP pages. The
Tiles framework provides an easy way to add tiles or templates to a JSP
page to present content in a dynamic fashion. The Tiles framework, just like
Struts can be localized to provide different tiles based on a users preferred
locale. For example, the header tile in Chapter 7 could be replaced with a
different header that corresponds to a specific locale. It could contain an
area-specific flag for instance or simply a different background color.
A Tiles application has a Tiles definition file (e.g.:/WEB-INF/tiles-defs.xml)
that defines the structure of a JSP page using various tiles, for the header, menu,
body, footer etc. In the case of a localized Tiles application, there will one such
file per locale along with the default tiles-defs.xml file. For example, if
your application supports US English and French, there will be two
definition files, one for each locale as well as a default one tiles-defs_fr.xml,
tiles-defs_en.xml and tiles-defs.xml
The naming conventions for the Tiles definition files are the same as for a
java.util.ResourceBundle class as explained earlier in the chapter. Again,
just as in a localized Struts application, the session attribute
174
the appropriate definition file are loaded. For instance, if the default file
tiles- defs.xml is:
<tilesdefinitions>
<definition name="foo.bar" path="MybankLayout.jsp">
<put name="title"
value="/menu.jsp" />
value="/body.jsp" />
</definition>
</tilesdefinitions>
value="/menu.jsp" />
value="/body.jsp" />
</definition>
</tilesdefinitions>
This approach is justified if you use different JSPs per locale. However if the
JSPs themselves are fully I18N capable, meaning the single JSP can adapt itself
to render local sensitive UI, then the only difference between the two tiles
definition for the two locales, is the title. The need for different definition files in
that case could be eliminated if there was a mechanism to specify the key to the
message resource bundle in the <put> element above. Unfortunately such a
mechanism doesnt seem to exist at the time of writing and hence you are
left with creating definitions for each locale.
* )
Localized input is data input in a native language using locale specific formats.
How does your back-end Java code process data input in a native language? Let
us consider a simple form with two fields, fullName and monthlySalary as
shown below.
175
John Doe enters his monthly salary as 5431.52 and submits the form. Thats
it, the form fields are populated nicely and the application works without a hitch.
The conversion of the monthly salary from a String to a double is automatically
taken care of by Struts and John Doe wont have any problems with the
application.
What happens if the same application is viewed by a user in France and he
decides to enter the same amount in the French format as 5 431,52? When the
French user submits the application, the monthlySalary attribute in
CustomerForm ends up being populated with 0.0 instead of 5431.52. Why so?
When the form is submitted, the RequestProcessor populates the JavaBeans
properties of the ActionForm with the request parameters by using the
RequestUtils and BeanUtils classes. The actual population is done by
the BeanUtils.populate() method. That method tries to parse the String
5
431,52 and assign it to monthlySalary a double field without caring much
for the Locale of the user. This obviously throws an exception on which
the default action is to set 0.0 in the monthlySalary field.
What is the solution then? How can you make the Struts applications process
localized input? Since the BeanUtils class does not check the locale at the time
of populating the form, the only way out of this situation is to make the
monthlySalary field a String instead of a double. Now, the
BeanUtils does not try to parse a double from the String. Instead the value is
assigned AS IS. A customized routine has to be written to convert the String into a
double in a Locale dependent manner.
5#
Earlier, when applications were built, they were built for one language.
Those were the days of code pages. Code pages described how binary values
mapped to human readable characters. A currently executing program was
considered to be in a single code page. These approaches were fine until
Internationalization came along. Then came the issue of how to represent multiple
character sets and encodings for an application. Hence came character sets and
encodings.
Character sets are sets of text and graphic symbols mapped to positive
integers. ASCII was one of the first character sets to be used. ASCII
though efficient, was good at representing only US English.
176
Request encoding
Page encoding
Response encoding
Request encoding deals with the encoding used to encode request parameters.
Browsers typically send the request encoding with the Content-type header.
If this is not present, the Servlet container will use ISO-8859-1 as the
default encoding.
Page encoding is used in JSP pages to indicate the character encoding
for that file. You can find the page encoding from:
The Page Encoding value of a JSP property group whose URL pattern
matches the page. To see how JSP property groups work, you can go to the
following URL:
http://java.sun.com/j2ee/1.4/docs/tutorial/doc/JSPIntro13.html#wp72193
The pageEncoding attribute in a JSP page specified along with the page
directive. If the value pageEncoding attribute differs from the value
specified in the JSP property group, a translation error can occur.
If none of these encodings are mentioned, then the default encoding of ISO8859-1 is used.
Response encoding is the encoding of the text response sent by a Servlet or a
JSP page. This encoding governs the way the output is rendered on a
clients browser and based on the clients locale. The web container sets a
response encoding from one of the following:
177
The Page Encoding value of a JSP property group whose URL pattern
matches the page
If none of these encodings are mentioned, then the default encoding of ISO8859-1 is used.
Early on, when internationalization of computer applications became
popular, there was a boom in the number of encodings available to the
user. Unfortunately these encodings were unable to cover multiple languages.
For instance, the European Union was not able to cover all the European languages
in one encoding, resulting in having to create multiple encodings to cover
them. This further worsened the problem as multiple encodings could use
the same number to represent different characters in different languages.
The result: higher chances of data corruption.
A big company had its applications working great with a default locale of US
English, until it decided to go global. One of the requirements was to
support Chinese characters. The application code was modified accordingly but
each time the application ran, it was just not able to produce meaningful output,
as the text seemed to be distorted. The culprit was the database encoding.
Chinese characters, just like Korean and Japanese, have writing schemes that
cannot be represented by single byte code formats such as ASCII and EBCDIC.
These languages need at least a Double Byte Character Set (DBCS) encoding to
handle their characters. Once the database was updated to support DBCS
encoding, the applications worked fine. These problems led to the creation of a
universal character-encoding format called Unicode.
Unicode is a 16 bit character encoding that assigns a unique number to each
character in the major languages of the world. Though it can officially support up
to 65,536 characters, it also has reserved some code points for mapping
into additional 16-bit planes with the potential to cope with over a million
unique characters. Unicode is more efficient as it defines a standardized
character set that represents most of the commonly used languages. In
addition, it can be extended to accommodate any additions. Unicode
characters are represented as escape sequences of type \uXXXX where
XXXX is a characters 16 bit representation in hexadecimal in cases where
a Java programs source encoding is not Unicode compliant.
Struts and character encoding
Setting the character encoding in the web application requires the following
steps:
178
1.
Configure the servlet container to support the desired encoding. For instance,
you have to set the servlet container to interpret the input as UTF-8 for
Unicode. This configuration is vendor dependent.
2.
Set the response content type to the required encoding (e.g. UTF-8). In Struts
1.1, this information is specified in the <controller> element in
struts- config.xml using the contentType attribute.
3.
This can also be set in the JSPs with the @page directive as follows:
4.
<meta httpequiv="contenttype"
content="text/html; charset=UTF8">
5.
Make sure you are using the I18N version rather than the US version of the
JRE. (If you are using JDK, this problem may ot arise)
6.
You can use other encodings besides UTF-8 too. Use the above tool on the
Struts prorperties files (message resource bundles) containing non Latin1 encoding. Without this conversion, the Struts application (or java for that
matter) will
not
be
able
to
interpret
the
encoded
text.
Consequently the
<bean:message> and <html:errors/> will display garbage.
5$
179
In this chapter you started with what I18N and L10N are, their need and
their advantages. You also got a quick overview of the Java and
Struts Internationalization API. Then you looked at the various ways to
internationalize the web tier using the features in Struts and Tiles. You also saw
how to process localized input using Struts applications.
180
181
Chapter 9
Struts and Exception Handling
In this chapter:
1.
2.
3.
4.
5.
182
before the customer calls for resolution. Furthermore, the service representative
should be able to use the unique error identifier (reported by the user) to lookup
the production log files for quick identification of the problem preferably up to
the exact line number (or at least the exact method). In order to provide both the
end user and the support team with the tools and services they need, you
as a developer must have a clear picture, as you are building a system, of
everything that can go wrong with it once it is deployed.
>(
By default, stack traces are logged to the console. But browsing the console
for an exception trace isn't feasible in a production system.
3.
4.
Even if you redirect the console log to an output file, chances are that the file
will be overwritten when the production J2EE app servers are restarted.
5.
What you need is a mechanism to declaratively control logging so that your test
code and your production code are the same, and performance overhead incurred
in production is minimal when logging is declaratively turned off. The obvious
solution here is to use a logging utility. It is pretty customary these days to use a
utility like Log4J (http://jakarta.apache.org/log4j) for logging. With the right
coding conventions in place, a logging utility will pretty much take care of
recording any type of messages, whether a system error or some warning.
However it is up to you as a developer to make the best use of the utilities. It
requires a lot of forethought to handle exceptions effectively. In this chapter we
will use Log4J to log exceptions effectively. Hence we will review Log4J before
proceeding to look at some commonly accepted principles of Exception handling
in Java.
183
Log4J is the logging implementation available from Apaches Jakarta project and
has been around long before JDK Logging appeared and quite naturally has
a larger developer base. Lot of material is freely available online if you want to
dig deeper into Log4J and we have held back from such a detailed treatment here.
As with any Logging mechanisms, this library provides powerful capabilities
to declaratively control logging and the level of logging.
In Log4J, all the logging occurs through the Logger class
in org.apache.log4j package. The Logger class supports five levels
for logging. They are FATAL, ERROR, WARNING, INFO, DEBUG.
Without Log4J, you would perhaps use a Boolean flag to control the logging. With
such a boolean flag, there are only two states logging or no logging. In
Log4J the levels are defined to fine tune the amount of logging. Here is
how you would user the Log4J.
Logger logger = Logger.getLogger (foo.bar);
logger.debug (This is a debug message);
The code above first obtains the Logger instance named foo.bar and logs a
message at DEBUG level. You can declaratively turn off the logging for
messages at lower level than WARNING. This means the messages logged
at INFO and DEBUG level will not be logged.
Logged messages always end up in a destination like file, database table etc.
The destination of the log message is specified using the Appender. The
Appender can represent a file, console, email address or as exotic as a
JMS channel. If you need a destination that is not supported by the classes out of
the box you can write a new class that implements the Appender
interface. Appenders can be configured at startup in a variety of ways. One
way to configure them is through an XML file. A XML file is shown below.
<appender name="MybankWarn"
class="org.apache.log4j.FileAppender">
<param name="Threshold" value="WARN" />
<param name="File"
value="./logs/mybankwarnings.log" />
184
>(
2.
3.
4.
Log an exception where you catch it, unless you plan to re-throw it.
5.
Preserve the stack trace when you re-throw the exception by wrapping
the original exception in the new one.
6.
185
handled.
7.
8.
9.
try {
13
14
15
custDAO.update(info);
} catch (SQLException e) {
16
17
18 }
186
187
When the account does not have enough balance for requested
withdrawal amount, the user gets a NotEnoughBalanceException. The user
can decide to withdraw lesser amount. Notice that the application exception is
not logged. In case of the application exceptions, the developer explicitly
throws them in the code and the intent is very clear. Hence there is no need for
content (log or stack trace).
Principle 10 is about the use of debug flags with compilation. At
compile time it is possible to tell the JVM to ignore line number information.
The byte code without the line information are optimized for hotspot or server
mode and the recommended way of deployment for production systems. In such
cases, the exception stack traces do not provide the line number information.
You can overcome this handicap by refactoring your code during development
time and creating smaller and modular methods, so that guessing the line
numbers for the exceptions is relatively easier.
188
not found, then the JVM pops the current stack frame and inspects the
calling method (the next method in the stack), for the catch clause for the
exception or its parents. The process continues until the bottom of the stack
is reached. In summary, it requires a lot of time and effort on the part of JVM.
Exceptions should be thrown only when there is no meaningful way of
handling the situation. If these situations (conditions) can be handled
programmatically in a meaningful manner, then throwing exceptions should
be avoided. For instance if it is possible to handle the problem of
withdrawal amount exceeding the balance in some other way, it has to chosen
over throwing an application exception.
Examples of SystemException can be a ConfigurationException, which
might indicate that the data load during start up failed. There is really nothing a
user or even the customer support could do about it, except to correct the
problem and restart the server. Hence it qualifies as a System exception and can
be modeled as runtime exception.
Certain exceptions like SQLException might indicate a system error or
application problem depending on the case. In either case, it makes a lot of sense
to model SQLException as a checked exception because that is not thrown from
your application logic. Rather it is thrown in the third party library and the
library developer wants you to explicitly handle such a scenario.
6 # 23B
method
on
189
the
following methods.
getClassName
getFileName
getLineNumber
getMethodName
isNativeMethod
By calling the above methods, you can display the stack trace in any format
you like. In addition, elegant error monitoring systems can be written. For
instance, the error monitoring system should alert the appropriate support team
for the sub system by intelligently analyzing the stack trace. This has been made
easier. The following code snippet can be used with JDK 1.4
StackTraceElement elements[] = e.getStackTrace();
for (int i=0, n=elements.length; i<n; i++) {
if ( elements[i].getClassName.equals("LegacyAccessEJB)
&& elements[i].getMethodName().equals(invokeCOBOL)
{
//Alert the COBOL support team
}
}
6 $ >(
<
)
In the previous section, you looked at the general principles in exception
handling without a J2EE tilt. In this section, we will cover what servlet
specification has to say about exception handling. Consider the doGet() method
signature in a HttpServlet.
public void doGet(HttpServletRequest request,
HttpServletResponse response) throws
ServletException, IOException
The above method signature implies that a Servlet or a JSP (and finally
a web application) is only allowed to throw
190
RuntimeExceptions
All other checked exceptions have to be handled in the Servlet/JSP code in
one of the following manner:
Catch the checked exception and log the error message and (or) take any
business related action.
Wrap
the
exception
ServletException.
in
ServletException and
(ServletException
has
throw the
overloaded
191
<location>exceptions/Page404.jsp</location>
</errorpage>
6 4 >(
'
ServletException,
IOException,
192
HttpServletResponse response)
throws java.lang.Exception
The
problems in looking up or creating the Session EJB. If everything goes fine, then
the user is forwarded to the ActionForward identified by success. However we
have made no attempt to catch them and handle. Instead we have deferred their
handling to Struts through the declarative exception handling facility.
Listing 9.5 Declarative Exception Handling in Struts
<strutsconfig>
<actionmappings>
<action
path="/submitCustomerForm"
type="mybank.example.CustomerAction"
name="customerForm"
scope="request"
input="/CustomerDetails.jsp">
<exception
key="database.error.duplicate"
path="/UserExists.jsp"
type="mybank.account.DuplicateUserException"/>
<exception
key="rmi.error"
193
type="java.rmi.RemoteException"
path="/rmierror.jsp"/>
</action>
</actionmappings>
</strutsconfig>
Listing 9.5 shows the Struts Config file with declarative exception handling
for the two exceptions DuplicateUserexception and RemoteException.
For each exception, an <exception> element is defined in the action mapping.
The path attribute in the <exception> element specifies the page shown to the
user upon that exception. For instance, if a DuplicateUserException is
thrown when submitting the modified user profile, the controller will
forward control to the UserExists.jsp page. The key attribute is used to retrieve
the error message template from the associated resource bundle. Since the
<exception> is local to the action mapping, it applies only for that action
invocation. As you might have already notice the J2EE and Struts way of
declaratively handling exceptions are complementary to one another.
In the Listing 9.5, the declarative exception handling was local to the
CustomerAction. You can add global declarative exception handling too. For
instance, if you want to handle the RemoteException in the same way across
the board, use the following approach:
<strutsconfig>
<globalexceptions>
<exception
key="rmi.error"
type="java.rmi.RemoteException"
path="/rmierror.jsp"/>
</globalexceptions>
</strutsconfig>
194
195
196
65-
>(
197
First, the control hasn't passed outside of the application server yet.
(Assuming both the web tier and ejb tier do not belong to disparate entities). The
so-called client tier, which is composed of JSP pages, servlets and their helper
classes, runs on the J2EE application server itself. Second, the classes in a welldesigned web tier have a hierarchy (for example, hierarchy in the Business
Delegate classes, Intercepting Filter classes, JSP base class, or in the Struts
Action classes) or single point of invocation in the form of a
FrontController servlet (Business Delegate, Intercepting Filter and Front
Controller are Core J2EE Patterns. Refer to Sun blueprints for more details).
The base classes of these hierarchies or the central point in FrontController
classes can contain the exception logging code. In the case of session EJBbased logging, each of the methods in the EJB component must have
logging code. As the business logic grows, so will the number of session
EJB methods, and so will the amount of logging code. A web tier system
will require less logging code. You should consider this option if you have colocated web tier and EJB tiers and you don't have a requirement to support any
other type of client.
To develop a full fledged exception handling strategy let us start with
a simple class shown in Listing 9.7. This class, ExceptionCategory categorizes
the exceptions into INFO, WARNING, ERROR and FATAL. This identification
helps us when the notification of Support personnel depends on the severity of
the exception.
Listing 9.8 Exception Info class
public class ExceptionInfo implements java.io.Serializable {
private ExceptionCategory exceptionCategory;
private String errorCode;
private String exceptionID;
private boolean logged;
public ExceptionInfo(ExceptionCategory aCategory,
String aErrorCode) {
198
this.exceptionCategory = aCategory;
this.errorCode = aErrorCode;
this.logged = false;
this.exceptionID =
UniqueIDGeneratorFactory.
getUniqueIDGenerator().getUniqueID();
}
}
The next class to look at is the ExceptionInfo class as shown in Listing 9.8
This class provides information about the Exception as the name indicates. Apart
from the ExceptionCategory, this class also holds a unique id associated with
the Exception and a boolean indicating if the exception has been already logged.
The UniqueIDGeneratorFactory is a factory class that returns a
UniqueIDGenerator. UniqueIDGenerator is represented by an interface
IUniqueIDGenerator. This interface has just one method getUniqueID().
Listing 9.9 shows a simple Unique ID Generator implementation IP Address and
time.
Listing 9.9 Simple Unique ID Generator
public class UniqueIDGeneratorDefaultImpl
implements IUniqueIDGenerator
{
private static IUniqueIDGenerator instance =
new UniqueIDGeneratorDefaultImpl();
private long counter = 0;
public String getUniqueID() throws UniqueIDGeneratorException
{
String exceptionID = null;
try {
exceptionID = InetAddress.getLocalHost().getHostName();
} catch(UnknownHostException ue) {
throw new UniqueIDGeneratorException(ue);
}
exceptionID = exceptionID +
System.currentTimeMillis() +
counter++;
return exceptionID;
199
}
}
And finally Listing 9.10 shows the actual Exception class. This is the base
class for all the checked exceptions originating in MyBank. It is always better to
have a base class for all exceptions originating in a system and then create new
types as required. In this way, you can decide how much fine grained you want
the catch exception blocks to be. Similarly you can have a base class for
all unchecked exceptions thrown from system. Listing 9.11 shows such a class.
Listing 9.11 MybankRuntimeException class
public abstract class MybankRuntimeException extends Exception {
private ExceptionInfo exceptionInfo;
private Throwable wrappedException;
public MybankException(ExceptionInfo aInfo,
Throwable aWrappedException) {
super();
this.exceptionInfo = aInfo;
this.wrappedException = aWrappedException;
}
}
200
201
Armed with these knowledge let us look at a scenario that will lead to
duplicate logging in the system when an exception occurs. Consider a case when a
method, foo(), in an entity EJB component is accessed in a session EJB
method, called bar(). A web-tier client invokes the method bar() on the
session EJB component, and also logs the exceptions. If an exception occurs in
the entity EJB method foo() when the session EJB method bar() is invoked
from the web-tier, the exception will have been logged in three places: first in the
entity EJB component, then in the session EJB component, and finally in the web
tier.
Fortunately, addressing these problems is fairly easy to do in a generic way.
All you need is a mechanism for the caller to:
Access the unique ID
Find out if the exception has already been logged
If the exception has been already logged dont log it again.
We have already developed the MybankException and ExceptionInfo
class that let us check if the exception is already logged. If not logged already,
log the exception and set the logged flag to be true. These classes also generate a
unique id for every exception. Listing 9.13 shows a sample.
Listing 9.13 Sample Exception Logging
try {
CustomerDAO cDao = CustomerDAOFactory.getDAO();
cDao.createCustomer(CustomerValue);
} catch (CreateException ce) {
//Assume CreateException is a subclass of MybankException
if (! ce.isLogged() ) {
String traceStr = StackTraceTool.getStackTraceAsString(ce);
LogFactory.getLog(getClass().getName()).error(
ce.getUniqueID() + ":" + traceStr);
ce.setLogged(true);
}
202
throw ce;
}
Listing 9.13 shows the logging scenario when the exception caught is of type
MybankException. It is very much a fact that not all of the exceptions thrown
by your application are in this hierarchy. Under such conditions it is even more
important that the logging is centralized in one place since there is no mechanism
to prevent duplicate logging for exceptions outside the MybankException
hierarchy. That brings us to the idea of centralized logging. In the beginning of
this section we said that it is easy and convenient to log exceptions on web-tier
since most of the web-tier classes have a hierarchy. Let us examine this claim in
more detail.
66
In the previous section, we saw how to avoid duplicate logging. But when
it comes to the entire application, you also learnt that logging should not only
be done once but also centralized for disparate modules of the system if
possible. There are various strategies to achieve centralized logging in the web
tier. This section will cover those strategies.
Consider the web-tier for MyBank. After the Struts Forms are populated the
RequestProcessor invokes the execute method on Action classes. Typically,
in the execute method you access enterprise data and business logic in
session ejbs and legacy systems. Since you want to decouple your web tier
from the business logic implementation technology (EJB for example which
forces you to catch RemoteException) or the legacy systems, you are most
likely to introduce Business Delegates. (Business Delegate is a Core J2EE
Pattern). The Business Delegates might throw a variety of exceptions, most of
which you want to handle by using the Struts declarative exception handling.
When using the declarative exception handling you are most likely to log the
exceptions in the JSPs since the control passes out of your code at the end of the
execute method. Instead of adding the exception logging code to every JSP
declared in Struts Config file, you can create a parent of all the error JSPs and
put the logging code in there. Listing 9.14 shows a sample base JSP class.
There is quite a bit going on in Listing 9.14. First the class implements the
javax.servlet.jsp.HttpJspPage interface. All the methods in this
interface except the _jspService() have concrete implementations. These
methods represent the various methods called during the JSP life cycle. In
particular you will recognize the service method that is similar to the
servlets service method. In the course of this method execution, the
_jspService() method is also executed. _jspService() method is not
implemented by the
203
page author or the developer. Instead it is auto generated by the servlet container
during JSP pre-compilation or run time. All the markup, tags and scriptlets
contained in the JSP get transformed into Java code and form the gist of
the
_jspService() method. The page author indicates that the jsp extends
from this java class by adding the directive
<%@ page extends="mybank.webtier.MybankBaseErrorJsp" %>
If all of the Error-JSPs extend from this abstract JSP class, centralized
logging is achieved. Before you celebrate for having nailed down the problem,
shall we remind you that this solution may not work in all servlet containers. The
reason for this is JspFactory and PageContext implementations are vendor
dependent. Normally the calls for JspFactory.getDefaultFactory() and
factory.getPageContext() occur in the auto generated _jspService()
method. It is possible that some of the implementations may not initialize these
objects we accessed in the service() method until they reach the
_jspService() method !
{}
204
factory
= JspFactory.getDefaultFactory();
// errorPageURL
false, // needsSession
JspWriter.DEFAULT_BUFFER,
true
// autoFlush
);
Exception exc = pageContext.getException();
String trace =StackTraceTool.getStackTraceAsString(exc);
Logger.getLogger(getClass().getName()).error(trace);
//proceed with container generated code from here
_jspService(request,response);
}
205
return EVAL_BODY_INCLUDE;
}
}
For those of you who are visualizing the big picture, you will realize
that logging from the JSP is not the right way. However there is no ideal
way to achieve centralized logging without taking this approach. Each
mechanism has its drawbacks and tradeoffs. This is something you will
experience whenever design abstractions meet reality.
Until now you have seen a JSP based approach of exception handling
and logging. What if you have a requirement to handle the exceptions
originating from your application differently? Let us consider the application
exceptions from our very own MyBank. The exceptions originating from the
MyBank
are subclasses
of
MybankException
and
MybankRuntimeException. When using Struts as the framework in your web
applications, then you will most likely have a base Action class with trivial
functionality common to all of the Actions factored out. The base Action class is
the ideal location to centralize the special processing for the application
exceptions. Listing 9.16 shows a sample base Action, MybankBaseAction for
the special processing just mentioned.
Listing 9.16 Mybank Base Action
public class MybankBaseAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form, HttpServletRequest request,
HttpServletResponse response) throws Exception
{
ActionForward aForward = null;
MybankBaseForm aForm = (MybankBaseForm)form;
try
206
aForward = errorPage;
}
return aForward;
}
This class implements the execute method, but defines three abstract methods
preprocess(), process() and postprocess() which take the same
arguments as the execute() method but are invoked before, during and
:0 )
Until now, you have looked at various exception logging strategies. After
the exception is logged, there is also a need to report the fatal and serious ones
by sending out emails and pager messages to the support team. Several
approaches exist and the choices are numerous, but in this chapter we
would like to consolidate the logging and error reporting for better
coordination and control. For this, let us look at what Log4J has to offer.
Listing 9.17 SMTP Appender setup
<appender name="MybankMail"
class="org.apache.log4j.net.SMTPAppender">
<param name="Threshold" value="ERROR" />
207
As you already know, Log4J has three main components: Layout, Appender,
and Category (also known as Logger). Layout represents the format of the
message to be logged. Appender is an alias for the physical location at which the
message will be logged. And category is the named entity; you can think of it as a
handle for logging. Layouts and Appenders are declared in an XML
configuration file. Every category comes with its own layout and Appender
definitions. When you get a category and log to it, the message ends up in all the
appenders associated with that category, and all those messages will
be represented in the layout format specified in the XML configuration file.
Log4J comes with several standard appenders and one of them is called
SMTPAppender. By using the SMTPAppender, you can declaratively send
email messages when the errors occur in your system. You can configure
the SMTPAppender like any other Appender in the Log4J configuration
file. Listing 9.17 shows a sample setup for SMTPAppender. It takes a
Threshold, beyond which the Appender is operational, a subject for the email, the
From and To addresses, SMTP server name and the pattern for the email message
body.
You can set up the category for the above SMTPAppender as
<category name=com.mybank.webtier.action additivity=false>
<priority value=ERROR/>
<appenderref ref=MybankMail/>
<appenderref ref=MybankErrorLog/>
</category>
With this setup all the exceptions that are caught in the base Struts Action
MybankBaseAction are reported to email address prod-support@mybank.com.
This is because the category name is identified by the package name for
MybankBaseAction and while logging in Listing 9.16, we used the
category whose name is same as the fully qualified class name for
MybankBaseAction
which
happens
to
be
com.mybank.webtier.action.MybankBaseAction. The
email address
prod-support@mybank.com is generally an email group configured in the
enterprise mail server to include several individual recipients.
208
Note that this setting is vendor specific and may not be portable to
other application servers. But this is a minor change and should not be a
problem if you decide to port to another application server say JBoss.
If you are wondering, Email messages are all fine. How do I send
pager beeps? The quick answer is No problem. Pagers have email
addresses too. You can ask your service provider to associate the email address
with the pager. Telecommunications companies and providers can use
JavaMail API to implement a PAGER transport protocol that sends
email messages to alphanumeric pagers. Similar approach works for Short
Message Service (SMS) too since you can email a SMS device.
In development environments, the developer can go back, fix the root cause of
the exception and move on. Not so in production systems. Exception handling is
a very crucial part of enterprise applications. It is the key to quick response from
the support team and resolution of the problems in working systems. A delayed
or no resolution can leave the customer frustrated. In the internet world, where
the competitor is just a click away, the importance of exception handling,
logging, reporting and resolving cannot be stressed enough. This chapter
gave you the insights into various gotchas on your way, common mistakes
and strategies to address them from a web tier perspective.
209
Chapter 10
Effectively extending Struts
In this chapter:
1. You will learn how to extend Struts using PlugIn as an example
2. You will see how to construct a rudimentary page flow controller by
customizing ActionMapping
3. You will develop a mechanism for controlling validation for image button
form submission
4. You will see how to handle sensitive resubmissions in a generic way
rather than handling in every form
5. You will devise a strategy to avail DispatchAction-like functionality
for image button form submission
Struts is a generic framework. It works fine without modification. But there are
times when it has to be customized. And we are not talking about straightforward
customizations like extending the Form, Action and custom tags. We are
referring to the hooks that Struts provides to extend the framework. In
this chapter you will see several practical uses of these hooks.
A word of caution though: The customization features are probably going to
be around without modification until Struts 2.0. The main candidates for
overhaul are ActionMapping and RequestProcessor. The replacements
would be designed using Command, interceptor and chain of responsibility
patterns. However, since the above classes is part of public API, an alternative
strategy will definitely emerge to seamlessly migrate the customizations
discussed in this chapter so that none of the application code is affected and only
the customization code might change. Of course this is just a speculation.
To understand the hooks, consider a Struts Plugin for say, a slick menu
utility
(Such
a
utility
indeed
exists.
Check
out
http://struts- menu.sourceforge.net). The menu utility needs to read the
configuration data from an external file. If the PlugIn were instead
implemented as a servlet, it would read the file name from an <init
param> in web.xml. The <set property> can do the same task for the
PlugIn. The file name is set in the struts-config.xml by using <set
property>.
210
<plugin className=mybank.example.MyMenuPlugIn>
<setproperty property=fileName
value=/WEBINF/menudata.xml/>
</plugin>
A JavaBeans property with the same name (fileName) is then added to the
PlugIn class. The <setproperty> tells the Struts framework to set the
corresponding JavaBeans property in the plugin class (or any class
associated with the configuration) with the value attribute of the <set
property> element. In addition, the Struts PlugIn implements the PlugIn
interface from org.apache.struts.action package. Accordingly, the
MyMenuPlugIn class is defined as:
public class MyMenuPlugIn implements PlugIn {
private String fileName;
public String getFileName() {
return fileName;
}
public void setFileName(String name) {
this.fileName = name;
}
public void init(ActionServlet servlet,
ModuleConfig config) throws ServletException {
.. ..
}
public void destroy() {
.. ..
}
}
During startup, Struts sets the fileName property using the corresponding
setter method (and other properties if exist) and finally calls the init() method.
Since PlugIns are the last ones to be configured by Struts, all other data from the
struts-config.xml is guaranteed to be loaded before the init() method is
invoked. The init() method is an opportunity to override and change any other
settings including the RequestProcessor! Frameworks like SAIF (stands for
Struts Action Invocation Framework. Available at http://struts.sourceforge.net)
utilize this to change the RequestProcessor to one of its own.
211
id
ID
#IMPLIED
property
CDATA
#REQUIRED
value
CDATA
#REQUIRED>
path="/submitCustomerForm"
className=mybank.struts.MyActionMapping
type="mybank.app1.CustomerAction"
name="CustomerForm"
scope="request"
validate="true"
input="CustomerDetails.jsp">
<setproperty property="buttons"
value="nextButton,saveButton,cancelButton" />
<setproperty property="forwards"
value="page2,success,cancel" />
<forward name="page2"
path="Page2.jsp"
/>
<forward name="success"
path="success.jsp"
<forward name="success"
path="cancel.jsp"
/>
/>
</action>
The
below:
public class MyActionMapping extends ActionMapping {
private String buttons;
212
The custom action mapping is now ready to use. During startup, Struts
instantiates the subclass of ActionMapping (instead of ActionMapping itself) and
sets its JavaBeans properties to the values specified in the corresponding <set
property> element. As you know, the execute() method in the Action
accepts ActionMapping as one of the arguments. When the execute()
method in the CustomerAction is invoked at runtime, MyActionMapping is
passed as the ActionMapping argument due to the setting in Listing 10.1. It can
then be cast to MyActionMapping to access its JavaBeans properties as follows:
public ActionForward execute(ActionMapping mapping,
ActionForm form, HttpServletRequest request,
HttpServletResponse response) throws Exception {
MyActionMapping customMapping =
(MyActionMapping) mapping;
String actions = customMapping.getButtons();
..
..
}
Listing 10.2 struts-config.xml with global custom action mapping
<actionmappings type=mybank.struts.MyActionMapping>
<action
path="/submitCustomerForm"
type="mybank.app1.CustomerAction"
name="CustomerForm"
scope="request"
validate="true"
input="CustomerDetails.jsp">
<setproperty property="buttons"
value="nextButton,saveButton,cancelButton" />
<setproperty property="forwards"
value="page2,success,cancel" />
<forward name="page2"
path="Page2.jsp"
/>
<forward name="success"
path="success.jsp"
<forward name="success"
path="cancel.jsp"
</action>
/>
/>
213
</actionmappings>
There are several uses of simple customizations like this. As you will
see later in this chapter, a lot of code that needs to be often repeated everywhere
can be eliminated by simple customizations. While doing so, a single
customized ActionMapping will be used for all the action mappings in the
application. Setting the className for individual action mapping as shown in
Listing 10.1 is painful. The alternative is to specify the type attribute on
the <action mappings> element as shown in Listing 10.2. This tells
Struts to use the corresponding ActionMapping class for all the <action>
elements. Listing 10.2 forms the basis for some of the utilities we develop
in this chapter a rudimentary page flow controller, auto-validation feature
for image based form submissions, a generic mechanism to handle sensitive form
resubmissions and a DispatchAction-like facility for image based form
submissions. All of these will use the base Action class and base form conceived
in Chapter 4 in conjunction with the HtmlButton described in Chapter 6 for form
submission.
%)
In the last section you have seen how ActionMapping can be customized. Let us
use the customized action mapping to build a rudimentary page flow controller.
Every Action has to render the next view to the user after successful
business logic
invocation.
This
would
mean
that
a
standard
mapping.findForward() in every method of yours. The rudimentary page
flow controller eliminates this by providing this information in a declarative
manner in struts-config.xml utilizing
MyActionMapping.
That
information is used at runtime to automatically decide which page to
forward to. The reason why the page flow controller is called rudimentary is
because it has a serious limitation. If the page transitions are dynamic, then it
cannot work. The controller serves as an example for customized action mapping
usage. Some of the groundwork for the page flow controller is already done in
Listing 10.2, in case you didnt know it already!). In particular pay attention to the
two lines:
<setproperty property="buttons"
value="nextButton,saveButton,cancelButton" />
<setproperty property="forwards"
value="page2,success,cancel" />
214
...
MybankBaseForm myForm = (MybankBaseForm) form;
MyActionMapping myMapping = (MyActionMapping) mapping;
String selectedButton =
getSelectedButton(myForm, myMapping);
preprocess(myMapping, myForm, request, response);
// Returns a null forward if the page controller is used.
ActionForward forward =
process(myMapping, myForm, request, response);
postprocess(myMapping, myForm, request, response);
...
if (forward == null) { // For page controller only
String forwardStr = mapping.getForward(selectedButton);
forward = mapping.findForward(forwardStr);
}
return forward;
}
215
<
216
change you will find in Listing 10.4 is that the validation is turned off by setting
validate=false. With this setting, the validation in RequestProcessor is
completely turned off for all buttons. The validation will be explicitly invoked in
the base Actions execute() method. Listing 10.5 shows the execute()
method. The changes are shown in bold.
Listing 10.4 struts-config.xml with global custom action mapping
<actionmappings type=mybank.struts.MyActionMapping>
<action
path="/submitCustomerForm"
type="mybank.app1.CustomerAction"
name="CustomerForm"
scope="request"
validate="false"
input="CustomerDetails.jsp">
<setproperty property="buttons"
value="nextButton,saveButton,cancelButton" />
<setproperty property="validationFlags"
value="false,true,false" />
<forward name="page2"
path="Page2.jsp"
/>
<forward name="success"
path="success.jsp"
<forward name="success"
path="cancel.jsp"
/>
/>
</action>
</actionmappings>
The above method uses the selected button name to lookup a HashMap
named validationFlagMap. As you know, the JavaBeans properties
buttons and validationFlags were provided as comma separated values.
Parsing through the comma separated values at runtime for every user is a sheer
waste of time and resources. Hence the comma-separated values are parsed
in
217
their corresponding setters to create a HashMap with the button name as the key.
This ensures a fast retrieval of the values.
Listing 10.5 execute() method for controlling validation
Public MybankBaseAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form, HttpServletRequest request,
HttpServletResponse response) throws Exception {
...
MybankBaseForm myForm = (MybankBaseForm) form;
...
MyActionMapping myMapping = (MyActionMapping) mapping;
String selectedButton =
getSelectedButton(myForm, myMapping);
boolean validationReqd =
myMapping.isValidationRequired(buttons[i]);
if (validationReqd) {
ActionErrors errors =
myForm.validate(myMapping, request);
if (errors != null && ! errors.isEmpty()) {
saveErrors(request, errors);
return myMapping.getInput();
}
}
}
}
218
As seen above, every setter invokes the resolve() method. When the final
setter is invoked, all the attributes are non-null and the if block in resolve()
is entered. At this point every instance variable is guaranteed to be set by
the Struts start up process. The resolve() method creates a
StringTokenizer and parses the comma-delimited values to create a HashMap
with button name as the key and the validation flag as the value. This HashMap
thus created is utilized at runtime for a faster retrieval of flag values in the
isValidationRequired() method.
219
Page after page, the logic remains the same as above with two blanks to be filled.
They are:
1.
Should the execute() (or any equivalent method in Action) method check
if the submission was valid for the current page? (through
the isTokenValid() method)?
2.
path="/submitCustomerForm"
type="mybank.app1.CustomerAction"
name="CustomerForm"
scope="request"
validate="true"
input="CustomerDetails.jsp">
path="cancel.jsp"
/>
220
Listing 10.6 shows all the customizations needed to achieve what is needed.
The application flow that is used is the same flow as before: CustomerForm is a
single page form. On submit, a success page is shown. On Cancel, cancel.jsp is
shown. However the only twist is that success.jsp is treated as a JSP with a form
that needs to avoid duplicate submission (For simplicity purposes, we are
not showing the redirect=true setting).
The action mapping in listing 10.6 provides all the information needed
for sensitive resubmission logic to be retrieved in a generic manner in the
base Action class. Before looking at the actual code in MybankBaseAction,
let us look at what Listing 10.6 conveys. It has two new <setproperty>
elements. The first setting, validateToken is used to determine if token
validation is necessary on entering the execute(). The second setting,
resetToken is useful for the multi-page form scenario when the token has to
be reset only on the final page (See chapter 4 for more information). These two
settings fill in the first blank.
Next,
there
is
a
new
kind
of
ActionForward
called
mybank.struts.MyActionForward . This is an example of extending the
ActionForward class to add custom settings. The <forward> itself now
contains a <setproperty> for a JavaBeans property called setToken on
MyActionForward. This setting fills the second blank.
Now, let us look at the actual code that handles form submission. This code
goes into the base Action class and is shown in Listing 10.7. The new code is
shown in bold. In addition, the listing includes all the useful code discussed so
far that should make into the base Action (except page flow Controller). You can
use this Listing as the template base Action for real world applications.
The getValidateToken() method retrieves the validateToken (<set
property>) from MyActionMapping. This setting tells the framework whether
to check for sensitive resubmissions on the current page. After the check is done,
duplicate form submissions need to be handled as prescribed by your business.
For regular submissions, retrieve the ActionForward for the next page. If the next
page happens to be one of those requiring a token in the Http Session,
saveToken() is invoked and then the ActionForward is returned.
Listing 10.7 The complete base Action class
public class MybankBaseAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form, HttpServletRequest request,
HttpServletResponse response) throws Exception
// Add centralized logging here (Entry point audit)
// Check here if the user has rights to this application
// or retrieve app specific profile for the user
221
request.getAttribute("profile"));
222
!
and LookupDispatchAction work by invoking a
method on the Action whose name matches a predefined request parameter. This
works fine for form submissions when all the buttons have the same name but
does not work for image button form submissions. In this section, we will
further customize the ActionMapping to support a DispatchAction like feature for
image based form submissions. This can be used in an enterprise application
without a second thought. It will definitely prove useful timesaver.
DispatchAction
223
224
forward = (ActionForward)
MethodUtils.invokeMethod(this, ,methodName, args);
return forward;
}
}
:#
225
INDEX
Action, 24, 28, 29, 32, 33, 34, 37, 38,
39, 40, 48, 52, 53, 59, 62, 63, 66, 67,
69, 74, 76, 78, 80, 82, 83, 84, 85, 87,
88, 89, 90, 92, 93, 94, 97, 108, 110,
112, 113, 114, 128, 129, 130, 131,
142, 146, 148, 164, 172, 189, 190,
192, 193, 195, 200, 203, 205, 207,
208, 210, 211, 212, 213, 214, 215,
217, 218, 220, 221
ActionError, 35, 36, 37, 40, 44, 57, 64,
103, 106, 123, 125, 126, 127, 170,
192, 193, 194
ActionErrors, 35, 36, 37, 40, 43, 57,
101, 102, 106, 112, 113, 114, 124,
125, 126, 193, 214, 215, 218
ActionForm, 23, 28, 29, 32, 33, 36, 37,
38, 41, 42, 43, 51, 52, 53, 55, 61, 62,
63, 68, 75, 76, 78, 79, 84, 85, 90, 94,
99, 100, 102, 106, 107, 108, 109,
110, 111, 115, 120, 121, 122, 123,
128, 129, 130, 131, 142, 173, 189,
192, 203, 210, 212, 215, 218
ActionForward, 28, 29, 33, 34, 37, 38,
53, 62, 76, 79, 85, 92, 94, 108, 112,
114, 129, 189, 190, 192, 193, 203,
209, 210, 212, 213, 215, 218, 221
ActionMapping, 28, 29, 31, 32, 33, 34,
36, 37, 38, 40, 42, 43, 48, 51, 53, 54,
55, 57, 61, 62, 67, 68, 69, 72, 74, 75,
76, 77, 78, 79, 82, 83, 85, 94, 108,
129, 189, 192, 203, 207, 209, 210,
211, 212, 213, 215, 218, 220
ActionServlet, 23, 24, 28, 29, 30, 31,
47, 48, 49, 67, 80, 81, 106, 208
Bean Tags
MessageTag, 48, 54, 56, 61, 63, 80,
130, 132, 133, 138, 139, 154, 171,
176
WriteTag, 132, 133, 138, 139, 141,
143, 145, 147
Business Logic, 17, 90, 91, 113, 114
Character encoding, 173, 174
Checkbox, Smart, 121
Commons Validator, 99, 100, 101, 102,
106, 107, 111, 115
Configurable Controller, 21
CORBA, 29, 91
226
HTTP Redirect, 35
HttpSession, 87, 89, 146, 149, 165, 166,
170
I18N, 80, 136, 148, 162, 164, 165, 169,
170, 171, 172, 176, 177, 193
DateFormat, 168
Locale, 37, 80, 138, 162, 163, 164,
165, 166, 167, 168, 169, 170, 171,
173, 194
MessageFormat, 168, 169, 170
NumberFormat, 167, 168
PropertiesResourceBundle, 166, 167
ResourceBundle, 37, 163, 166, 169,
171
ImageButtonBean, 128, 129, 130, 131
IncludeAction, 66, 73, 74
jsp
include, 73, 74, 153, 154, 155, 160
JSTL, 104, 117, 133, 135, 136, 137,
138, 139, 141, 151, 171
EL, 117, 136, 137, 138, 139
List based Forms, 141
Logic Tags, 134, 140
EqualTag, 134, 135, 140
IterateTag, 135, 136, 143, 145, 147
LookupDispatchAction, 63, 66, 78, 79,
80, 90, 97, 131, 171, 220
Message Resource Bundle, 37, 40, 43,
44, 47, 48, 51, 56, 57, 63, 67, 79,
106, 127, 130, 170
Multiple, 132
MessageResources, 64, 82, 169, 209
Model 1 Architecture, 17, 18
Model 2 Architecture, 18, 19
Multi-page Lists, 145
MVC, 15, 18, 19, 20, 21, 22, 26, 66, 67,
70, 97, 148