Struts 2 Design and Programming
Struts 2 Design and Programming
by Budi Kurniawan
Publisher: BrainySoftware
Pub Date: January 25, 2008
Print ISBN-10: 0-9803316-0-9
Print ISBN-13: 978-0-9803316-0-8
Pages: 576
Overview
Offering both theoretical explanations and real-world applications, this in-depth guide
covers the 2.0 version of Struts, revealing how to design, build, and improve Java-based
Web applications within the Struts development framework. Feature functionality is
explained in detail to help programmers choose the most appropriate feature to accomplish
their objectives, while other chapters are devoted to file uploading, paging, and object
caching.
Editorial Reviews
Product Description
Offering both theoretical explanations and real-world applications, this in-depth guide
covers the 2.0 version of Struts, revealing how to design, build, and improve Java-based
Web applications within the Struts development framework. Feature functionality is
explained in detail to help programmers choose the most appropriate feature to accomplish
their objectives, while other chapters are devoted to file uploading, paging, and object
caching.
Introduction
Welcome to Struts 2 Design and Programming: A Tutorial.
Servlet technology and JavaServer Pages (JSP) are the main technologies for developing
Java web applications. When introduced by Sun Microsystems in 1996, Servlet technology
was considered superior to the reigning Common Gateway Interface (CGI) because servlets
stay in memory after responding to the first requests. Subsequent requests for the same
servlet do not require re-instantiation of the servlet's class, thus enabling better response
time.
The problem with servlets is it is very cumbersome and error-prone to send HTML tags to
the browser because HTML output must be enclosed in Strings, like in the following code.
PrintWriter out = response.getWriter();
out.println("<html><head><title>Testing</title></head>");
out.println("<body style=\"background:#ffdddd\">");
...
This is hard to program. Even small changes in the presentation, such as a change to the
background color, will require the servlet to be recompiled.
Sun recognized this problem and came up with JSP, which allows Java code and HTML tags
to intertwine in easy to edit pages. Changes in HTML output require no recompilation.
Automatic compilation occurs the first time a page is called and after it is modified. A Java
code fragment in a JSP is called a scriptlet.
Even though mixing scriptlets and HTML seems practical at first thought, it is actually a bad
idea for the following reasons:
Interweaving scriptlets and HTML results in hard to read and hard to maintain
applications.
Writing code in JSPs diminishes the opportunity to reuse the code. Of course, you
can put all Java methods in a JSP and include this page from other JSPs that need to
use the methods. However, by doing so you're moving away from the objectoriented paradigm. For one thing, you will lose the power of inheritance.
It is harder to write Java code in a JSP than to do it in a Java class. Let's face it, your
IDE is designed to analyze Java code in Java classes, not in JSPs.
It is easier to debug code if it is in a Java class.
It is easier to test business logic that is encapsulated in a Java class.
Java code in Java classes is easier to refactor.
In fact, separation of business logic (Java code) and presentation (HTML tags) is such an
important issue that the designers of JSP have tried to encourage this practice right from
the first version of JSP.
JSP 1.0 allowed JavaBeans to be used for encapsulating code, thereby supported code and
presentation separation. In JSP you use <jsp:useBean> and <jsp:setProperty> to create a
JavaBean and set its properties, respectively.
Unfortunately, JavaBeans are not the perfect solution. With JavaBeans, method names must
follow certain naming convention, resulting in occasionally clumsy names. On top of that,
there's no way you can pass arguments to methods without resorting to scriptlets.
To make code and HTML tags separation easier to accomplish, JSP 1.1 defines custom tag
libraries, which are more flexible than JavaBeans. The problem is, custom tags are hard to
write and JSP 1.1 custom tags have a very complex life cycle.
Later an effort was initiated to provide tags with specific common functionality. These tags
are compiled in a set of libraries named JavaServer Pages Standard Tag Libraries (JSTL).
There are tags for manipulating scoped objects, iterating over a collection, performing
conditional tests, parsing and formatting data, etc.
Despite JavaBeans, custom tags, and JSTL, many people are still using scriptlets in their
JSPs for the following reasons.
In a project involving programmers with different skill levels, it is difficult to make sure all
Java code goes to Java classes. To make scriptlet-free JSPs more achievable, JSP 2.0 added
a feature that allows software architects to disable scriptlets in JSPs, thus enforcing the
separation of code and HTML. In addition, JSP 2.0 provides a simpler custom tag life cycle
and allows tags to be built in tag files, if effect making writing custom tags easier.
the division of labor between the page designer and the web developer because the
developer is involved in both page authoring and business logic coding.
To summarize, Model 1 is not recommended for these reasons:
Navigation problem. If you change the name of a JSP that is referenced by other
pages, you must change the name in many locations.
There is more temptation to use scriptlets in JSPs because JavaBeans are limited and
custom tags are hard to write. However, as explained above, mixing Java code and
HTML in JSPs is a bad thing.
If you can discipline yourself to not write Java code in JSPs, you'll end up spending
more time developing your application because you have to write custom tags for
most of your business logic. It's faster to write Java code in Java classes.
Model 2
The second design model is simply called Model 2. This is the recommended architecture to
base your Java web applications on. Model 2 is another name for the Model-View-Controller
(MVC) design pattern. In Model 2, there are three main components in an application: the
model, the view, and the controller. This pattern is explained in detail in Chapter 1, "Model
2 Applications."
Note
The term Model 2 was first used in the JavaServer Pages Specification version 0.92.
In Model 2, you have one entry point for all pages and usually a servlet or a filter acts as
the main controller and JSPs are used as presentation. Compared to Model 1 applications,
Model 2 applications enjoy the following benefits.
Struts Overview
Now that you understand why Model 2 is the recommended design model for Java web
applications, the next question you'll ask is, "How do I increase productivity?"
This was also the question that came to servlet expert Craig R. McClanahan's mind before
he decided to develop the Struts framework. After some preliminary work that worked,
McClanahan donated his brainchild to the Apache Software Foundation in May 2000 and
Struts 1.0 was released in June 2001. It soon became, and still is, the most popular
framework for developing Java web applications. Its web site is http://struts.apache.org.
In the meantime, on the same planet, some people had been working on another Java open
source framework called WebWork. Similar to Struts 1, WebWork never neared the
popularity of its competitor but was architecturally superior to Struts 1. For example, in
Struts 1 translating request parameters to a Java object requires an "intermediary" object
called the form bean, whereas in WebWork no intermediary object is necessary. The
implication is clear, a developer is more productive when using WebWork because fewer
classes are needed. As another example, an object called interceptor can be plugged in
easily in WebWork to add more processing to the framework, something that is not that
easy to achieve in Struts 1.
Another important feature that WebWork has but Struts 1 lacks is testability. This has a
huge impact on productivity. Testing business logic is much easier in WebWork than in
Struts 1. This is so because with Struts 1 you generally need a web browser to test the
business logic to retrieve inputs from HTTP request parameters. WebWork does not have
this problem because business classes can be tested without a browser.
A superior product (WebWork) and a pop-star status (Struts 1) naturally pressured both
camps to merge. According to Don Brown in his blog
(www.oreillynet.com/onjava/blog/2006/10/my_history_of_struts_2.html), it all started at
JavaOne 2005 when some Struts developers and users discussed the future of Struts and
came up with a proposal for Struts Ti (for Titanium), a code name for Struts 2. Had the
Struts team proceeded with the original proposal, Struts 2 would have included coveted
features missing in version 1, including extensibility and AJAX. On WebWork developer
Jason Carreira's suggestion, however, the proposal was amended to include a merger with
WebWork. This made sense since WebWork had most of the features of the proposed Struts
Ti. Rather than reinventing the wheel, 'acquisition' of WebWork could save a lot of time.
As a result, internally Struts 2 is not an extension of Struts 1. Rather, it is a re-branding of
WebWork version 2.2. WebWork itself is based on XWork, an open source command-pattern
framework from Open Symphony (http://www.opensymphony.com/xwork). Therefore, don't
be alarmed if you encounter Java types that belong to package com.opensymphony.xwork2
throughout this book.
Note
In this book, Struts is used to refer to Struts 2, unless otherwise stated.
So, what does Struts offer? Struts is a framework for developing Model 2 applications. It
makes development more rapid because it solves many common problems in web
application development by providing these features:
Because Struts is a Model 2 framework, when using Struts you should stick to the following
unwritten rules:
No Java code in JSPs, all business logic should reside in Java classes called action
classes.
Use the Expression Language (OGNL) to access model objects from JSPs.
Little or no writing of custom tags (because they are relatively hard to code).
Upgrading to Struts 2
If you have programmed with Struts 1, this section provides a brief introduction of what to
expect in Struts 2. If you haven't, feel free to skip this section.
Instead of a servlet controller like the ActionServlet class in Struts 1, Struts 2 uses
a filter to perform the same task.
There are no action forms in Struts 2. In Struts 1, an HTML form maps to an
ActionForm instance. You can then access this action form from your action class
and use it to populate a data transfer object. In Struts 2, an HTML form maps
directly to a POJO. You don't need to create a data transfer object and, since there
are no action forms, maintenance is easier and you deal with fewer classes.
Now, if you don't have action forms, how do you programmatically validate user
input in Struts 2? By writing the validation logic in the action class.
Struts 1 comes with several tag libraries that provides custom tags to be used in
JSPs. The most prominent of these are the HTML tag library, the Bean tag library,
and the Logic tag library. JSTL and the Expression Language (EL) in Servlet 2.4 are
often used to replace the Bean and Logic tag libraries. Struts 2 comes with a tag
library that covers all. You don't need JSTL either, even though in some cases you
may still need the EL.
In Struts 1 you used Struts configuration files, the main of which is called strutsconfig.xml (by default) and located in the WEB-INF directory of the application. In
Struts 2 you use multiple configuration files too, however they must reside in or a
subdirectory of WEB-INF/classes.
Java 5 and Servlet 2.4 are the prerequisites for Struts 2. Java 5 is needed because
annotations, added to Java 5, play an important role in Struts 2. Considering that
Java 6 has been released and Java 7 is on the way at the time of writing, you're
probably already using Java 5 or Java 6.
Struts 1 action classes must extend org.apache.struts.action.Action. In Struts 2
any POJO can be an action class. However, for reasons that will be explained in
Chapter 3, "Actions and Results" it is convenient to extend the ActionSupport class
in Struts 2. On top of that, an action class can be used to service related actions.
Instead of the JSP Expression Language and JSTL, you use OGNL to display object
models in JSPs.
Tiles, which started life as a subcomponent of Struts 1, has graduated to an
independent Apache project. It is still available in Struts 2 as a plug-in.
Chapter 1, "Model 2 Applications" explains the Model 2 architecture and provides two
Model 2 applications, one using a servlet controller and one utilizing a filter dispatcher.
Chapter 2, "Starting with Struts" is a brief introduction to Struts. In this chapter you
learn the main components of Struts and how to configure Struts applications.
Struts solves many common problems in web development such as page navigation, input
validation, and so on. As a result, you can concentrate on the most important task in
development: writing business logic in action classes. Chapter 3, "Actions and Results"
explains how to write effective action classes as well as related topics such as the default
result types, global exception mapping, wildcard mapping, and dynamic method invocation.
Chapter 4, "OGNL" discusses the expression language that can be used to access the
action and context objects. OGNL is a powerful language that is easy to use. In addition to
accessing objects, OGNL can also be used to create lists and maps.
Struts ships with a tag library that provides User Interface (UI) tags and non-UI tags
(generic tags). Chapter 5, "Form Tags" deals with form tags, the UI tags for entering
form data. You will learn that the benefits of using these tags and how each tag can be
used.
Chapter 6, "Generic Tags" explains non-UI tags. There are two types of non-UI tags,
control tags and data tags.
HTTP is type-agnostic, which means values sent in HTTP requests are all strings. Struts
automatically converts these values when mapping form fields to non-String action
properties. Chapter 7, "Type Conversion" explains how Struts does this and how to
write your own converters for more complex cases where built-in converters are not able to
help.
Chapter 10, "Model Driven and Prepare Interceptors" discusses two important
interceptors for separating the action and the model. You'll find out that many actions will
need these interceptors.
Chapter 11, "The Persistence Layer" addresses the need of a persistence layer to
store objects. The persistence layer hides the complexity of accessing the database from its
clients, notably the Struts action objects. The persistence layer can be implemented as
entity beans, the Data Access Object (DAO) pattern, by using Hibernate, etc. This chapter
shows you in detail how to implement the DAO pattern. There are many variants of this
pattern and which one you should choose depends on the project specification.
Chapter 12, "File Upload" discusses an important topic that often does not get enough
attention in web programming books. Struts supports file upload by seamlessly
incorporating the Jakarta Commons FileUpload library. This chapter discusses how to
achieve this programming task in Struts.
Chapter 13, "File Download" deals with file download and demonstrates how you can
send binary streams to the browser.
In Chapter 14, "Security" you learn how to configure the deployment descriptor to
restrict access to some or all of the resources in your applications. What is meant by
"configuration" is that you need only modify your deployment descriptor fileno
programming is necessary. In addition, you learn how to use the roles attribute in the
action element in your Struts configuration file. Writing Java code to secure web
applications is also discussed.
Chapter 15, "Preventing Double Submits" explains how to use Struts' built-in
features to prevent double submits, which could happen by accident or by the user's not
knowing what to do when it is taking a long time to process a form.
Debugging is easy with Struts. Chapter
you can capitalize on this feature.
Chapter 17, "Progress Meters" features the Execute and Wait interceptor, which can
emulate progress meters for long-running tasks.
Chapter 18, "Custom Interceptors" shows you how to write your own interceptors.
Struts supports various result types and you can even write new ones.
"Custom Result Types" shows how you can achieve this.
Chapter 19,
Chapter 22, "XSLT" discusses the XSLT result type and how you can convert XML to
another XML, XHTML, or other formats.
Chapter 23, "Plug-ins" discusses how you can distribute Struts modules easily as plugins.
Chapter 24, "The Tiles Plug-in" provides a brief introduction to Tiles 2, an open source
project for laying out web pages.
Chapter 25, "JFreeChart Plug-ins" discusses how you can easily create web charts
that are based on the popular JFreeChart project.
Chapter 26, "Zero Configuration" explains how to develop a Struts application that
does not need configuration and how the CodeBehind plug-in makes this feature even more
powerful.
AJAX is the essence of Web 2.0 and it is becoming more popular as time goes by.
Chapter
27, "AJAX" shows Struts' support for AJAX and explains how to use AJAX custom tags to
build AJAX components.
Appendix C, "Annotations" discusses the new feature in Java 5 that is used extensively
in Struts.
http://struts.apache.org/downloads.html
There are different ZIP files available. The struts-VERSION-all.zip file, where VERSION is the
Struts version, includes all libraries, source code, and sample applications. Its size is about
86MB and you should download this if you have the bandwidth. If not, try struts-VERSIONlib.zip (very compact at 4MB), which contains the necessary libraries only.
Once you download a ZIP, extract it. You'll find dozens of JARs in the lib directory. The
names of the JARs that are native to Struts 2 start with struts2. The name of each Struts
JAR contains version information. For instance, the core library is packaged in the struts2core-VERSION.jar file, where VERSION indicates the major and minor version numbers. For
Struts 2.1.0, the core library name is struts2-core-2.1.0.jar.
There are also dependencies that come from other projects. The commons JAR files are
from the Apache Jakarta Commons project. You must include these commons JARs. The
ognl- VERSION.jar contains the OGNL engine, an important dependency. The freemarkerVERSION.jar contains the FreeMarker template engine. It is needed even if you use JSP as
your view technology because FreeMarker is the template language for Struts custom tags.
The xwork- VERSION.jar contains XWork, the framework Struts 2 depends on. Always
include this JAR.
The only JARs you can exclude are the plug-in files. Their names have this format:
struts2-xxx-plugin-VERSION.jar
Here, xxx is the plug-in name. For example, the Tiles plug-in is packaged in the struts2tiles-plugin-VERSION.jar file.
You do not need the Tiles JARs either unless you use Tiles in your application.
Sample Applications
The examples used in this book can be downloaded from this site.
http://jtute.com
appXXy
where XX is the two digit chapter number and y is a letter that represents the application
order in the chapter. Therefore, the second application in Chapter 1 is app01b.
Tomcat 6 was used to test all applications. All of them were run on the author's machine on
port 8080. Therefore, the URLs for all applications start with http://localhost:8080, followed
by the application name and the servlet path.
Model 2 Overview
Model 2 is based on the Model-View-Controller (MVC) design pattern, the central concept
behind the Smalltalk-80 user interface. As the term "design pattern" had not been coined
yet at that time, it was called the MVC paradigm.
An application implementing the MVC pattern consists of three modules: model, view, and
controller. The view takes care of the display of the application. The model encapsulates the
application data and business logic. The controller receives user input and commands the
model and/or the view to change accordingly.
Note
The paper entitled Applications Programming in Smalltalk-80(TM): How to use
Model-View-Controller (MVC) by Steve Burbeck, Ph.D. talks about the MVC pattern. You
can find it at
http://st-www.cs.uiuc.edu/users/smarch/st-docs/mvc.html.
In Model 2, you have a servlet or a filter acting as the controller of the MVC pattern. Struts
1 employs a servlet controller whereas Struts 2 uses a filter. Generally JavaServer Pages
(JSPs) are employed as the views of the application, even though other view technologies
are supported. As the models, you use POJOs (POJO is an acronym for Plain Old Java
Object). POJOs are ordinary objects, as opposed to Enterprise Java Beans or other special
objects.
In a Model 2 application, every HTTP request must be directed to the controller. The
request's Uniform Request Identifier (URI) tells the controller what action to invoke. The
term "action" refers to an operation that the application is able to perform. The POJO
associated with an action is called an action object. In Struts 2, as you'll find out later, an
action class may be used to serve different actions. By contrast, Struts 1 dictates that you
create an action class for each individual action.
A seemingly trivial function may take more than one action. For instance, adding a product
would require two actions:
1. Display the "Add Product" form to enter product information.
2. Save the data to the database.
As mentioned above, you use the URI to tell the controller which action to invoke. For
instance, to get the application to send the "Add Product" form, you would use the following
URL:
http://domain/appName/Product_input.action
The controller examines every URI to decide what action to invoke. It also stores the action
object in a place that can be accessed from the view, so that server-side values can be
displayed on the browser. Finally, the controller uses a RequestDispatcher object to
forward the request to the view (JSP). In the JSP, you use custom tags to display the
content of the action object.
In the next two sections I present two simple Model 2 applications. The first one uses a
servlet as the controller and the second one employs a filter.
Figure 1.2 and submit it. The application will then send a
confirmation page to the user and display the details of the saved product. (See Figure
1.3)
will fill in a form like the one in
Figure 1.4.
}
public String save() {
// add here code to save the product to the database
return "success";
}
}
} else if (action.equals("Product_save.action")) {
// instantiate action class
Product product = new Product();
// populate action properties
product.setProductName(
request.getParameter("productName"));
product.setDescription(
request.getParameter("description"));
product.setPrice(request.getParameter("price"));
// execute action method
product.save();
// store action in a scope variable for the view
request.setAttribute("product", product);
}
// forward to a view
String dispatchUrl = null;
if (action.equals("Product_input.action")) {
dispatchUrl = "/jsp/ProductForm.jsp";
} else if (action.equals("Product_save.action")) {
dispatchUrl = "/jsp/ProductDetails.jsp";
}
if (dispatchUrl != null) {
RequestDispatcher rd =
request.getRequestDispatcher(dispatchUrl);
rd.forward(request, response);
}
}
}
The process method in the ControllerServlet class processes all incoming requests. It
starts by obtaining the request URI and the action name.
String uri = request.getRequestURI();
int lastIndex = uri.lastIndexOf("/");
String action = uri.substring(lastIndex + 1);
Note
The .action extension in every URI is the default extension used in Struts 2 and is therefore
used here.
The process method then continues by performing these steps:
2. If an action object exists, populate the action's properties with request parameters.
There are three properties in the Product_save action: productName, description,
and price.
3. If an action object exists, call the action method. In this example, the save method
on the Product object is the action method for the Product_save action.
4. Forward the request to a view (JSP).
The part of the process method that determines what action to perform is in the following
if block:
// execute an action
if (action.equals("Product_input.action")) {
// there is nothing to be done
} else if (action.equals("Product_save.action")) {
// instantiate action class
...
}
There is no action class to instantiate for the action Product_input. For Product_save,
the process method creates a Product object, populates its properties, and calls its save
method.
Product product = new Product();
// populate action properties
product.setProductName(
request.getParameter("productName"));
product.setDescription(
request.getParameter("description"));
product.setPrice(request.getParameter("price"));
// execute action method
product.save();
// store action in a scope variable for the view
request.setAttribute("product", product);
}
The Product object is then stored in the HttpServletRequest object so that the view can
access it.
The process method concludes by forwarding to a view. If action equals
Product_input.action, control is forwarded to the ProductForm.jsp page. If action is
Product_save.action, control is forwarded to the ProductDetails.jsp page.
// forward to a view
String dispatchUrl = null;
if (action.equals("Product_input.action")) {
dispatchUrl = "/jsp/ProductForm.jsp";
} else if (action.equals("Product_save.action")) {
dispatchUrl = "/jsp/ProductDetails.jsp";
}
if (dispatchUrl != null) {
RequestDispatcher rd =
request.getRequestDispatcher(dispatchUrl);
rd.forward(request, response);
}
The Views
The application utilizes two JSPs for the views of the application. The first JSP,
ProductForm.jsp, is displayed if the action is Product_input.action. The second page,
ProductDetails.jsp, is shown for Product_save.action. ProductForm.jsp is given in
90
<html>
<head>
<title>Save Product</title>
<style type="text/css">@import url(css/main.css);</style>
</head>
<body>
<div id="global">
<h4>The product has been saved.</h4>
<p>
<h5>Details:</h5>
Product Name: ${product.productName}<br/>
Description: ${product.description}<br/>
Price: $${product.price}
</p>
</div>
</body>
</html>
The ProductForm.jsp page contains an HTML form for entering a product's details. The
ProductDetails.jsp page uses the JSP Expression Language (EL) to access the product
scoped object in the HttpServletRequest object. Struts 2 does not depend on the EL to
access action objects. Therefore, you can still follow the examples in this book even if you
do not understand the EL.
Listing 1.5.
Putting the JSPs under WEB-INF. Anything under WEB-INF or a subdirectory under
WEB-INF is protected. If you put your JSPs under WEB-INF you cannot access
them by using a browser, but the controller can still dispatch requests to those JSPs.
However, this is not a recommended approach since not all containers implement
this feature. BEA's WebLogic is an example that does not.
Using a servlet filter and filter out requests for JSP pages.
Using security restriction in your deployment descriptor. This is easier than using a
filter since you do not have to write a filter class. This method is chosen for this
application.
When you submit the form, the following URL will be sent to the server:
http://localhost:8080/app01a/Product_save.action
init. Called once by the web container just before the filter is put into service.
doFilter. Called by the web container each time it receives a request with a URL that
matches the filter's URL pattern.
destroy. Called by the web container before the filter is taken out of service, i.e.
when the application is shut down.
There is one distinct advantage of using a filter over a servlet as a controller. With a filter
you can conveniently choose to serve all the resources in your application, including static
ones. With a servlet, your controller only handles access to the dynamic part of the
application. Note that the url-pattern element in the web.xml file in the previous
application is
<servlet>
<servlet-name>Controller</servlet-name>
<servlet-class>...</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Controller</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
With such a setting, requests for static resources are not handled by the servlet controller,
but by the container. You wouldn't want to handle static resources in your servlet controller
because that would mean extra work.
A filter is different. A filter can opt to let through requests for static contents. To pass on a
request, call the filterChain.doFilter method in the filter's doFilter method. You'll learn
how to do this in the application to come.
Consequently, employing a filter as the controller allows you to block all requests to the
application, including request for static contents. You will then have the following setting in
your deployment descriptor:
<filter>
<filter-name>filterDispatcher</filter-name>
<filter-class>...</filter-class>
</filter>
<filter-mapping>
<filter-name>filterDispatcher</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
What is the advantage of being able to block static requests? One thing for sure, you can
easily protect your static files from curious eyes. The following code will send an error
message if a user tries to view a JavaScript file:
public void doFilter(ServletRequest request, ServletResponse
response, FilterChain filterChain) throws IOException,
ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
String uri = req.getRequestURI();
if (uri.indexOf("/css/") != -1
&& req.getHeader("referer") == null) {
res.sendError(HttpServletResponse.SC_FORBIDDEN);
} else {
// handle this request
}
}
It will not protect your code from the most determined people, but users can no longer type
in the URL of your static file to view it. By the same token, you can protect your images so
that no one can link to them at your expense.
On the other hand, using a servlet as the controller allows you to use the servlet as a
welcome page. This is an important feature since you can then configure your application so
that the servlet controller will be invoked simply by the user typing your domain name (such
as http://example.com) in the browser's address box. A filter does not have the
privilege to act as a welcome page. Simply typing the domain name won't invoke a filter
dispatcher. In this case, you will have to create a welcome page (that can be an HTML, a
JSP, or a servlet) that redirects to the default action.
The following example (app01b) is a Model 2 application that uses a filter dispatcher.
The directory structure of app01b is shown in
Figure 1.5.
The JSPs and the Product class are the same as the ones in app01a. However, instead of a
servlet as the controller, we have a filter called FilterDispatcher (given in
Listing 1.6).
Listing 1.7.
<filter>
<filter-name>filterDispatcher</filter-name>
<filter-class>app01b.FilterDispatcher</filter-class>
</filter>
<filter-mapping>
<filter-name>filterDispatcher</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Restrict direct access to JSPs.
For the security constraint to work, the auth-constraint
and login-config elements must be present -->
<security-constraint>
<web-resource-collection>
<web-resource-name>JSPs</web-resource-name>
<url-pattern>/jsp/*</url-pattern>
</web-resource-collection>
<auth-constraint/>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
</login-config>
</web-app>
To test the application, direct your browser to this URL:
http://localhost:8080/app01b/Product_input.action
Summary
In this chapter you learned the Model 2 architecture and how to write Model 2 applications,
using either a servlet controller or a filter dispatcher. These two types of Model 2
applications were demonstrated in app01a and app01b, respectively.
Practically the filter dispatcher in app01b illustrates the main function of the Struts 2
framework. However, what you've seen does not cover even 0.1% of what Struts can do.
You'll write your first Struts application in the next chapter and learn more features in
subsequent chapters.
The first benefit of using Struts is that you don't have to write a controller and can
concentrate on writing business logic in action classes. Here is the list of features that Struts
is equipped with to make development more rapid:
The list shows how Struts can help you with the tasks you did when developing the Model 2
applications in Chapter 1, "Model 2 Applications." There is much more. Custom tags for
displaying data, data conversion, support for AJAX, support for internationalization and
localization, and extension through plug-ins are some of them.
There's a lot that a filter dispatcher in a Model 2 application has to do and Struts' filter
dispatcher is by no means an exception. Since Struts has more, actually much more,
features to support, its filter dispatcher could grow infinitely in complexity. However, Struts
approaches this by splitting task processing in its filter dispatcher into subcomponents called
interceptors. The first interceptor you'll notice is the one that populates the action object
with request parameters. You'll learn more about interceptors in the section
"Interceptors" later in this chapter.
In a Struts application the action method is executed after the action's properties are
populated. An action method can have any name as long as it is a valid Java method name.
An action method returns a String value. This value indicates to Struts where control
should be forwarded to. A successful action method execution will forward to a different
view than a failed one. For instance, the String "success" indicates a successful action
method execution and "error" indicates that there's been an error during processing and an
error message should be displayed. Most of the time a RequestDispatcher will be used to
forward to a JSP, however JSPs are not the only allowed destination. A result that returns a
file for download does not need a JSP. Neither does a result that simply sends a redirection
command or sends a chart to be rendered. Even if an action needs to be forwarded to a
view, the view may not necessarily be a JSP. A Velocity template or a FreeMarker template
Now that you know all the basic components in Struts, I'll continue by explaining how Struts
works. Since Struts uses a filter dispatcher as its controller, all activities start from this
object.
The first things that a filter dispatcher does is verify the request URI and determine what
action to invoke and which Java action class to instantiate. The filter dispatcher in app01b
did this by using a string manipulation method. However, this is impractical since during
development the URI may change several times and you will have to recompile the filter
each time the URI or something else changes.
For matching URIs with action classes, Struts uses a configuration file named struts.xml.
Basically, you need to create a struts.xml file and place it under WEB-INF/classes. You
define all actions in the application in this file. Each action has a name that directly
corresponds to the URI used to invoke the action. Each action declaration may specify the
fully qualified name of an action class, if any. You may also specify the action method name
unless its name is execute, the default method name Struts will assume in the absence of
an explicit one.
An action class must have at least one result to tell Struts what to do after it executes the
action method. There may be multiple results if the action method may return different
results depending on, say, user inputs.
The struts.xml file is read when Struts starts. In development mode, Struts checks the
timestamp of this file every time it processes a request and will reload it if it has changed
since the last time it was loaded. As a result, if you are in development mode and you
change the struts.xml file, you don't need to restart your web container. Saving you time.
Configuration file loading will fail if you don't comply with the rules that govern the
struts.xml file. If, or should I say when, this happens, Struts will fail to start and you must
restart your container. Sometimes it's hard to decipher what you've done wrong due to
unclear error messages. If this happens, try commenting out actions that you suspect are
causing it, until you isolate and fix the one that is impending development.
Note
I'll discuss Struts development mode when discussing the Struts configuration files in the
section "Configuration
Figure 2.1 shows how Struts processes action invocation. It does not include the reading
of the configuration file, that only happens once during application launch.
For every action invocation the filter dispatcher does the following:
1. Consult the Configuration Manager to determine what action to invoke based on the
request URI:
2. Run each of the interceptors registered for this action. One of the interceptors will
populate the action's properties.
3. Execute the action method.
4. Execute the result.
Note that some interceptors run again after action method execution, before the result is
executed.
Interceptors
As mentioned earlier, there are a lot of things a filter dispatcher must do. Code that would
otherwise reside in the filter dispatcher class is modularized into interceptors. The beauty of
interceptors is they can be plugged in and out by editing the Struts' configuration file. Struts
achieves a high degree of modularity using this strategy. New code for action processing
can be added without recompiling the main framework.
Table 2.1 lists Struts default interceptors. The words in brackets in the Interceptor
column are names used to register the interceptors in the configuration file. Yes, as you will
see shortly, you need to register an interceptor in the configuration file before you can use
it. For example, the registered name for the Alias interceptor is alias.
Interceptor
Description
Alias (alias)
Chaining (chain)
Checkbox (checkbox)
Cookie (cookie)
Conversion Error
(conversionError)
Create Session
(createSession)
Debugging (debugging)
Exception (exception)
I18n (i18n)
Interceptor
Description
9, "Message Handling."
Logger (logger)
Model Driven
(modelDriven)
Supports for the model driven pattern for action classes that
implement ModelDriven. See Chapter 10, "The Model
Driven Pattern" for details.
Parameters (params)
Prepare (prepare)
Scope (scope)
Servlet Config
(servletConfig)
Static Parameters
(staticParams)
Roles (roles)
Interceptor
Description
Timer (timer)
Token (token)
Token Session
(tokenSession)
Validation (validation)
Workflow (workflow)
Profiling (profiling)
There are quite a number of interceptors, and this can be confusing to a beginner. The thing
is you don't have to know about interceptors intimately before you can write a Struts
application. Just know that interceptors play a vital role in Struts and we will revisit them
one at a time in subsequent chapters.
Most of the time the default interceptors are good enough. However, if you need nonstandard action processing, you can write your own interceptor. Writing custom interceptors
is discussed in
A, "Struts Configuration."
Appendix
Note
It is possible to have no configuration file at all. The zero configuration feature, discussed in
Chapter 26, "Zero Configuration," is for advanced developers who want to skip this
mundane task.
In struts.xml you define all aspects of your application, including the actions, the
interceptors that need to be called for each action, and the possible results for each action.
Interceptors and result types used in an action must be registered before they can be used.
Happily, Struts configuration files support inheritance and default configuration files are
included in the struts2-core- VERSION.jar file. The struts-default.xml file, one of such
default configuration files, registers the default result types and interceptors. As such, you
can use the default result types and interceptors without registering them in your own
struts.xml file, making it cleaner and shorter.
The default.properties file, packaged in the same JAR, contains settings that apply to all
Struts applications. As a result, unless you need to override the default values, you don't
need to have a struts.properties file.
Let's now look at struts.xml and struts.properties in more detail.
</package>
<package name="package-2" namespace="namespace-2">
extends="struts-default">
<action name="..."/>
<action name="..."/>
...
</package>
...
<package name="package-n" namespace="namespace-n">
extends="struts-default">
<action name="..."/>
<action name="..."/>
...
</package>
</struts>
A package element must have a name attribute. The namespace attribute is optional and
if it is not present, the default value "/" is assumed. If the namespace attribute has a nondefault value, the namespace must be added to the URI that invokes the actions in the
package. For example, the URI for invoking an action in a package with a default
namespace is this:
/context/actionName.action
To invoke an action in a package with a non-default namespace, you need this URI:
/context/namespace/actionName.action
A package element almost always extends the struts-default package defined in strutsdefault.xml. By doing so, all actions in the package can use the result types and
interceptors registered in struts-default.xml. Appendix A, "Struts Configuration" lists
all the result types and interceptors in struts-default. Here is the skeleton of the strutsdefault package. The interceptors have been omitted to save space.
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package name="struts-default">
<result-types>
<result-type name="chain" class="com.opensymphony.
xwork2.ActionChainResult"/>
<result-type name="dispatcher"
class="org.apache.struts2.dispatcher.ServletDispatcherResult"
default="true"/>
<result-type name="freemarker"
class="org.apache.struts2.views.freemarker.FreemarkerResult"/>
<result-type name="httpheader"
class="org.apache.struts2.dispatcher.HttpHeaderResult"/>
<result-type name="redirect"
class="org.apache.struts2.dispatcher.ServletRedirectResult"/>
<result-type name="redirect-action"
class="org.apache.struts2.dispatcher.ServletActionRedirectResult"/>
<result-type name="stream"
class="org.apache.struts2.dispatcher.StreamResult"/>
<result-type name="velocity"
class="org.apache.struts2.dispatcher.VelocityResult"/>
<result-type name="xslt"
class="org.apache.struts2.views.xslt.XSLTResult"/>
<result-type name="plaintext"
class="org.apache.struts2.dispatcher.PlainTextResult"/>
</result-types>
<interceptors>
[all interceptors]
</interceptors>
</package>
</struts>
Each module.xml file would have the same DOCTYPE element and a struts root element.
Here is an example:
Note
Most sample applications in this book only have one struts.xml file. The only sample
application that splits the struts.xml file into smaller files can be found in
"The
Chapter 25,
JFreeChart Plug-in."
Chapter 3,
If an action has a non-default action class, however, you must specify the fully class name
using the class attribute. In addition, you must also specify the name of the action method,
which is the method in the action class that will be executed when the action is invoked.
Here is an example.
<action name="Address_save" class="app.Address" method="save">
If the class attribute is present but the method attribute is not, execute is assumed for
the method name. In other words, the following action elements mean the same thing.
<action name="Employee_save" class="app.Employee" method="execute">
<action name="Employee_save" class="app.Employee">
The first result will be executed if the action method save returns "success," in which case
the Confirm.jsp page will be displayed. The second result will be executed if the method
returns "input," in which case the Product.jsp page will be sent to the browser.
By the way, the type attribute of a result element specifies the result type. The value of
the type attribute must be a result type that is registered in the containing package or a
parent package extended by the containing package. Assuming that the action
Product_save is in a package that extends struts-default, it is safe to use a Dispatcher
result for this action because the Dispatcher result type is defined in struts-default.
If you omit the name attribute in a result element, "success" is implied. In addition, if the
type attribute is not present, the default result type Dispatcher is assumed. Therefore,
these two result elements are the same.
An alternative syntax that employs the param element exists for the Dispatcher result
element. In this case, the parameter name to be used with the param element is location.
In other words, this result element
<result>/test.jsp</result>
To apply an interceptor to an action, use the interceptor-ref element under the action
element of that action. For instance, the following configuration registers four interceptors
and apply them to the Product_delete and Product_save actions.
<package name="main"
<interceptors>
<interceptor
<interceptor
<interceptor
<interceptor
</interceptors>
extends="struts-default">
name="alias" class="..."/>
name="i18n" class="..."/>
name="validation" class="..."/>
name="logger" class="..."/>
With these settings every time the Product_delete or Product_save actions are invoked,
the four interceptors will be given a chance to process the actions. Note that the order of
appearance of the interceptor-ref element is important as it determines the order of
invocation of registered interceptors for that action. In this example, the alias interceptor
will be invoked first, followed by the i18n interceptor, the validation interceptor, and the
logger interceptor.
With most Struts application having multiple action elements, repeating the list of
interceptors for each action can be a daunting task. In order to alleviate this problem,
Struts allows you to create interceptor stacks that group required interceptors. Instead of
referencing interceptors from within each action element, you can reference the interceptor
stack instead.
For instance, six interceptors are often used in the following orders: exception,
servletConfig, prepare, checkbox, params, and conversionError. Rather than
referencing them again and again in your action declarations, you can create an interceptor
stack like this:
<interceptor-stack name="basicStack">
<interceptor-ref name="exception"/>
<interceptor-ref name="servlet-config"/>
<interceptor-ref name="prepare"/>
<interceptor-ref name="checkbox"/>
<interceptor-ref name="params"/>
<interceptor-ref name="conversionError"/>
</interceptor-stack>
The struts-default package defines several stacks. In addition, it defines a defaultinterceptor-ref element that specifies the default interceptor or interceptor stack to use if
no interceptor is defined for an action:
<default-interceptor-ref name="defaultStack"/>
If an action needs a combination of other interceptors and the default stack, you must
redefine the default stack as the default-interceptor-ref element will be ignored if an
interceptor element can be found within an action element.
Used within an action element, param can be used to set an action property. For example,
the following param element sets the siteId property of the action.
And the following param element sets the excludeMethod of the validation interceptorref:
<interceptor-ref name="validation">
<param name="excludeMethods">input,back,cancel</param>
</interceptor-ref>
The excludeMethods parameter is used to exclude certain methods from invoking the
enclosing interceptor.
Appendix
A, "Struts Configuration" provides the complete list of key/value pairs that may appear in a
struts.properties file.
To avoid creating a new file, you can use constant elements in the struts.xml file.
Alternatively, you can use the init-param element in the filter declaration of the Struts
filter dispatcher:
<filter>
<filter-name>struts</filter-name>
<filter-class>
org.apache.struts2.dispatcher.FilterDispatcher
</filter-class>
<init-param>
<param-name>struts.devMode</param-name>
<param-value>true</param-value>
</init-param>
</filter>
Figure 2.2.
Listing 2.2.
Listing 2.1. The deployment descriptor (web.xml file)
90
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<filter>
<filter-name>struts2</filter-name>
<filterclass>org.apache.struts2.dispatcher.FilterDispatcher</filterclass>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Restrict direct access to JSPs.
For the security constraint to work, the auth-constraint
and login-config elements must be present -->
<security-constraint>
<web-resource-collection>
<web-resource-name>JSPs</web-resource-name>
<url-pattern>/jsp/*</url-pattern>
</web-resource-collection>
<auth-constraint/>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
</login-config>
</web-app>
90
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package name="app02a" namespace="/" extends="struts-default">
<action name="Product_input">
<result>/jsp/ProductForm.jsp</result>
</action>
<action name="Product_save" class="app02a.Product">
<result>/jsp/ProductDetails.jsp</result>
</action>
</package>
</struts>
The struts.xml file defines a package (app02a) that has two actions, Product_input and
Product_save. The Product_input action does not have an action class. Invoking
Product_input simply forwards control to the ProductForm.jsp page. This page contains
an entry form for entering product information.
The Product_save action has a non-default action class (app02.Product). Since no
method attribute is present in the action declaration, the execute method in the Product
class will be invoked.
Note
During development you can add these two constant elements on top of your package
element.
<constant name="struts.enable.DynamicMethodInvocation"
value="false" />
<constant name="struts.devMode" value="true" />
The first constant disables dynamic method invocation, explained in Chapter 3, "Actions
and Results." The second constant element causes Struts to switch to development mode.
this.productName = productName;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
public String execute() {
return "success";
}
}
http://localhost:8080/app02a/Product_input.action
Dependency Injection
Before we continue, I'd like to introduce a popular design pattern that is used extensively in
Struts: dependency injection. Martin Fowler wrote an excellent article on this pattern. His
article can be found here:
http://martinfowler.com/articles/injection.html
Before Fowler coined the term "dependency injection," the phrase "inversion of control" was
often used to mean the same thing. As Fowler notes in his article, the two are not exactly
the same. This book therefore uses "dependency injection."
Overview
I'll explain dependency injection with an example.
If you have two components, A and B, and A depends on B, you can say A is dependent on
B or B is a dependency of A. Suppose A has a method, importantMethod, that uses B as
defined in the following code fragment:
public class A {
public void importantMethod() {
B b = ... // get an instance of B
b.usefulMethod();
...
}
...
}
A must obtain an instance of B before it can use B. While it is as straightforward as using
the new keyword if B is a Java concrete class, it can be problematic if B is not and there
are various implementations of B. You will have to choose an implementation of B and by
doing so you reduce the reusability of A (you cannot use A with implementations of B that
you did not choose).
As a more concrete example, consider the following PersistenceManager class that can be
used to persist objects to a database.
public class PersistenceManager {
public void store(Object object) {
DataSource dataSource = ... // obtain DataSource
try {
Connection connection = dataSource.getConnection();
... // store object in the database
} catch (SQLException e) {
}
}
}
PersistenceManager depends on DataSource. It has to obtain a DataSource before it
can create a Connection object to insert data to the database. In a Java EE application,
obtaining a data source often involves performing a JNDI lookup using the following
boilerplate code:
DataSource dataSource = null;
try {
context = new InitialContext();
dataSource = (DataSource)
context.lookup("java:/comp/env/jdbc/myDataSource");
} catch (NamingException e) {
}
Here is a problem. To perform a JNDI lookup you need a JNDI name. However, there's no
guarantee every application that uses PersistenceManager will provide the same JNDI
name. If you hard-code the JNDI like I did in the code above, PersistenceManager will
become less reusable.
Dependency injection dictates that dependency should be injected to the using component.
In the context of the PersistenceManager example here, a DataSource object should be
passed to the PersistenceManager instead of forcing PersistenceManager to create one.
One way to do it is by providing a constructor that accepts the dependency, in this case a
DataSource:
public class PersistenceManager {
private DataSource dataSource;
public PersistenceManager(DataSource dataSource) {
this.dataSource = dataSource;
}
public void store(Object object) {
try {
Connection connection = dataSource.getConnection();
... // store object in the database
} catch (SQLException e) {
}
}
}
Now, anyone who wants to use PersistenceManager must "inject" an instance of
DataSource through the PersistenceManager class's constructor. PersistenceManager
has now become decoupled from the DataSource instance it is using, making
PersistenceManager more reusable. The user of PersistenceManager will likely be in a
better position to provide a DataSource than the author of PersistenceManager because
the user will be familiar with the environment PersistenceManager will be running on.
Struts uses setter methods for its dependency injection strategy. For example, the
framework sets action properties by injecting HTTP request parameters' values. As a result,
you can use an action's properties from within the action method, without having to worry
about populating the properties.
Note
Java 5 EE supports dependency injection at various levels. Feel free to visit this site:
http://java.sun.com/developer/technicalArticles/J2EE/injection/
Summary
In this chapter you have learned what Struts offers to speed up Model 2 application
development. You have also learned how to configure Struts applications and written your
first Struts application.
Action Classes
Every operation that an application can perform is referred to as an action. Displaying a
Login form, for example, is an action. So is saving a product's details. Creating actions is
the most important task in Struts application development. Some actions are as simple as
forwarding to a JSP. Others perform logic that needs to be written in action classes.
An action class is an ordinary Java class. It may have properties and methods and must
comply with these rules.
A property must have a get and a set methods. Action property names follow the
same rules as JavaBeans property names. A property can be of any type, not only
String. Data conversion from String to non-String happens automatically.
An action class must have a no-argument constructor. If you don't have a
constructor in your action class, the Java compiler will create a no-argument
constructor for you. However, if you have a constructor that takes one or more
arguments, you must write a no-argument constructor. Or else, Struts will not be
able to instantiate the class.
An action class must have at least one method that will be invoked when the action
is called.
An action class may be associated with multiple actions. In this case, the action class
may provide a different method for each action. For example, a User action class
may have login and logout methods that are mapped to the User_login and
User_logout actions, respectively.
Since Struts 2, unlike Struts 1, creates a new action instance for every HTTP request,
an action class does not have to be thread safe.
Struts 2, unlike Struts 1, by default does not create an HttpSession object.
However, a JSP does. Therefore, if you want a completely session free action, add
this to the top of all your JSPs:
<%@page session="false"%>
The Employee class in Listing 3.1 is an action class. It has four properties (firstName,
lastName, birthDate, and emails) and one method (register).
SUCCESS. Indicates that the action execution was successful and the result view
should be shown to the user.
NONE. Indicates that the action execution was successful but no result view should
be shown to the user.
ERROR. Indicates that that action execution failed and an error view should be sent
to the user.
INPUT. Indicates that input validation failed and the form that had been used to
take user input should be shown again.
LOGIN. Indicates that the action could not execute because the user was not logged
in and the login view should be shown.
You need to know the values of these static fields as you will use the values when
configuring results. Here they are.
public
public
public
public
public
static
static
static
static
static
final
final
final
final
final
String
String
String
String
String
SUCCESS = "success";
NONE = "none";
ERROR = "error";
INPUT = "input";
LOGIN = "login";
Note
One thing to note about the Struts action is you don't have to worry about how the view will
access it. Unlike in the app01a and app01b applications where values had to be stored in
scoped attributes so that the view could access them, Struts automatically pushes actions
and other objects to the Value Stack, which is accessible to the view. The Value Stack is
explained in
Chapter 4, "OGNL."
Accessing Resources
From an action class, you can access resources such as the ServletContext, HttpSession,
HttpServletRequest, and HttpServletResponse objects either through the
ServletActionContext object or by implementing Aware interfaces. The latter is an
implementation of dependency injection and is the recommended way as it will make your
action classes easier to test.
This section discusses the techniques to access the resources.
Aware Interfaces
Struts provides four interfaces that you can implement to get access to the
ServletContext, HttpServletRequest, HttpServletResponse, and HttpSession objects,
respectively: The interfaces are
org.apache.struts2.util.ServletContextAware
org.apache.struts2.interceptor.ServletRequestAware
org.apache.struts2.interceptor.ServletResponseAware
org.apache.struts2.interceptor.SessionAware
I discuss these interfaces in the following subsections and provide an example of an action
that implements these interfaces in the next section.
ServletContextAware
You implement the ServletContextAware interface if you need access to the
ServletContext object from within your action class. The interface has one method,
setServletContext, whose signature is as follows.
void setServletContext(javax.servlet.ServletContext servletContext)
When an action is invoked, Struts will examine if the associated action class implements
ServletContextAware. If it does, Struts will call the action's setServletContext method
and pass the ServletContext object prior to populating the action properties and executing
the action method. In your setServletContext method you need to assign the
ServletContext object to a class variable. Like this.
private ServletContext servletContext;
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
You can then access the ServletContext object from any point in your action class through
the servletContext variable.
ServletRequestAware
This interface has a setServletRequest method whose signature is as follows.
void setServletRequest(javax.servlet.http.HttpServletRequest
servletRequest)
Now you can access the HttpServletRequest object via the servletRequest reference.
ServletResponseAware
The setServletResponse method is the only method defined in ServletResponseAware.
Here is its signature.
void setServletResponse(javax.servlet.http.HttpServletResponse
servletResponse)
Implement this interface if you need to access the HttpServletResponse object from your
action class. When an action is invoked, Struts checks to see if the action class implements
ServletResponseAware. If it does, Struts calls its setServletResponse method passing
the current HttpServletResponse object. You need to assign the passed object to a class
variable. Here is an example of how to do it.
private HttpServletResponse servletResponse;
public void setServletResponse(HttpServletResponse
servletResponse) {
this.servletResponse = servletResponse;
}
You can now access the HttpServletResponse object via the servletResponse variable.
SessionAware
If you need access to the HttpSession object from within your action class, implementing
the SessionAware interface is the way to go. The SessionAware interface is a little
different from its three other counterparts discussed earlier. Implementing SessionAware
does not give you the current HttpSession instance but a java.util.Map. This may be
confusing at first, but let's take a closer look at the SessionAware interface.
This interface only has one method, setSession, whose signature is this.
void setSession(java.util.Map map)
Struts will call the setSession method of an implementing action class when the action is
invoked. Upon doing so, Struts will pass an instance of
org.apache.struts2.dispatcher.SessionMap, which extends java.util.AbstractMap,
which in turn implements java.util.Map.SessionMap is a wrapper for the current
HttpSession object and maintains a reference to the HttpSession object.
The reference to the HttpSession object inside SessionMap is protected, so you won't be
able to access it directly from your action class. However, SessionMap provides methods
that make accessing the HttpSession object directly no longer necessary. Here are the
public methods defined in the SessionMap class.
public void invalidate()
Invalidates the current HttpSession object. If the HttpSession object has not been
created, this method exits gracefully.
public void clear()
Removes all attributes in the HttpSession object. If the HttpSession object has not been
created, this method does not throw an exception.
public java.util.Set entrySet() {
Returns a Set of attributes from the HttpSession object. If the HttpSession object is null,
this method returns an empty set.
public java.lang.Object get(java.lang.Object key)
Returns the session attribute associated with the specified key. It returns null if the
HttpSession object is null or if the key is not found.
public java.lang.Object put(java.lang.Object key,
java.lang.Object value)
Stores a session attribute in the HttpSession object and returns the attribute value. If the
HttpSession object is null, it will create a new HttpSession object.
public java.lang.Object remove(java.lang.Object key)
Removes the specified session attribute and returns the attribute value. If the HttpSession
object is null, this method returns null.
For example, to invalidate the session object, call the invalidate method on the
SessionMap:
if (session instanceof org.apache.struts2.dispatcher.SessionMap) {
((SessionMap) session).invalidate();
}
Listing 3.3.
Listing
3.4. This class has two properties (userName and password) and implements
ServletContextAware, ServletRequestAware, ServletResponseAware, and
SessionAware to provide access to resources. Note that to save space the get and set
methods for the properties are not shown.
HttpServletRequest servletRequest) {
this.servletRequest = servletRequest;
}
public void setSession(Map map) {
this.sessionMap = map;
}
public void setServletResponse(
HttpServletResponse servletResponse) {
this.servletResponse = servletResponse;
}
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
public String login() {
String referrer = servletRequest.getHeader("referer");
if (referrer != null && userName.length() > 0
&& password.length() > 0) {
int onlineUserCount = 0;
synchronized (servletContext) {
try {
onlineUserCount = (Integer) servletContext
.getAttribute("onlineUserCount");
} catch (Exception e) {
}
servletContext.setAttribute("onlineUserCount",
onlineUserCount + 1);
}
return "success";
} else {
return "input";
}
}
/*
* The onlineUserCount is accurate only if we also
* write a javax.servlet.http.HttpSessionListener
* implementation and decrement the
* onlineUserCount attribute value in its
* sessionDestroyed method, which is called by the
* container when a user session is inactive for
* a certain period of time.
*/
public String logout() {
if (sessionMap instanceof SessionMap) {
((SessionMap) sessionMap).invalidate();
}
int onlineUserCount = 0;
synchronized (servletContext) {
try {
onlineUserCount = (Integer) servletContext
.getAttribute("onlineUserCount");
} catch (Exception e) {
}
servletContext.setAttribute("onlineUserCount",
onlineUserCount - 1);
}
return "success";
}
}
The User class can be used to manage user logins and maintain the number of users
currently logged in. In this application a user can log in by typing in a non-empty user name
and a non-empty password in a Login form.
You can access the HttpServletRequest object because the User class implements
ServletRequestAware. As demonstrated in the login method, that gets invoked every
time a user logs in, you retrieve the referer header by calling the getHeader method on
the servletRequest object. Verifying that the referer header is not null makes sure that
the action was invoked by submitting the Login form, not by typing the URL of the
User_input action. Next, the login method increments the value of the application
attribute onlineUserCount.
The logout method invalidates the HttpSession object and decrements onlineUserCount.
Therefore, the value of onlineUserCount reflects the number of users currently logged in.
You can test this application by invoking the User_input action using this URL:
http://localhost:8080/app03a/User_input.action
You will see the Login form like the one in Figure 3.1. You can log in by entering a nonempty user name and a non-empty password. When you submit the form, the User_login
action will be invoked. If login is successful, you'll see the second page that looks like the
one in
Every time the action MyAction is invoked, its siteId property will be set to "california0l" and
its siteType property to "retail."
Results
An action method returns a String that determines what result to execute. An action
declaration must contain result elements that each corresponds to a possible return value
of the action method. If, for example, an action method returns either Action.SUCCESS or
Action.INPUT, the action declaration must have two result elements like these
name. The name of the result that matches the output of the action method. For
example, if the value of the name attribute is "input," the result will be used if the
action method returns "input." The name attribute is optional and its default value is
"success."
type. The result type. The default value is "dispatcher," a result type that forwards
to a JSP.
The default values of both attributes help you write shorter configuration. For example,
these result elements
<result name="success type="dispatcher">/Product.jsp</result>
<result name="input" type="dispatcher">/ProductForm.jsp</result>
The first result element does not have to contain the name and type attributes as it uses
the default values. The second result element needs the name attribute but does not need
the type attribute.
Dispatcher is the most frequently used result type, but it's not the only type available.
Table 3.1 shows all standard result types. The words in brackets in the Result Type
column are names used to register the result types in the configuration file. That's right,
you must register a result type before you can use it.
Result Type
Description
Chain (chain)
Dispatcher (dispatcher)
FreeMarker (freemarker)
Result Type
Description
HttpHeader (httpheader)
Redirect (redirect)
Stream (stream)
Velocity (velocity)
XSLT (xslt)
PlainText (plaintext)
Chain
The Chain result type is there to support action chaining, whereby an action is forwarded to
another action and the state of the original action is retained in the target action. The
Chaining interceptor makes action chaining possible and since this interceptor is part of
defaultStack, you can use action chaining right away.
The following declarations show an example of action chaining.
<package name="package1" extends="struts-default">
<action name="action1" class="...">
<result type="chain">action2</result>
</action>
<action name="action2" class="...">
<result type="chain">
<param name="actionName">action3</param>
<param name="namespace">/namespace2</param>
</result>
</action>
</package>
<package name="package2" namespace="/namespace2"
extends="struts-default">
<action name="action3" class="...">
<result>/MyView.jsp</result>
</action>
</package>
Dispatcher
The Dispatcher result type is the most frequently used type and the default type. This result
type has a location parameter that is the default parameter. Since it is the default
parameter, you can either pass a value to it by using the param element like this:
<result name="...">
<param name="location">resource</param>
</result>
Use this result type to forward to a resource, normally a JSP or an HTML file, in the same
application. You cannot forward to an external resource and its location parameter cannot
be assigned an absolute URL. To direct to an external resource, use the Redirect result type.
As almost all accompanying applications in this book utilize this result type, a separate
example is not given here.
FreeMarker
This result type forwards to a FreeMarker template. See
details.
HttpHeader
This result type is used to send an HTTP status to the browser. For example, the app03a
application has this action declaration:
<default-action-ref name="CatchAll"/>
<action name="CatchAll">
<result type="httpheader">
<param name="status">404</param>
</result>
</action>
The default-action-ref element is used to specify the default action, which is the action
that will be invoked if a URI does not have a matching action. In the example above, the
CatchAll action is the default action. CatchAll uses a HttpHeader result to send a 404
status code to the browser. As a result, if there's no matching action, instead of getting
Struts' error messages:
Struts Problem Report
Struts has detected an unhandled exception:
Messages: There is no Action mapped for namespace / and action name
blahblah
the user will get a 404 status report and will see a default page from the container.
Redirect
This result type redirects, instead of forward, to another resource. This result type accepts
these parameters
The main reason to use a redirect, as opposed to a forward, is to direct the user to an
external resource. A forward using Dispatcher is preferable when directing to an internal
resource because a forward is faster. Redirection would require a round trip since the client
browser would be forced to re-send a new HTTP request.
Having said that, there is a reason why you may want to redirect to an internal resource.
You normally redirect if you don't want a page refresh invokes the previously invoked
action. For instance, in a typical application, submitting a form invokes a Product_save
action, that adds a new product to the database. If this action forwards to a JSP, the
Address box of the browser will still be showing the URL that invoked Product_save. If the
user for some reason presses the browser's Reload or Refresh button, the same action will
be invoked again, potentially adding the same product to the database. Redirection removes
the association with the previous action as the redirection target has a new URL.
Here is an example of redirecting to an external resource.
<action name="..." class="...">
<result name="success" type="redirect">
http://www.example.com/test.html
</result>
</action>
When redirecting to an internal resource, you specify a URI for the resource. The URI can
point to an action. For instance,
<action name="..." class="...">
<result name="success" type="redirect">
User_input.action
</result>
</action>
In the last two examples, the target object was a resource relative to the current URL.
Redirect does not care if the target is a JSP or an action, it always treat it as if the target is
another page. Contrast this with the Redirect Action result type explained in the next
section.
The underlying class for the Redirect result type calls
HttpServletResponse.sendRedirect. Consequently, the action that was just executed is
lost and no longer available. If you need the state of the source action available in the
target destination, you can pass data through the session or request parameters. The
RedirectTest action below redirects to the User_input action and passes the value of the
userName property of the TestUser action class as a userName request parameter. Note
that the dynamic value is enclosed in ${ and }.
<action name="RedirectTest" class="app03a.TestUser">
<result type="redirect">
User_input.action?userName=${userName}
</result>
</action>
Note also that you need to encode special characters such as & and + . For example, if the
target is
Redirect Action
This result type is similar to Redirect. Instead of redirecting to a different resource,
however, Redirect Action redirects to another action. The Redirect Action result type can
take these parameters:
actionName. Specifies the name of the target action. This is the default attribute.
namespace. The namespace of the target action. If no namespace parameter is
present, it is assumed the target action resides in the same namespace as the
enclosing action.
For example, the following Redirect Action result redirects to a User_input action.
<result type="redirect-action">
<param name="actionName">User_input</param>
</result>
And since actionName is the default parameter, you can simply write:
<result type="redirect-action">User_input</result>
Note that the value of the redirection target is an action name. There is no .action suffix
necessary as is the case with the Redirect result type.
In addition to the two parameters, you can pass other parameters as request parameters.
For example, the following result type
<result type="redirect-action">
<param name="actionName">User_input</param>
<param name="userId">xyz</param>
<param name="area">ga</param>
</result>
Stream
This result type does not forward to a JSP. Instead, it sends an output stream to the
browser. See
Velocity
This result type forwards to a Velocity template. See
XSLT
This result type uses XML/XSLT as the view technology. This result type is explained further
in
PlainText
A PlainText result is normally used for sending a JSP's source. For example, the action
Source_show below displays the source of the Menu.jsp page.
<action name="Source_show" class="...">
<result name="success" type="plaintext">/jsp/Menu.jsp</result>
</action>
your code may have bugs that are not known at the time you deploy your application. Any
uncaught exception will result in an embarrassing HTTP 500 code (internal error).
Fortunately for Struts programmers, Struts lets you catch whatever you cannot catch in
your action classes by using the exception-mapping element in the configuration file.
This exception-mapping element has two attributes, exception and result. The exception
attribute specifies the exception type that will be caught. The result attribute specifies a
result name, either in the same action or in the global-results declaration, that will be
executed if an exception is caught. You can nest one or more exception-mapping elements
under your action declaration. For example, the following exception-mapping element
catches all exceptions thrown by the User_save action and executes the error result.
<action name="User_save" class="...">
<exception-mapping exception="java.lang.Exception"
result="error"/>
<result name="error">/jsp/Error.jsp</result>
<result>/jsp/Thanks.jsp</result>
</action>
You can also provide a global exception mapping through the use of the global-exceptionmappings element. Any exception-mapping declared under the global-exception-mappings
element must refer to a result in the global-results element. Here is an example of globalexception-mappings.
<global-results>
<result name="error">/jsp/Error.jsp</result>
<result name="sqlError">/jsp/SQLError.jsp</result>
</global-results>
<global-exception-mappings>
<exception-mapping exception="java.sql.SQLException"
result="sqlError"/>
<exception-mapping exception="java.lang.Exception"
result="error"/>
</global-exception-mappings>
Behind the scenes is the Exception interceptor that handles all exceptions caught. Part of
the default stack, this exception adds two objects to the Value Stack (which you'll learn in
Chapter 4, "OGNL"), for every exception caught by an exception-mapping element.
This way, you can display the exception message or the stack trace in your view, if you so
choose. The property tag that you will learn in Chapter 5, "Form Tags" can be used for this
purpose:
<s:property value="exception.message"/>
<s:property value="exceptionStack"/>
Wildcard Mapping
A large application can have dozens or even a hundred action declarations. These
declarations can clutter the configuration file and make it less readable. To ease this
situation, you can use wildcard mapping to merge similar mappings to one mapping.
Consider these package and action declarations.
<package name="wildcardMappingTest" namespace="/wild"
extends="struts-default">
<action name="Book_add" class="app03a.Book" method="add">
<result>/jsp/Book.jsp</result>
</action>
</package>
You can invoke the Book_add action by using this URI that contains the combination of the
package namespace and the action name:
/wild/Book_add.action
However, if there is no action with the name Book_add, Struts will match the URI with any
action name that includes the wildcard character *. For example, the same URI will invoke
the action named *_add if Book_add does not exist.
Now consider this package declaration.
<package name="wildcardMappingTest" namespace="/wild"
extends="struts-default">
<action name="*_add" class="app03a.Book" method="add">
<result>/jsp/Book.jsp</result>
</action>
</package>
The action in the package above can be invoked using any URI that contains the correct
namespace and _add, including
/wild/Book_add.action
/wild/Author_add.action
/wild/_add.action
/wild/Whatever_add.action
If more than one wildcard match was found, the last one found prevails. In the following
example, the second action will always get invoked.
<package name="wildcardMappingTest" namespace="/wild"
extends="struts-default">
<action name="*_add" class="app03a.Book" method="add">
<result>/jsp/Book.jsp</result>
</action>
<action name="*" class="app03a.Author" method="add">
<result>/jsp/Author.jsp</result>
</action>
</package>
If multiple matches were found, the pattern that does not use a wildcard character wins.
Look at these action declarations again:
<package name="wildcardMappingTest" namespace="/wild"
extends="struts-default">
<action name="Book_add" class="app03a.Book" method="add">
<result>/jsp/Book.jsp</result>
</action>
<action name="*_add" class="app03a.Author" method="add">
<result>/jsp/Author.jsp</result>
</action>
</package>
The URI /wild/Book_add.action matches both actions. However, since the first action
declaration does not use a wildcard character, it will take precedence over the second.
There's more to it.
The part of the URI that was matched by the wildcard is available as {1}. What it means is
if you use the URI /wild/MyAction_add.action and it matches an action whose name is
*_add, {1} will contain MyAction. You can then use {1} to replace other parts of the
configuration.
For instance, using both * and {1} the action declarations
<package name="wildcardMappingTest" namespace="/wild"
extends="struts-default">
<action name="Book_add" class="app03a.Book" method="add">
<result>/jsp/Book.jsp</result>
</action>
<action name="Author_add" class="app03a.Author" method="add">
<result>/jsp/Author.jsp</result>
</action>
</package>
The URI /wild/Book_add.action will invoke the action *_add, where "Book" was matched by
* . The class name will be app03a.Book and the JSP to forward to will be Book.jsp.
Using /wild/Author_add.action, on the other hand, will also invoke the action *_add, where
"Author" was matched by *. The class name will be app03a.Author and the JSP to forward
to will be Author.jsp.
If you try /wild/Whatever_add.action, it will still match the action *_add. However, it will
throw an exception because there are no Whatever class and Whatever.jsp JSP.
Using multiple wildcards is possible. Consider the following:
<package name="wildcardMappingTest" namespace="/wild"
extends="struts-default">
<action name="Book_add" class="app03a.Book" method="add">
<result>/jsp/Book.jsp</result>
</action>
<action name="Book_edit" class="app03a.Book" method="edit">
<result>/jsp/Book.jsp</result>
</action>
<action name="Book_delete" class="app03a.Book" method="delete">
<result>/jsp/Book.jsp</result>
</action>
<action name="Author_add" class="app03a.Author" method="add">
<result>/jsp/Author.jsp</result>
</action>
<action name="Author_edit" class="app03a.Author" method="edit">
<result>/jsp/Author.jsp</result>
</action>
<action name="Author_delete" class="app03a.Author"
method="delete">
<result>/jsp/Author.jsp</result>
</action>
</package>
You've seen that Book_add and Author_add can be combined into *_add. By extension,
Book_edit and Author_edit can also merge, and so can Book_delete and Author_delete. If
you note that an action name contains the combination of the action class name and the
action method name and realizing that {1} contains the first replacement and {2} the
second replacement, you can shorten the six action declarations above into this.
<package name="wildcardMappingTest" namespace="/wild"
extends="struts-default">
<action name="*_*" class="app03a.{1}" method="{2}">
<result>/jsp/{1}.jsp</result>
</action>
</package>
For example, the URI /wild/Book_edit.action will match *_*. The replacement for the first *
is Book and the replacement for the second * is edit. Therefore, {1} will contain Book and
{2} will contain edit. /wild/Book_edit.action consequently will invoke the app03a.Book class
and execute its edit method.
Note
{0} contains the whole URI.
Note also that * matches zero or more characters excluding the slash ('/') character. To
include the slash character, use **. To escape a character, use the '\' character.
As a result, the execute method on Book will be invoked. However, using the bang notation
you can invoke a different method in the same action. The URI /Book!edit.action, for
example, will invoke the edit method on Book.
You are not recommended to use dynamic method invocation because of security concerns.
You wouldn't want your users to be able to invoke methods that you do not expose.
By default, dynamic method invocation is enabled. The default.properties file specifies a
value of true for struts.enable.DynamicMethodInvocation:
struts.enable.DynamicMethodInvocation = true
To disable this feature, set this key to false, either in a struts.properties file or in a
struts.xml file using a constant element like this:
<constant name="struts.enable.DynamicMethodInvocation"
value="false" />
action.setPassword("secret");
String result = action.execute();
if ("success".equals(result)) {
// action okay
} else
// action not okay
}
Summary
Struts solves common problems in web application development such as page navigation,
input validation, and so on. As a result, you can concentrate on the most important task in
development: writing business logic in action classes. This chapter explained how to write
effective action classes as well as related topics such as the default result types, global
exception mapping, wildcard mapping, and dynamic method invocation.
Chapter 4. OGNL
The view in the Model-View-Controller (MVC) pattern is responsible for displaying the model
and other objects. To access these objects from a JSP, you use OGNL (Object-Graph
Navigation Language), the expression language Struts inherits from WebWork.
OGNL can help you do the following.
Bind GUI elements (text fields, check boxes, etc) to model objects and converts
values from one type to another.
Bind generic tags with model objects.
Create lists and maps on the fly, to be used with GUI elements.
Invoke methods. You can invoke any method, not only getters and setters.
OGNL is powerful, but only part of its power is relevant to Struts developers. This chapter
discusses OGNL features that you will need for Struts projects. If you're interested in
learning other features of OGNL, visit these websites.
http://www.opensymphony.com/ognl
http://www.ognl.org
Note
After reading this chapter the first time, do not worry if you don't get a firm understanding
of OGNL. Just skip to the next chapter and see how OGNL is used in form tags and generic
tags. Once you've started using it, you can revisit this chapter for reference.
Note
The term Value Stack is often used to refer to the Object Stack in the Value Stack.
The following are the maps that are pushed to the Context Map.
parameters. A Map that contains the request parameters for the current request.
request. A Map containing all the request attributes for the current request.
session. A Map containing the session attributes for the current user.
application. A Map containing the ServletContext attributes for the current
application.
attr. A Map that searches for attributes in this order: request, session, and
application.
You can use OGNL to access objects in the Object Stack and the Context Map. To tell the
OGNL engine where to search, prefix your OGNL expression with a # if you intend to access
the Context Map. Without a #, search will be conducted against the Object Stack.
Note
A request parameter always returns an array of Strings, not a String. Therefore, to access
the number of request parameters, use this
#parameters.count[0]
and not
#parameters.count
object.propertyName
object['propertyName' ]
object["propertyName" ]
Object stack objects can be referred to using a zero-based index. For example, the top
object in the Object Stack is referred to simply as [0] and the object right below it as [1].
For example, the following expression returns the value of the message property of the
object on top:
[0].message
The index [n] specifies the starting position for searching, rather than the object to search.
The following expression searches from the third object in the stack for the property user.
[2]["user"]
If you want a search to start from the top object, you can remove the index entirely.
Therefore,
[0].password
is the same as
password
Note also that if the returned value has properties, you can use the same syntax to access
the properties. For instance, if a Struts action has an address property that is returns an
instance of Address, you can use the following expression to access the streetNumber
property of the address property of the action.
[0].address.streetNumber
For example, the following expression returns the value of the session attribute code.
#session.code
This expression returns the contactName property of the request attribute customer.
#request["customer"]["contactName"]
The following expression tries to find the lastAccessDate attribute in the request object. If
no attribute is found, the search will continue to the session and application objects.
#attr['lastAccessDate']
@app04a.Util@now()
Here object represents a reference to an Object Stack object. You use the same syntax as
when accessing a property. For example, this refers to the first object in the stack:
[0]
You can also call an array's length field to find out how many elements it has. For example,
this returns 3.
colors.length
Listing 4.4
You can enquiry about a List's size by calling its size method or the special keyword size.
The following returns the number of elements in countries.
countries.size
countries.size()
The isEmpty keyword or a call to its isEmpty method tells you whether or not a List is
empty.
countries.isEmpty
countries.isEmpty()
You can also use OGNL expressions to create Lists. This feature will come in handy when
you're working with form tags that require options such as select and radio. To create a
list, you use the same notation as when declaring an array in Java. For example, the
following expression creates a List of three Strings:
{"Alaska", "California", "Washington"}
The following creates a List of two Integers. The primitive elements will be automatically
converted to Integers.
{6, 8}
or
cities['CA']
You can use size or size() to get the number of key/value pairs in a Map.
cities.size
cities.size()
And yes, you can access the Maps in the Context Map too. Just don't forget to use a #
prefix. For example, the following expression accesses the application Map and retrieves the
value of "code":
#application["code"]
There can be empty spaces between a key and the colon and between a colon and a value.
For example, the cities Map can be rewritten by this OGNL expression:
#{ "CA":"Sacramento", "WA":"Olympia", "UT":"Salt Lake City" }
This will be useful when you have started working with tags that need options, such as radio
and select.
However, you can achieve the same using this shorter JSP Expression Language expression:
${serverValue}
Also, there's no easy way to use Struts custom tags to print a request header. With EL, it's
easy. For instance, the following EL expression prints the value of the host header:
${header.host}
You will therefore find it practical to use OGNL and EL together. The EL is explained in
Appendix B, "The Expression Language."
Summary
The view in the Model-View-Controller (MVC) pattern is responsible for displaying the model
and other objects and you use OGNL to access the objects. This chapter discussed the Value
Stack that stores the action and context objects and explained how to use OGNL to access
them and create arrays, lists, and maps.
A tag attribute can be assigned a static value or an OGNL expression. If you assign an OGNL
expression, the expression will be evaluated if you enclose it with %{ and }. For instance,
the following label attribute is assigned the String literal "userName"
label="userName"
This one is assigned an OGNL expression userName, and the value will be whatever the
value of the userName action property is:
label="%{userName}"
This one assigns the label attribute the value of the session attribute userName:
label="%{#session.userName}"
Common Attributes
Tag classes of all Struts tags are part of the org.apache.struts2.components package
and all UI tags are derived from the UIBean class. This class defines common attributes
that are inherited by the UI tags.
Name
Data
Type
Description
cssClass
String
cssStyle
String
title
String
disabled
String
label*
String
Specifies the label for a form element in the xhtml and ajax
theme.
labelPosition*
String
key
String
requiredposition* String
name
String
Name
Description
Data
Type
tabIndex
String
value
String
An attribute name with an asterisk indicates that the attribute is only available if a nonsimple theme is used. Themes are explained toward the end of this chapter.
The name attribute is probably the most important one. In an input tag it maps to an action
property. Other important attributes include value, label, and key. The value attribute
holds the user value. You seldom use this attribute in an input tag unless the input tag is a
hidden field.
By default, each input tag is accompanied by a label element. The label attribute specifies
the text for the label element. The key attribute is a shortcut for the name and label
attributes. If the key attribute is used, the value assigned to this attribute will be assigned
to the name attribute and the value returned from the call to getText(key) will be
assigned to the label attribute. In other words,
key="aKey"
is the same as
name="aKey" label="%{getText('aKey')}"
If both the key and name attributes are present, the explicit value for name takes
precedence and the label attribute is assigned the result of getText(key). If the key
attribute and the label attribute are present, the value assigned to the label attribute will
be used.
The key attribute will be discussed further in
Name
Data Type
Description
templateDir String
theme
String
template
String
Name
Data Type
Description
onclick
String
ondblclick
String
onmousedown String
onmouseup
String
onmouseover String
onmouseout
String
onfocus
String
onblur
String
onkeypress
String
onkeyup
String
Name
Data Type
Description
onkeydown
String
onselect
String
onchange
String
Name
tooltip
Description
Data
Type
String The text used as a tooltip.
String The delay (in milliseconds) from the time the mouse hovers over
the tooltip icon to the time the tooltip is shown. The default
value is 500.
Name
Data
Type
acceptcharset String
Default Value
Description
Name
Data
Type
Default Value
Description
String
enctype
String
method
String
namespace
onsubmit
current
namespace
String
openTemplate String
portletMode String
target
String
validate
Boolean false
windowState String
An input field nested within a form tag is rendered as a table row. The row has two fields,
one for a label and one for the input element. A submit button is translated into a table row
with a single cells that occupies two columns. For instance, the following tags
<s:form action="...">
<s:textfield name="userName" label="User Name"/>
<s:password name="password" label="Password"/>
<s:submit/>
</s:form>
are rendered as
<form id="User_login" name="User_login" onsubmit="return true;"
action="..." method="POST">
<table class="wwFormTable">
<tr>
<td class="tdLabel">
<label for="User_login_userName" class="label">
User Name:
</label>
</td>
<td>
<input type="text" name="userName" value=""
id="User_login_userName"/>
</td>
</tr>
<tr>
<td class="tdLabel">
<label for="User_login_password" class="label">
Password:
</label>
</td>
<td>
<input type="password" name="password"
id="User_login_password"/>
</td>
</tr>
<tr>
<td colspan="2">
<div align="right">
<input type="submit" id="User_login_0" value="Submit"/>
</div>
</td>
</tr>
</table>
</form>
You can change the default layout by changing the theme. Themes are discussed in the
section "Themes" near the end of this chapter.
Table 5.6.
Table 5.6. textfield and password tags attributes
Name
Data
Type
Description
Default
Value
maxlength integer
size
integer
The password tag extends textfield by adding a showPassword attribute. This attribute
takes a boolean value and its default value is false. It determines whether or not the
entered value will be redisplayed when the containing form fails to validate. A value of true
redisplays the password when control is redirected back to the form.
For example, the following password tag has its showPassword attribute set to true.
<s:form action="Product_save">
<s:password key="password" showPassword="true"/>
. . .
</s:form>
The TextField action in the app05a application shows how you can use the textfield,
password, and hidden tags. The action is associated with the TextFieldTestAction class
in
Table 5.7.
Name
Data
Type
Description
Default
Value
action String
align
String
method String
type
String
Table 5.8.
Name
Data
Type
Description
Default
Value
action String
align
String
method String
type
String
Table 5.9.
String
Description
The HTML for attribute
Table 5.10.
integer
readonly boolean
Description
The HTML cols attribute.
false
rows
Integer
wrap
boolean
</body>
</html>
To test this example, direct your browser to this URL:
http://localhost:8080/app05a/TextArea.action
Name
fieldValue String
true
Description
The actual value of the checkbox.
Like other input elements, an HTML checkbox adds a request parameter to the HTTP request
when the containing form is submitted. The value of a checked checkbox is "on." If the
name of the checkbox element is subscribe, for example, the key/value pair of the
corresponding request parameter is
subscribe=on
However, an unchecked checkbox does not add a request parameter. It would be good if it
sent this:
subscribe=off
If the checkbox is checked when the containing form is submitted, both values (the check
box and the hidden value) will be sent to the server. If the checkbox is not checked, only
the hidden field is sent, and the absence of the checkbox parameter indicates that the
checkbox was unchecked. The Checkbox interceptor helps make sure the property setter
gets invoke regardless the state of the checkbox. A checked checkbox will pass the String
literal "true" to the property setter and an unchecked one will pass the String literal
"false."
The last checkbox is disabled and its value cannot be changed. Sometimes you may want to
display a disabled checkbox to show the user a default selection that cannot be changed.
Now, let's look at another great feature of the checkbox tag.
The checkbox tag has a fieldValue attribute that specifies the actual value that is sent to
the server when the containing form of a checked checkbox is submitted. If no fieldValue
attribute is present, the value of the checkbox is either "true" or "false." If it is present
and the checkbox was checked, the value of the fieldValue is sent. If the fieldValue
attribute is present and the checkbox is unchecked, no request parameter associated with
the checkbox will be sent.
This attribute can be used to send selected values of a series of checkboxes. For example,
the CheckBoxTest2Action class in Listing 5.7 has a getter that returns a list of
Magazine objects. You can use the checkbox tag and the fieldValue attribute to
construct the same number of checkboxes as the number of magazines on the list, as
shown in the CheckBox2.jsp page in
code.
</s:form>
</body>
</html>
The iterator tag will iterate over the magazine list and will be explained in
"Generic Tags." The whole form will be rendered as
Chapter 6,
<form ...>
<input type="checkbox" name="magazines" value="034" .../>
<input type="hidden" name="__checkbox_magazines" value="034" />
<input type="checkbox" name="magazines" value="122" .../>
<input type="hidden" name="__checkbox_magazines" value="122" />
<input type="checkbox" name="magazines" value="434" />
<input type="hidden" name="__checkbox_magazines" value="434" />
<input type="checkbox" name="magazines" value="906" />
<input type="hidden" name="__checkbox_magazines" value="906" />
</form>
All checkboxes have the same name (magazines) which means their values are linked to an
array or a collection. If a checkbox is checked, its value (magazine code) will be sent. If it is
not, its value will not be sent. As such, you'll know which magazines have been selected.
You can test this example by using this URL:
http://localhost:8080/app05a/CheckBox2.action
The checkboxes are shown in Figure 5.4. Note that there are four checkboxes
constructed since there are four magazines on the list.
Note
The checkboxlist tag renders multiple checkboxes too, but its layout is fixed. Using
checkbox tags, on the other hand, gives you more flexibility in laying out the rendered
elements.
Figure 5.5.
Figure 5.5. Radio buttons
As you can see, the radio set has a set of values (1, 2, 3) and a set of labels (Atlanta,
Chicago, Detroit). The value/label pairs are as follows.
1 - Atlanta
2 - Chicago
3 - Detroit
<select name="city">
<option value="l">Atlanta</option>
<option value="2">Chicago</option>
<option value="3">Detroit</option>
</select>
Note
In a select element, the value attribute is optional. If it is not present, the label will be sent
as the value when the corresponding option is selected. With radio buttons, the value
attribute is not required but when the value attribute is absent, "on" will be sent, and not
the label. Therefore, a radio button must always have the value attribute.
This section explains how you can use the list, listKey, and listValue attributes in the
radio, select, and other tags that require options. When you use these tags, you need to
have label/value pairs as the source of your options. Of the three attributes, the list
attribute is required and the other two are optional. You can assign a String, an array, a
java.util.Enumeration, a java.util.Iterator, a java.util.Map, or a Collection to the list
attribute. The object can be placed in an action object, in the session object, or the
ServletContext object.
Note
If the object you dynamically assign to the list attribute has no options, you must return an
empty array/Collection/Map instead of null.
Assigning A String
You can assign a String representation of an array. For example, the following select tag is
assigned a string.
<s:select list="{'Atlanta', 'Chicago', 'Detroit'}"/>
Note that each string element is used as both the value and the label.
Most of the time, you want to use values that are different from labels for your options. In
this case, the syntax is this:
#{'value-1': 'label-1', ' value-2':'label-2', ... 'value-n':'label-n'}
is rendered as
<select>
<option value="l">Atlanta</option>
<option value="2">Chicago</option>
<option value="3">Detroit</option>
</select>
Assigning a Map
You use a Map as the source for your options if the value of each option needs to be
different from the label. Using a Map is very straightforward. Put the values as the Map
keys and the labels as the Map values. For example, here is how to populate a Map called
cities with three cities:
Map<Integer, String> cities = new HashMap<Integer, String>();
cities.put(1, "Atlanta");
cities.put(2, "Chicago");
cities.put(3, "Detroit");
If cities is an action property, you can assign it to the list attribute. Like this:
<s:select list="cities"/>
For example, assuming that the action object's getCities method return a List of City
objects with an id and a name properties, you would use the following to assign the List to
a select tag.
<s:select list="cities" listKey="id" listValue="name" />
You will see more examples in the sections to come.
Name
Data
Type
Default
Value
Description
list*
String
listKey
String
The property of the object in the list that will supply the
option values.
listValue String
The property of the object in the list that will supply the
option labels.
The following example uses two radio tags to get the user type and the income level on a
club membership form. The first tag gets its options from a hardcoded list and the second
tag gets its options from a Map.
The RadioTestAction class in Listing 5.9 is the action class for this example. Note that
the incomeLevels Map is a static variable that is populated inside a static block so that it's
only populated once for all instances of the action class.
<s:form>
<s:radio name="userType" label="User Type"
list="#{'1':'Individual', '2':'Organization'}"
/>
<s:radio name="incomeLevel" label="Income Level"
list="incomeLevels"
/>
<s:submit/>
</s:form>
</div>
</body>
</html>
To run the test, use this URL:
http://localhost:8080/app05a/Radio.action
Note that the first radio tag is rendered as two radio buttons, in accordance with the
number of hardcoded options. The second radio tag translates into four radio buttons
because it's linked to a Map with four elements.
Table 5.13.
Name
Data
Type
Default
Value
Description
headerKey
String
headerValue String
list*
String
listKey
String
listValue
String
multiple
boolean false
size
integer
The headerKey and headerValue attributes can be used to insert an option. For instance,
the following select tag inserts a header.
<s:select name="city" label="City"
headerKey="0" headerValue="[Select a city]"
list="#{'1':'Atlanta', '2':'Chicago', '3':'Detroit'}"
/>
The following example is used to let the user select a country and a city using two select
elements. The first select element displays three countries (US, Canada, Mexico) from a
Listing 5.12.
"Atlanta");
"Chicago");
"Detroit");
"Vancouver");
Listing 5.13.
Figure 5.8 shows the city options when US is selected and Figure 5.9 shows what
cities the user can choose when the country is Canada.
Table 5.14.
Name
Data
Type
Description
Default
Value
list*
String
listKey
String
The property of the object in the list that will supply the
option values.
listValue String
The property of the object in the list that will supply the
option labels.
For example, the OptGroupTestAction class in Listing 5.14 is an action class that has
three Map properties, usCities, canadaCities, and mexicoCities.
return city;
}
public void setCity(int city) {
this.city = city;
}
public Map<Integer, String> getUsCities() {
return usCities;
}
public Map<Integer, String> getCanadaCities() {
return canadaCities;
}
public Map<Integer, String> getMexicoCities() {
return mexicoCities;
}
}
The OptGroup.jsp page in Listing 5.15 shows how to use the optgroup tag to group
options in the select element in this example.
If you're curious, you can view the source and see that the select element is rendered as
these HTML tags.
<select name="city" id="OptGroup_city">
<option value=""></option>
<option value="2">Chicago</option>
<option value="1">Atlanta</option>
<option value="3">Detroit</option>
<optgroup label="Canada">
<option value="4">Vancouver</option>
<option value="6">Montreal</option>
<option value="5">Toronto</option>
</optgroup>
<optgroup label="Mexico">
<option value="8">Tijuana</option>
<option value="7">Mexico City</option>
</optgroup>
</select>
Table 5.15.
Table 5.15. checkboxlist tag attribute
Name
Data
Type
Description
Default
Value
list*
String
listKey
String
The property of the object in the list that will supply the
option values.
listValue String
The property of the object in the list that will supply the
option labels.
Listing 5.16 shows the CheckBoxListTestAction class, the action class for this
example, and the Interest class.
Listing 5.17 shows the CheckBoxList.jsp page that uses a checkboxlist tag.
Listing 5.17. The CheckBoxList.jsp page
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
<title>checkboxlist Tag Example</title>
<style type="text/css">@import url(css/main.css);</style>
</head>
<body>
<div id="global" style="width:450px">
<h3>Select Interests</h3>
<s:form>
<s:checkboxlist name="interests" label="Interests"
list="interestOptions"
listKey="id" listValue="description"
/>
<s:submit/>
</s:form>
</div>
</body>
</html>
You can run the action by directing your browser to this URL:
http://localhost:8080/app05a/CheckBoxList.action
Figure 5.11.
Table 5.16.
Table 5.16. combobox tag attribute
Name
Data
Type
Default
Value
Description
headerKey
integer
headerValue String
list*
String
listKey
String
Name
Data
Type
Description
Default
Value
the option values.
listValue
String
maxlength
integer
readonly
boolean false
size
integer
Unlike the select tag, the options for a combo box normally do not need keys. Also, the
label of the selected option, and not the value, is sent when the containing form is
submitted.
<body>
<div id="global" style="width:300px">
<h3>Select Car Make</h3>
<s:form>
<s:combobox name="make" label="Car Make" size="24"
headerKey="-1" headerValue="Select a make"
list="{ 'Ford', 'Pontiac', 'Toyota'}"
/>
<s:submit/>
</s:form>
</div>
</body>
</html>
Use this URL to test the action:
http://localhost:8080/app05a/ComboBox.action
Figure 5.12.
Figure 5.12. Using combobox
Figure 5.13).
Figure 5.13. Using updownselect
Name
Data
Type
Default
Value
Description
allowMoveUp
boolean true
allowSelectAll
boolean true
emptyOption
boolean false
headerKey
String
headerValue
String
list*
String
listKey
String
listValue
String
moveDownLabel String
moveUpLabel
String
multiple
boolean false
selectAllLabel
String
Name
size
Data
Type
Integer
Description
Default
Value
Note
When the form containing the updownselect tag fails to validate, the previously selected
value(s) of the updownselect tag is not retained.
Listing
5.20 shows an action class (UpDownSelectTestAction) for this example and Listing
5.21 the JSP that uses the tag.
The following example shows how to use updownselect to select multiple colors.
</body>
</html>
Use this URL to test the example:
http://localhost:8080/app05a/UpDownSelect.action
Figure 5.13.
Name
Data
Type
Default
Value
Description
addAllToLeftLabel
String
addAllToLeftOnclick
String
addAllToRightLabel
String
addAllToRightOnclick
String
addToLeftLabel
String
addToLeftOnclick
String
addToRightLabel
String
addToRightOnclick
String
Name
Data
Type
Default
Value
Description
boolean true
allowAddAllToRight
boolean true
allowAddToLeft
boolean true
allowAddToRight
boolean true
allowSelectAll
boolean true
allowUpDownOnLeft
boolean true
buttonCssClass
String
buttonCssStyle
String
doubleCssClass
String
doubleCssStyle
String
Name
Data
Type
Default
Value
Description
doubledDisabled
boolean false
doubleEmptyOption
boolean false
doubleHeaderKey
String
doubleHeaderValue
String
doubleId
String
doubleList*
String
doubleListKey
String
doubleListValue
String
doubleMultiple
boolean false
doubleName*
String
doubleSize
integer
emptyOption
boolean false
formName
String
Name
Data
Type
Default
Value
Description
headerKey
String
headerValue
String
leftDownLabel
String
leftTitle
String
leftUpLabel
String
list*
String
listKey
String
listValue
String
multiple
boolean
rightDownLabel
String
rightTitle
String
rightUpLabel
String
selectAllLabel
String
selectAllOnclick
String
size
integer
Name
Data
Type
Description
Default
Value
first selection.
upDownOnLeftOnclick String
upDownOnRightOnclick String
Note
Only selected (highlighted) options are sent to the server. Simply transferring an option to
the right select element does not make the option selected.
For example, the OptionTransferSelectTestAction class in Listing 5.22 is an action
class with a selectedLanguages property that is mapped to an optiontransferselect tag.
The tag is used in the OptionTransferSelect.jsp page in
Listing 5.23.
Figure 5.14.
Table 5.19.
Table 5.19. optiontransferselect attributes
Name
Data
Type
Default
Value
Description
doubleCssClass
String
doubleCssStyle
String
doubleDisabled
booelan false
Name
Data
Type
doubleEmptyOption
doubleHeaderKey
Default
Value
false
String
Description
doubleHeaderValue String
doubleId
String
doubleList*
String
doubleListKey
String
doubleListValue
String
doubleMultiple
boolean false
doubleName*
String
doubleSize
interger
doubleValue
String
emptyOption
boolean false
formName
String
Name
headerKey
Data
Type
Description
Default
Value
String
headerValue
list
listKey
String
listValue
String
multiple
boolean False
size
Integer
Listing 5.25.
Listing 5.24. The DoubleSelectTestAction class
package app05a;
import com.opensymphony.xwork2.ActionSupport;
public class DoubleSelectTestAction extends ActionSupport {
private String country;
private String city;
// getters and setters not shown
}
<head>
<title>doubleselect Tag Example</title>
<style type="text/css">@import url(css/main.css);</style>
<style>
select {
width:170px;
}
</style>
</head>
<body>
<div id="global" style="width:300px">
<s:form>
<s:doubleselect label="Select Location"
name="country"
list="{'US', 'Canada', 'Mexico'}"
doubleName="city"
doubleList="top == 'US' ?
{'Atlanta', 'Chicago', 'Detroit'}
: (top == 'Canada' ?
{'Vancouver', 'Toronto', 'Montreal'}
: {'Mexico City', 'Tijuana'})"
/>
<s:submit/>
</s:form>
</div>
</body>
</html>
To test the example, use this URL:
http://localhost:8080/app05a/DoubleSelect.action
Themes
Each UI tag in the Struts Tag Library is rendered to an HTML element or HTML elements.
Struts lets you choose how the rendering should happen. For instance, by default the form
tag is rendered as an HTML form element and a table element. Therefore,
<s:form></s:form>
is translated into
<form id="..." name="..." onsubmit="return true;" action="..."
method="post">
<table class="wwFormTable">
</table>
</form>
The table element is great for formatting because every input tag, such as textfield,
checkbox, and submit, will be rendered as an input element contained within a tr element
and td elements, accompanied by a label.
For example, this textfield tag
<s:textfield label="My Label">
will be rendered as
<tr>
<td class="tdLabel">
<label for="..." class="label">My Label:</label>
</td>
<td>
<input type="text" name="..." id="..."/>
</td>
</tr>
Since most forms are formatted in a table, this kind of rendering helps.
However, sometimes you do not want your textfield tag to be rendered as an input element
in tr and td's and, instead, want it to be translated as a lone <input> because you want to
apply your own formatting. Can you do this?
You can because each UI tag comes with several rendering templates you can choose. One
template renders <s:form> as a form and a table elements, but another translates the
same form tag into a form element, without a <table>. These templates are written in
FreeMarker, but you don't have to know FreeMarker to use these templates.
Similar templates are packaged together into a theme. A theme therefore is a collection of
templates that produce the same look and feels for all UI tags. There are currently four
themes available:
simple. Templates in the simple theme translate UI tags into their simplest HTML
equivalents and will ignore the label attribute. For example, using this theme a
<s:form> is rendered as a form element, without a table element. A textfield tag
translates into an input element without bells and whistles.
xhtml. The xhtml theme is the default theme. Templates in this collection provides
automatic formatting using a layout table. That's why a <s:form> is rendered as a
<form> and a <table>.
css_xhtml. Templates in this theme are similar to those in the xhtml theme but
rewritten to use CSS for layout.
ajax. This theme contains templates based on xhtml templates but provides
advanced AJAX features. AJAX programming will be discussed in Chapter 27, "AJAX".
All the templates from the four themes are included in the struts-core-VERSION.jar file,
under the template directory.
Now that you know how UI tags are rendered, it's time to learn how to choose a theme for
your UI tags.
As mentioned earlier, if you don't specify a theme, the templates in the xhtml theme will be
used. To easiest way to change a theme for a UI tag is by using the theme attribute of that
tag. For example, the following textfield tag uses the simple theme:
<s:textfield theme="simple" name="userId"/>
If the theme attribute is not present in a form input UI tag, the form's theme will be used.
For instance, the following tags all use the css_xhtml theme since the containing form uses
that theme, except for the last checkbox tag that uses the simple theme.
<s:form theme="css_xhtml">
<s:checkbox theme="simple" name="daily" label="Daily news alert"/>
<s:checkbox name="weekly" label="Weekly reports"/>
<s:checkbox theme="simple" name="monthly" label="Monthly reviews"
value="true" disabled="true"
/>
<s:submit/>
</s:form>
In addition to using the theme attribute, there are two other ways to select a theme:
1. By adding an attribute named theme to the page, request, session, or application
JSP implicit objects.
2. By assigning a theme to the struts.ui.theme property in the struts.properties file,
discussed in Appendix A, "Struts Configuration."
Summary
Struts comes with a tag library that include UI and non-UI tags. Some of the UI tags are
used for entering form values and are referred to as the form tags. In this chapter you have
learned all the tags in the form tags.
a
action
bean
date
debug
i18n
include
param
push
set
text
url
property
Note
Chapter 9,
"Message Handling." The debug tag is used for debugging and explained in Chapter
16, "Debugging and Profiling."
The i18n and text tags are related to internationalization and discussed in
if
elself
else
append
generator
iterator
merge
sort
subset
Each of the generic tags is discussed in the following sections. The accompanying samples
can be found in the app06a application.
Table
Name
Type
Default
Description
default String
value String
For instance, this property tag prints the value of the customerId action property:
<s:property value="customerId"/>
If the value attribute is not present, the value of the object at the top of the Value Stack
will be printed. By default, the property tag escapes HTML special characters in
Table
Character
Escaped Characters
"
"
&
&
<
<
>
>
Note that in many cases, the JSP Expression Language provides shorter syntax. For
example, the following EL expression prints the customerId action property.
${customerId}
The Property action in app06a demonstrates the use of the property tag. The action is
associated with the PropertyTestAction class (in
named temperature.
The a Tag
The a tag renders an HTML anchor. It can accept all attributes that the a HTML element can.
For example, this a tag creates an anchor that points to www.example.com.
<s:a href="http://www.example.com">Click Here</s:a>
This tag is of not much use, however the a tag in the AJAX tag library, discussed in Chapter
27, "AJAX," is very powerful.
Table 6.3.
Name
Type
Default
Description
executeResult
boolean false
flush
boolean true
Name
Type
Default
Description
component tag.
name*
String
namespace
String
the namespace
The namespace of the action to be
from where the tag invoked.
is used
var
String
For example, the following action tag causes the MyAction action to be executed. The
action object will also be accessible through the obj variable in the Value Stack's context
map.
<s:action var="obj" name="MyAction" executeResult="false"/>
Table 6.4.
Description
name String
value String
The value attribute is always evaluated even if it is written without the %{ and }. For
example, the value of the following param tag is the userName action property:
<s:param name="userName" value="userName"/>
It is the same as
<s:param name="userName" value="%{userName}"/>
To send a String literal, enclose it with single quotes. For example, the value of this param
tag is naomi.
<s:param name="userName" value="'naomi'"/>
The value attribute can also be written as text between the start and the end tags.
Therefore, instead of writing
<s:param name="..." value="..."/>
The second form allows you to pass an EL expression. For example, the following passes the
current host to the host parameter:
<s:param name="host">${header.host}</s:param>
Table 6.5.
Table 6.5. bean tag attributes
Description
name* String
var
The name used to reference the value pushed into the Value
Stack's context map.
String
Table 6.6.
Name
Type Default
Description
format String
name* String
nice
boolean false
var
String
The format attribute conforms to the date and time patterns defined for the
java.text.SimpleDateFormat class. For example, the Date.jsp page in
date tags to format dates.
Figure 6.3.
Figure 6.3. Using the date tag
Table 6.7.
Table 6.7. include tag attrbute
Description
The servlet/JSP whose output is to be included.
the
the
the
the
the
Table 6.8.
Description
name String
value String
scope String default The scope of the target variable. The value can be application,
session, request, page, or default.
However, as you can see from the Set.jsp page in Listing 6.7, you could also push the
variable customer to represents the Customer object in the Session map.
<s:set name="customer" value="#session.customer"/>
You can then refer to the Customer object simply by using these property tags.
<s:property value="#customer.contact"/>
<s:property value="#customer.email"/>
Figure 6.4.
Table 6.9.
Description
The value to be pushed to the value stack.
For example, the PushTestAction class in Listing 6.8 has an execute method that
places an Employee object in the HttpSession object.
implements SessionAware {
private Map sessionMap;
public void setSession(Map sessionMap) {
this.sessionMap = sessionMap;
}
public String execute() {
Employee employee = new Employee();
employee.setId(1);
employee.setFirstName("Karl");
employee.setLastName("Popper");
sessionMap.put("employee", employee);
return SUCCESS;
}
}
class Employee {
private int id;
private String firstName;
private String lastName;
// getters and setters not shown
}
The Push.jsp page in
Value Stack.
Table 6.10.
Name
Type Default
Description
action
String
anchor
String
encode
Boolean true
escapeAmp
Boolean true
includeParams String
get
Name
Type Default
Description
method
String
namespace
String
portletMode
String
portletlUrlType String
scheme
String
value
String
var
String
???
windowState
String
The url tag can be very useful. For example, this url tag creates a URL for the HTTPS
protocol and includes all the parameters in the current URL.
<s:url id="siteUrl" forceAddSchemeHostAndPort="true" value=""
includeparams="none" scheme="https"/>
Table 6.11.
Description
The test condition.
For instance, this if tag tests if the ref request parameter is null:
<s:if test="#parameters.ref == null">
And this trims the name property and tests if the result is empty.
<s:if test="name.trim() == ''">
In the following example, an if tag is used to test if the session attribute loggedIn exists. If
it is not found, a login form is displayed. Otherwise, a greeting is shown. The example relies
on the IfTestAction class in
Figure 6.6.
Name
Type
value String
Default
Description
The iterable object to iterate over.
status org.apache.struts2.views.jsp.
IteratorStatus
var
String
Upon execution, the iterator tag pushes an instance of IteratorStatus to the context map
and updates it at each iteration. The status attribute can be assigned a variable that points
to this IteratorStatus object.
The properties of the IteratorStatus object are shown in
Table 6.13.
Name
Type
Description
index
count
first
boolean The value is true if the current element is the first element in the
iterable object.
last
boolean The value is true if the current element is the last element in the
iterable object.
even
odd
modulus int
For example, the IteratorTestAction class in Listing 6.12 presents an action class with
two properties, interests and interestOptions, that return an array and a List,
respectively. The Iterator.jsp page in
iterate over an array or a Collection.
Another helpful use of iterator is to simulate a loop, similar to the for loop in Java. This is
easy to do since all an iterator needs is an array or another iterable object. The following
code creates a table containing four rows. The cells in each row contain two textfield tags
whose names are user[n].firstName and user[n].lastName, respectively. This is useful
when you need to generate a variable number of input boxes.
<table>
<s:iterator value="new int[3]" status="stat">
<tr>
<td><s:textfield
name="%{'users['+#stat.index+'].firstName'}"/></td>
<td><s:textfield
name="%{'users['+#stat.index+'].lastName'}"/></td>
</tr>
</s:iterator>
</table>
name="users[0].firstName"/></td>
name="users[0].lastName"/></td>
name="users[1].firstName"/></td>
name="users[1].lastName"/></td>
name="users[2].firstName"/></td>
name="users[2].lastName"/></td>
In this case, we generate an array of four ints. We do not need to initialize the array
elements since we're only using the array's status.count attribute.
The following example employs the modulus property of the IteratorStatus object to
format iterated elements in a four-column table.
<table border="1">
<s:iterator id="item" value="myList" status="status">
<s:if test="#status.modulus(4)==1">
<tr>
</s:if>
<td>${item}</td>
<s:if test="#status.modulus(4)==0">
</tr>
</s:if>
</s:iterator>
<%-- if the list size is not equally divisible by 4, we need to pad
with <td></td> and </tr> --%>
<s:if test="myList.size%4!=0">
<s:iterator value="new int[4 - myList.size%4]">
<td> </td>
</s:iterator>
</tr>
</s:if>
</table>
List 1, element 1
List 1, element 2
List 1, element 3
List 2, element 1
List 2, element 2
List 2, element 3
Table 6.14.
String
Description
The variable that will be created to reference the appended
iterators.
Also, see the merge tag, which is very similar to append. If you replace append with
merge in the example above, you will get
one
1
two
2
3
List
List
List
List
List
List
1,
2,
1,
2,
1,
2,
element
element
element
element
element
element
1
1
2
2
3
3
Table 6.15.
String
Description
The variable that will be created to reference the appended
iterators.
In the following example, the action class MergeTestAction provides three properties that
each returns a List: americanCars, europeanCars, and japaneseCars. The action class
is given in
Listing 6.15.
japaneseCars.add("Nissan");
japaneseCars.add("Toyota");
}
public List<String> getAmericanCars() {
return americanCars;
}
public List<String> getEuropeanCars() {
return europeanCars;
}
public List<String> getJapaneseCars() {
return japaneseCars;
}
}
The Merge.jsp page in
Table 6.16.
Name
Type
Default
Description
converter Converter
count
Integer
Name
Type
Default
Description
separator* String
val*
String
var
String
When used, the converter attribute must be set to an action property of type Converter,
an inner interface defined in the org.apache.struts2.util.IteratorGenerator class.
The use of the converter is depicted in the second example of this section.
The Generator.jsp page in
of Strings (car makes).
Figure 6.8.
Listing
6.15. This class has one property, myConverter, that returns an implementation of
IteratorGenerator.Converter. The Converter interface defines one method, convert,
whose signature is given as follows.
Object convert(String value) throws Exception
In a generator tag that has a converter, each element of the generated iterator will be
passed to this method.
Table 6.17.
Name
Type
Default
Description
comparator* java.util.Comparator
source
String
var
String
Note
It is a good design choice to leave data sorting to the presentation layer, even though it
may be easier to sort data at the model or data level using the ORDER BY clause in the SQL
statement. This is a design decision that should be considered carefully.
</div>
</body>
</html>
To see the elements in the iterators sorted, direct your browser to this URL:
http://localhost:8080/app06a/Sort.action
Table 6.18.
Name
Type Default
Description
count Integer
decider Decider
source String
start
Integer
var
String
You tell the subset tag how to create a subset of an iterator by using an instance of the
Decider class, which is an inner class of org.apache.struts2.util.SubsetIteratorFilter.
For example, the SubsetTestAction class in Listing 6.22 is a Decider. It will cause a
subset tag to include an element if the String representation of the element is more than
four characters long. The Subset.jsp page in
uses the Decider.
Summary
The Struts tag library comes with non-UI tags that are often referred to as generic tags.
These tags can be categorized into the data tags and the control tags and you've learned
every one of them in this chapter.
checking the request parameter an inelegant solution, it also defeats the purpose of using
Struts because Struts is capable of mapping request parameters to action properties.
So, what does Struts have to offer?
A failed type conversion will not necessarily stop Struts. There are two possible outcomes
for this misbehavior. Which one will happen depends on whether or not your action class
implements the com.opensymphone.xwork2.ValidationAware interface.
If the action class does not implement this interface, Struts will continue by invoking the
action method upon failed type conversions, as if nothing bad had happened.
If the action class does implement ValidationAware, Struts will prevent the action method
from being invoked. Rather, Struts will enquiry if the corresponding action element
declaration contains an input result. If so, Struts will forward to the page defined in the
result element. If no such result was found, Struts will throw an exception.
You can override the default error message by providing a key/value pair of this format:
invalid.fieldvalue.fieldName=Custom error message
Here, fieldName is the name of the field for which a custom error message is provided. The
key/value pair must be added to a ClassName.properties file, where ClassName is the
name of the class that contains the field that is the target of the conversion. Further, the
ClassName.properties file must be located in the same directory as the Java class.
In addition to customizing an error message, you can also customize its CSS style. Each
error message is wrapped in an HTML span element, and you can apply formatting to the
message by overriding the errorMessage CSS style. For example, to make type conversion
error messages displayed in red, you can add this to your JSP:
<style>
.errorMessage {
color:red;
}
</style>
A type conversion error customization example is given in the app07a application. The
directory structure of this application is shown in
Figure 7.1.
The Transaction action class in Listing 7.1 has four properties: accountId (String),
transactionDate (Date), amount (double), and transactionType (int). More
important, Transaction extends the ActionSupport class, thus indirectly implementing
ValidationAware.
String accountId;
Date transactionDate;
double amount;
int transactionType;
<action name="Transaction1">
<result>/jsp/Transaction.jsp</result>
</action>
<action name="Transaction2" class="app07a.Transaction">
<result name="input">/jsp/Transaction.jsp</result>
<result name="success">/jsp/Receipt.jsp</result>
</action>
Transaction1 simply displays the Transaction.jsp page, which contains a form and is
shown in Listing 7.2. Transaction2 has two result branches. The first one is executed if
the action method returns "input," as is the case when there is a type conversion error. The
second one is executed if no type conversion error occurs and forwards to the Receipt.jsp
page in
Listing 7.3.
<tr>
<td>Account ID:</td>
<td><s:property value="accountId"/>
</tr>
<tr>
<td>Transaction Date:</td>
<td><s:property value="transactionDate"/>
</tr>
<tr>
<td>Transaction Type:</td>
<td><s:property value="transactionType"/>
</tr>
<tr>
<td>Amount:</td>
<td><s:property value="amount"/>
</tr>
</table>
</div>
<s:debug/>
</body>
</html>
The Transaction.properties file, shown in Listing 7.4, overrides the type conversion
error message for the transactionDate field. This file must be located in the same
directory as the Transaction action class.
Figure 7.2.
To test the type conversion feature in Struts, I deliberately enter incorrect values in the
Transaction Date and Amount boxes. In the Transaction Date box I enter abcd and in the
Amount box I type 14,999.95. After the form is submitted, you will see the same form as
shown in
Figure 7.3.
What happened was abcd could not be converted to a Date. 14,999.50 looks like a valid
numerical value, but its formatting makes it a bad candidate for a double, the type of the
amount property. Had I entered 14999.50, Struts would happily have converted it to a
double and assigned it to the amount property.
The Transaction Date field is being adorned with the custom error message specified in the
Transaction.properties file. The Amount field is being accompanied by a default error
message since the Transaction.properties file does not specify one for this field.
An important thing to notice is that the wrong values are re-displayed. This is an important
feature since the user can easily see what is wrong with his/her form.
The TypeConverter interface has only one method, convertValue, whose signature is as
follows. Struts invokes this method and passes the necessary parameters whenever it needs
the converter's service.
java.lang.Object convertValue(java.util.Map context,
java.lang.Object target, java.lang.reflect.Member member,
java.lang.String propertyName, java.lang.Object value,
java.lang.Class toType);
context. The OGNL context under which the conversion is being performed.
target. The target object in which the property is being set
member. The class member (constructor, method, or field) being set
propertyName. The name of the property being set
The context argument is very useful as it contains references to the Value Stack and
various resources. For example, to retrieve the Value Stack, use this code:
ValueStack valueStack = (ValueStack)
context.get(ValueStack.VALUE_STACK);
And, of course, once you have a reference to the Value Stack, you can obtain a property
value by using the findValue method:
valueStack.findValue(propertyName);
For a custom converter to function, you need to provide code that works for each supported
type conversion. Typically, a converter should support at least two type conversions, from
String to another type and vice versa. For instance, a currency converter responsible for
converting String to double and double to String would implement convertValue like
this:
public Object convertValue(Map context, Object target,
Member member, String propertyName, Object value,
Class toType) {
if (toType == String.class) {
// convert from double to String and return the result
...
} else if (toType == Double.class || toType == Double.TYPE) {
// convert String to double and return the result
...
}
return null;
}
Here, ActionClass is the name of the action class. For instance, to configure custom
converters for an action class called User, create a filed named Userconversion.properties. The content of this file would look something like this.
field1=customConverter1
field2=customConverter2
...
In addition, the configuration file must reside in the same directory as the action class. The
app07b application shows how you can write a field-based configuration file for your
custom converters.
In class-based configuration you specify the converter that will convert a request parameter
to an instance of a class. In this case, you create an xwork-conversion.properties file
under WEB-INF/classes and pair a class with a converter. For example, to use
CustomConverter1 for a class, you'll write
fullyQualifiedClassName=CustomConverter1
...
Listing
7.6. The first if block provides conversion to String by using NumberFormat and
DecimalFormat. Conversions from String to double are done in the second if block by
removing all commas in the value.
Extending StrutsTypeConverter
Since in most type converters you need to provide implementation for String to non-String
conversions and the other way around, it makes sense to provide an implementation class
of TypeConverter that separates the two tasks into two different methods. The
StrutsTypeConverter class, a child of DefaultTypeConverter, is such a class. There are
two abstract methods that you need to implement when extending StrutsTypeConverter,
convertFromString and convertToString. See the StrutsTypeConverter class definition
in
Listing 7.9.
Listing
7.10. A color consists of red, green, and blue components and have a getHexCode
method that returns the hexadecimal code of the color.
Listing 7.11.
and blue components and constructs a Color object. Its convertToString method takes a
Color object and constructs a String.
If you enter a valid color and submit the form, you will invoke the Design2 action and have
the color displayed as in
Figure 7.8.
Figure 7.8. Displaying a color
This sample application has two actions, Admin1 and Admin2, that can be used to add an
Employee to the database. Every time a new employee is added, the admin id must also be
noted because there are multiple users in the admin role. The action declarations in the
struts.xml are shown in
Listing 7.15.
form, how do you populate an Admin and an Employee and at the same time use a
custom converter for the birthDate property?
Listing 7.18
Listing 7.21.
Figure 7.10. app07e directory structure
7.23,
<h4>Add Employees</h4>
<s:fielderror/>
<s:form theme="simple" action="Admin2">
<table>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Birth Date</th>
</tr>
<tr>
<td><s:textfield name="employees[0].firstName"/></td>
<td><s:textfield name="employees[0].lastName"/></td>
<td><s:textfield name="employees[0].birthDate"/></td>
</tr>
<tr>
<td><s:textfield name="employees[1].firstName"/></td>
<td><s:textfield name="employees[1].lastName"/></td>
<td><s:textfield name="employees[1].birthDate"/></td>
</tr>
<tr>
<td colspan="3"><s:submit/></td>
</tr>
</table>
</s:form>
</div>
</body>
</html>
The Confirmation.jsp page, shown in Listing 7.25, uses the iterator tag to iterate over
the employees property in the Admin action. It also employs the date tag to format the
birthdates.
<s:debug/>
</body>
</html>
You can test this example by directing your browser to this URL.
http://localhost:8080/app07e/Admin1.action
Being able to add two employees is great, but you probably want more. The rest of the
section discusses how get more flexibility.
Instead of hardcoding the text fields for employees as we did in the Admin.jsp page, we
use an iterator tag to dynamically build text fields. For example, to create four sets of
fields, you need an iterator tag with four elements like this.
<s:iterator value="new int[4]" status="stat">
Or, better still, you can pass a count request parameter to the URL and use the value to
build the iterator:
new int[#parameters.count[0]]
Note that the [0] is necessary because parameters always returns an array of Strings,
not a String.
Here are the tags that build text fields on the fly. You can find them in the Admin1b.jsp
page in app07e.
<s:iterator value="new int[#parameters.count[0]]" status="stat">
<tr>
<td><s:textfield
name="%{'employees['+#stat.index+'].firstName'}"/></td>
<td><s:textfield
name="%{'employees['+#stat.index+'].lastName'}"/></td>
<td><s:textfield
name="%{'employees['+#stat.index+'].birthDate'}"/></td>
</tr>
</s:iterator>
Invoke the action by using this URL, embedding a count request parameter.
http://localhost:8080/app07e/Admin1b.action?count=n
where n is the number of rows you want created. You can now enter as many employees as
you want in one go.
Figure 7.13.
Figure 7.13. app07f directory structure
The action declarations, shown in Listing 7.26, are similar to those in app07e. Admin1
displays a multiple record entry form, Admin2 displays the entered data, and Admin1b
can be used to add any number of employees.
Listing 7.30 shows the field-based configuration file for the Employee class.
Listing 7.30. The Employee-conversion.properties file
birthDate=app07f.converter.MyDateConverter
The Admin.jsp page in Listing 7.31 contains a form for entering two employees,
employees['user0'].lastName indicates the lastName property of the entry in the
employees Map whose key is user0.
</s:form>
</div>
</body>
</html>
Listing 7.32 shows the Confirmation.jsp page that displays entered data by iterating
over the employees Map.
the form, and you will see the entered data displayed, as shown in
To have a form for entering n employees, use the technique described in app07e.
Summary
Struts performs type conversions when populating action properties. When a conversion
fails, Struts also displays an error message so that the user knows how to correct the input.
You've learned in this chapter how to override the error message.
Sometimes default type conversions are not sufficient. For example, if you have a complex
object or you want to use a different format than the default, you need to write custom
converters. This chapter has also shown how to write various custom converters and
configure them.
Validator Overview
There are two types of validators, field validators and plain validators (non-field validators).
A field validator is associated with a form field and works by verifying a value before the
value is assigned to an action property. Most bundled validators are field validators. A plain
validator is not associated with a field and is used to test if a certain condition has been
met. The validation interceptor, which is part of the default stack, is responsible for loading
and executing registered validators.
Using a validator requires these three steps:
The first pattern is more common. However, since an action class can be used by
multiple actions, there are cases whereby you only want to apply validation on certain
actions. For example, the UserAction class may be used with User_create and
User_edit actions. If both actions are to be validated using the same rules, you can
simply declare the rules in a UserAction-validation.xml file. However, if User_create
and User_edit use different validation rules, you must create two validator
configuration files, UserAction-User_create-validation.xml and UserActionUser_edit-validation.xml.
3. Determine where the user should be forwarded to when validation fails by defining a
<result name="input"> element in the struts.xml file. Normally, the value of the result
element is the same JSP that contains the validated form.
Note on Validator Registration
All bundled validators are registered by default and can be used without you having to
worry about registration. Registration becomes an issue if you're using a custom validator.
If this is the case, read the section "Writing Custom Validators" later in this chapter.
Validator Configuration
The task of configuring validators centers around writing validator configuration files, which
are XML documents that must comply with the XWork validator DTD.
A validator configuration file always starts with this DOCTYPE statement.
<!DOCTYPE validators PUBLIC
"-//OpenSymphony Group//XWork Validator 1.0.2//EN"
"http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
The root element of a validator configuration file is validators. <validators> may have any
number of field and validator elements. A field element represents a form field to which one
or more field validators will be applied. A validator element represents a plain validator.
Here is the skeleton of a typical validator configuration file.
<!DOCTYPE validators PUBLIC
"-//OpenSymphony Group//XWork Validator 1.0.2//EN"
"http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
<validators>
<field name="...">
...
</field>
<field name="...">
...
</field>
...
<validator type="...">
...
</validator>
<validator type="...">
...
</validator>
...
</validators>
The name attribute in a field element specifies the form field to be validated.
You can apply any number of validators to a form field by nesting field-validator elements
within the field element. For instance, the following field element indicates that the
userEmail field must be validated by required and email validators.
<field name="userEmail">
<field-validator type="required">
</field-validator>
<field-validator type="email">
</field-validator>
</field>
You can pass parameters to a validator by nesting param elements within the field-validator
element. You can also define a validation error message by using the message element
within the field-validator element. As an example, this stringlength field validator receives
two parameters, minLength and maxLength, and the error message that must be displayed
when validation fails.
<field-validator type="stringlength">
<param name="minLength">6</param>
<param name="maxLength">14</param>
<message>
User name must be between 6 and 14 characters long
</message>
</field-validator>
A field-validator element can have zero or more param element and at most one message
element.
The validator element is used to represent a plain validator. It can also contain multiple
param element and a message element. For example, the following validator element
dictates that the max field must be greater than the min field or validation will fail.
<validator type="expression">
<param name="expression">
max > min
</param>
<message>
Maximum temperature must be greater than Minimum temperature
</message>
</validator>
Like field-validator, the validator element must have a type attribute and may have a shortcircuit attribute.
Bundled Validators
Struts comes with these built-in validators.
required validator.
requiredstring validator
int validator
date validator
expression validator
fieldexpression validator
email validator
url validator
visitor validator
conversion validator
stringlength validator
regex validator
required Validator
This validator makes sure that a field value is not null. An empty string is not null and
therefore will not raise an exception.
For instance, the RequiredTestAction class in
8.2.
Listing
</s:form>
</div>
</body>
</html>
You can use this URL to display the page:
http://localhost:8080/app08a/Required2.action
Figure 8.1 shows the form after a failed validation. It is rejected since the userName
field is missing.
requiredstring validator
The requiredstring validator ensures a field value is not null and not empty. It has a trim
parameter that by default has a value of true. If trim is true, the validated field will be
trimmed prior to validation. If trim is false, the value of the validated field will not be
trimmed. The trim parameter is described in
Table 8.1.
Name
Description
Data
Type
trim boolean
With trim true, a field that contains only spaces will fail to be validated.
The following example validates the fields associated with the properties of the
RequiredStringTestAction class in
Listing 8.5 assigns the requiredstring validator to the userName and password fields.
Listing 8.4. The RequiredStringTestAction class
package app08a;
import com.opensymphony.xwork2.ActionSupport;
public class RequiredStringTestAction extends ActionSupport {
private String userName;
private String password;
// getters and setters deleted
}
Note that the requiredstring validator for the userName has its trim parameter set to true,
which means a space or spaces do not qualify. The RequiredString.jsp page in
Listing
Submitting the form without first entering values to the fields will result in the form being
returned.
stringlength Validator
You use stringlength to validate that a non-empty field value is of a certain length. You
specify the minimum and maximum lengths through the minLength and maxLength
parameters. The complete list of parameters is given in
Table 8.2.
Name
Data
Type
Description
minLength int
maxLength int
Name
trim
Data
Type
Description
For example, the StringLengthTestAction class in Listing 8.7 defines two properties,
userName and password. A user name must be between six to fourteen characters long
and the stringlength validator is used to ensure this. The validator configuration file for this
example is presented in Listing 8.8. The StringLength.jsp page in
the form whose field is mapped to the userName property.
int Validator
The int validator checks if a field value can be converted into an int and, if the min and max
parameters are used, if its value falls within the specified range. The int validator's
parameters are listed in
Table 8.3.
Table 8.3. int validator parameters
Name
min
Data
Type
int
max int
Description
</html>
Direct your browser to this URL to test the int validator.
http://localhost:8080/app08a/Int1.action
Figure 8.14.
date Validator
This validator checks if a specified date field falls within a certain range.
possible parameters of the date validator.
Name
Description
Data
Type
max date
The maximum value allowed. If this parameter is not present, there will
be no maximum value.
min
The minimum value allowed. If this parameter is not present, there will
be no minimum value.
date
Note
The date pattern used to validate a date is dependant on the current locale.
Figure 8.5.
email Validator
The email validator can be used to check if a String evaluates to an email address. This
validator uses the Java Regular Expression API and use the following pattern:
"\\b(^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@([A-Za-z0-9-])+(\\.[A-Za-z0-9]+)*((\\.[A-Za-z0-9]{2,})|(\\.[A-Za-z0-9]{2,}\\.[A-Za-z0-9]{2,}))$)\\b"
This means an email can start with any combination of letters and numbers that is followed
by any number of periods and letters and numbers. It must have a @ character followed by
a valid host name.
As an example, the EmailTestAction class in Listing 8.16 defines an email property
that will be validated using the email validator. The validator configuration file is given in
Listing 8.17 and the JSP that contains a form with the corresponding field in printed in
Listing 8.18.
Listing 8.16. The EmailTestAction class
package app08a;
import com.opensymphony.xwork2.ActionSupport;
public class EmailTestAction extends ActionSupport {
private String email;
//getter and setter not shown
}
Figure 8.6 shows the form that contains a textfield tag named email.
url Validator
The url validator can be used to check if a String qualifies as a valid URL. The validator
does it work by trying to create a java.net.URL object using the String. If no exception is
thrown during the process, validation is successful.
The following are examples of valid URLs:
http://www.google.com
https://hotmail.com
ftp://yahoo.com
file:///C:/data/V3.doc
will be validated using the url validator. The validation configuration file is given in
8.20.
regex Validator
This validator checks if a field value matches the specified regular expression pattern. Its
parameters are listed in Table 8.5. See the documentation for the
java.lang.regex.Pattern class for more details on Java regular expression patterns.
Name
Data
Type
expression* String
Description
caseSensitive boolean Indicates whether or not the matching should be done in a case
sensitive way. The default value is true.
trim
Table 8.6.
Name
Data Type
expression* String
Description
The OGNL expression that governs the validation process.
There are two examples in this section. The first one deals with the expression validator, the
second with the fieldexpression validator.
shows a validator configuration file that uses the expression validator and specifies that the
value of the max property must be greater than the value of min.
JSP with a form with two fields.
conversion Validator
The conversion validator tells you if the type conversion for an action property generated a
conversion error. The validator also lets you add a custom message on top of the default
conversion error message. Here is the default message for a conversion error:
Invalid field value for field "fieldName".
With the conversion validator, you can add another message:
Invalid field value for field "fieldName".
[Your custom message]
validator for the age field and adds an error message for a failed conversion.
http://localhost:8080/app08a/Conversion1.action
Figure 8.10 shows the conversion validator in action. There are two error messages
displayed, the default one and the one that you added using the conversion validator.
visitor Validator
The visitor validator introduces some level of reusability, enabling you to use the same
validator configuration file with more than one action. Consider this scenario.
Suppose you have an action class (say, Customer) that has an address property of type
Address, which in turn has five properties (streetName, streetNumber, city, state, and
zipCode). To validate the zipCode property in an Address object that is a property of the
Customer action class, you would write this field element in a Customer-validation.xml
file.
<field name="address.zipCode">
<field-validator type="requiredstring">
<message>Zip Code must not be empty</message>
</field-validator>
</field>
Suppose also that you have an Employee action class that uses Address as a property
type. If the address property of Employee requires the same validation rules as the
address property in Customer, you would have an Employee-validation.xml file that is
an exact copy of the Customer-validation.xml file.
This is redundant and the visitor validator can help you isolate identical validation rules into
a file. Every time you need to use the validation rules, you simply need to reference the file.
In this example, you would isolate the validation rules for the Address class into an
Address-validation.xml file. Then, in your Customer-validation.xml file you would write
<field name="address">
<field-validator type="visitor">
<message>Address: </message>
</field-validator>
</field>
This field element says, for the address property, use the validation file that comes with
the property type (Address). In other words, Struts would use the Addressvalidation.xml file for validating the address property. If you use Address in multiple
action classes, you don't need to write the same validation rules in every validator
configuration file for each action.
Another feature of the visitor validator is the use of context. If one of the actions that use
Address needs other validation rules than the ones specified the Address-validation.xml
file, you can create a new validator configuration file just for that action. The new validator
configuration file would be named:
Address-context-validation.xml
Here, context is the alias of the action that needs specific validation rules for the Address
class. If the AddEmployee action needed special validation rules for its address property,
you would have this file:
Address-AddEmployee-validation.xml
That's not all. If the context name is different from the action alias, for example, if the
AddManager action also requires the validation rules in the Address-AddEmployeevalidaton.xml instead of the ones in Address-validation.xml, you can tell the visitor
validator to look at a different context by writing this field element.
<field name="address">
<field-validator type="visitor">
<param name="context">specific</param>
<message>Address: </message>
</field-validator>
</field>
This indicates to the visitor validator that to validate the address property, it should use
Address-specific-validation.xml and not Address-AddManager-validation.xml.
Now let's look at the three sample applications (app08b, app08c, and app08d) that
illustrate the use of the visitor validator. The app08b application shows a Customer action
that has an address property of type Address and uses a conventional way to validate
address. The app08c application features the same Customer and Address classes, but
use the visitor validator to validate the address property. The app08d application employs
the visitor validator and uses a different context.
Listing
8.33. Note that you can specify the validators for the properties in the Address object
here.
Figure 8.12.
Listing 8.35).
The Customer-validation.xml file (shown in Listing 8.36) is now shorter, since the
validation rules for the address property are no longer here. Instead, it uses the visitor
validator to point to the Address-validation.xml file.
In addition to the Customer class, there is an Employee class that has an address
property. There is a new validator configuration file for the Address class, Addressspecific-validation.xml, which is shown in
Listing 8.37.
The package names in Figure 8.15 have been omitted. The Validator, FieldValidator,
and ShortCircuitableValidator interfaces belong to the
com.opensymphony.xwork2.validator package. The rest are part of the
com.opensymphony.xwork2.validator.validators package. The Validator interface is
printed in
Listing 8.39.
Listing 8.40 shows the RequiredStringValidator class, the underlying class for the
requiredstring validator.
Registration
As mentioned at the beginning of this chapter, bundled validators are already registered so
you don't need to register them before use. They are registered in the
com/opensymphony/xwork2/validator/validators/default.xml file (shown in
Listing 8.41), which is included in the xwork jar file. If you are using a custom or third
party validator, you need to register it in a validators.xml file deployed under WEBINF/classes or in the classpath.
Note
The Struts website maintains, at the time of writing, that if you have a validators.xml file
in your classpath, you must register all bundled validators in this file because Struts will not
load the default.xml file. My testing revealed otherwise. You can still use the bundled
validators without registering them in a validators.xml file.
Example
The following example teaches you how to write a custom validator and register it. This
example showcases a strongpassword validator that checks the strength of a password. A
password is considered strong if it contains at least one digit, one lowercase character, and
one uppercase character. In addition, the validator can accept a minLength parameter that
the user can pass to set the minimum length of an acceptable password.
Now that you've registered your custom validator, you can use it the same way you would a
Listing 8.48.
Summary
Input validation is one of the features Struts offer to expedite web application development.
In fact, Struts comes with built-in validators that are available for use in most cases. As
you've learned in this chapter, you can also write custom validators to cater for validations
not already covered by any of the bundled validators. In addition, you can perform
programmatic validation in more complex situations.
them with an underscore, and put the most important one first. For example, a Traditional
Spanish collation might construct a locale with parameters for the language, the country,
and the variant as es, ES, Traditional_WIN, respectively.
The language code is a valid ISO 639 language code.
country codes. The complete list can be found at
http://www.w3.org/WAI/ER/IG/ert/iso639.htm.
Table 9.1. Examples of ISO 639 language codes
Code
Language
de
German
el
Greek
en
English
es
Spanish
fr
French
hi
Hindi
it
Italian
ja
Japanese
nl
Dutch
pt
Portuguese
ru
Russian
zh
Chinese
The country argument is also a valid ISO code, which is a two-letter, uppercase code
Table 9.2 lists some of the country codes in ISO 3166. The
complete list can be found at http://userpage.chemie.fuberlin.de/diverse/doc/ISO_3166.html or
specified in ISO 3166.
http://www.iso.org/iso/en/prods-services/iso3166ma/02iso-3166code-lists/list-en1.html.
Table 9.2. Examples of ISO 3166 Country Codes
Country
Code
Australia
AU
Brazil
BR
Canada
CA
China
CN
Egypt
EG
France
FR
Germany
DE
India
IN
Mexico
MX
Switzerland
CH
Taiwan
TW
United Kingdom
GB
United States
US
An internationalized application stores its textual elements in a separate properties file for
each locale. Each file contains key/value pairs, and each key uniquely identifies a localespecific object. Keys are always strings, and values can be strings or any other type of
object. For example, to support American English, German, and Chinese, you will have
three properties files, all with the same keys.
Here is the English version of the properties file. Note that it has two keys: greetings and
farewell:
greetings = Hello
farewell = Goodbye
And the properties file for the Chinese language would be this:
greetings=\u4f60\u597d
farewell=\u518d\u89c1
Note
With Struts you don't need to know any more than writing properties files in multiple
languages. However, if interested, you may want to learn about the
java.util.ResourceBundle class and study how it selects and reads a properties file
specific to the user's locale.
Each of the properties files in an internationalized application must be named according to
this format.
basename_languageCode_countryCode
For example, if the base name is MyAction and you define three locales US-en, DE-de,
CN-zh, you would have these properties files:
MyAction_en_US.properties
MyAction_de_DE.properties
MyAction_zh_CN.properties
Gets the message associated with the key and returns null if the message cannot be found.
public java.lang.String getText(java.lang.String key,
java.lang.String defaultValue)
Gets the message associated with the key and returns the specified default value if the
message cannot be found.
public java.lang.String getText(java.lang.String key,
java.lang.String[] args)
Gets the message associated with the key and formats it using the specified arguments in
accordance with the rules defined in java.text.MessageFormat.
public java.lang.String getText(java.lang.String key,
args)
java.util.List
Gets the message associated with the key and formats it using the specified arguments in
accordance with the rules defined in java.text.MessageFormat.
public java.lang.String getText(java.lang.String key,
java.lang.String defaultValue, java.lang.String[] args)
Gets the message associated with the key and formats it using the specified arguments in
accordance with the rules defined in java.text.MessageFormat. If the message cannot be
found, this method returns the specified default value.
public java.lang.String getText(java.lang.String key,
java.lang.String defaultValue, java.util.List args)
Gets the message associated with the key and formats it using the specified arguments in
accordance with the rules defined in java.text.MessageFormat. If the message cannot be
found, this method returns the specified default value.
When you call a getText method, it searches for the appropriate properties file in this
order.
1. The action class properties file, i.e. one whose basename is the same as the name of
the corresponding action class and located in the same directory as the action class.
For example, if the action class is app09a.Customer, the relevant file for the
default locale is Customer.properties in WEB-INF/classes/app09a.
2. The properties file for each interface that the action class implements. For example,
if the action class implements a Dummy interface, the default properties file that
corresponds to this interface is Dummy.properties.
3. The properties file for each of its parent class followed by each interface the parent
class implements. For instance, if the action class extends ActionSupport, the
ActionSupport.properties file will be used. If the message is not found, the search
moves up to the next parent in the hierarchy, up to java.lang.Object.
4. If the action class implements com.opensymphony.xwork2.ModelDriven, Struts
calls the getModel method and does a class hierarchy search for the class of the
model object. ModelDriven is explained in Chapter 10, "Model Driven and Prepare
Interceptors."
5. The default package properties file. If the action class is app09a.Customer, the
default package ResourceBundle is package in app09a.
6. The package resource bundle in the next parent package.
7. Global resources
You can display a localized message using the property tag or the label attribute of a form
tag by calling getText. The syntax for calling it is
%{getText('key')}
For example, to use a textfield tag to retrieve the message associated with key
customer.name, use this:
<s:textfield name="name" label="%{getText('customer.name')}"/>
The following property tag prints a message associated with the key customer.contact.
<s:property value="%{getText('customer.contact')}"/>
The following sample application shows how to use the message handling feature in a
monolingual application. It is shown here how easy it is to change messages across the
application by simply editing properties files.
The application centers around the Customer action class, which implements an interface
named Dummy. This interface does not define any method and is used to demonstrate the
order of properties file search.
The directory structure of the example (app09a) is shown in
Figure 9.1.
Table
9.3.
Table 9.3. text tag attributes
Name
Data
Type
Description
name* String
var
The name of the variable that references the value to pushed to the
stack context.
String
For example, the following text tag prints the message associated with the key greetings:
<s:text name="greetings"/>
If the var attribute is present, however, the message is not printed but pushed to the Value
Stack's context map. For instance, the following pushes the message associated with
greetings to the context map and creates a variable named msg that references the
message.
<s:text name="greetings" id="msg"/>
You can then use the property tag to access the message.
<s:text name="greetings" id="msg"/>
<s:property value="#msg"/>
You can pass parameters to a text tag. For example, if you have the following key in a
properties file
greetings=Hello {0}
A parameter can be a dynamic value too. For example, the following code passes the value
of the firstName property to the text tag.
<s:text name="greetings">
<s:param><s:property value="firstName"/></s:param>
</s:text>
The app09b application shows how to use the text tag in a multilingual site. Three
languages are supported: English (default), German, and Chinese.
Note that three properties files correspond to the Main class. The properties files are given
in
You want to use a ListResourceBundle so that you can associate a key with a nonString object.
You wish to pre-process a key.
The tag falls back to the default resource bundle if the specified custom ResourceBundle
cannot be found.
The i18n tag has one attribute, name, which is described in
Table 9.4.
Description
The fully qualified Java class to load.
For example, the app09c application features two custom ResourceBundles that extend
ListResourceBundle, MyCustomResourceBundle and MyCustomResourceBundle_de.
The custom ResourceBundles are shown in Listings 9.8 and 9.9, respectively. These
ResourceBundles return one of two message arrays. If the current time is before 12 am, it
will return the first array. Otherwise, the second array will be returned. Therefore, the user
will get a different message depending on the current time.
The Main.jsp page in Listing 9.10 uses an i18n tag to select a custom
ResourceBundle and employs two text tags to display the localized messages.
Listing
9.11.
Listing 9.11. The action declarations
<package name="app09d" extends="struts-default">
<action name="Language">
<result>/jsp/Language.jsp</result>
</action>
<action name="Main1" class="app09d.Main">
<result>/jsp/Main1.jsp</result>
</action>
<action name="Main2" class="app09d.Main">
<result>/jsp/Main2.jsp</result>
</action>
</package>
The first action, Language, displays the Language.jsp page (shown in
that shows two links that let the user select a language.
Listing 9.12)
<h3>Select Language</h3>
<ul>
<li><s:a href="%{enUrl}">English</s:a></li>
<li><s:a href="%{deUrl}">Deutsch</s:a></li>
</li>
</div>
</body>
</html>
Selecting the first link invokes the Main1 action and passes the request_locale=en
request parameter to the server. Selecting the second link invokes Main2 and passes
request_locale=de. The Main1.jsp and Main2.jsp pages, associated with actions Main1
and Main2, are shown in
Figure 9.4.
Summary
Message handling is one of the most important tasks in application development. Today
applications also often require that applications be able to display internationalized and
localized messages. Struts has been designed with i18n and l10n in mind, and the tags in
the Struts tag library support internationalized message handling.
Listing
10.1.
Listing 10.1. The ModelDriven interface
package com.opensymphony.xwork2;
/**
* ModelDriven Actions provide a model object to be pushed onto the
* ValueStack in addition to the Action itself, allowing a FormBean
* type approach like Struts 1.
*/
public interface ModelDriven<T> {
/**
* @return the model to be pushed onto the ValueStack instead of
* the Action itself
*/
T getModel();
}
An action class that implements ModelDriven must override the getModel method. As an
not have a matching property in the model, the Param interceptor will try the next object in
the Value Stack. In this case, the ProductAction object will be used.
As an example, the app10a application shows how you can separate an action and a model.
This simple application manages employees and comes with two actions:
Listing 10.4.
Listing 10.5.
}
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
public void setEmployees(List<Employee> employees) {
this.employees = employees;
}
public String list() {
employees = EmployeeManager.getEmployees();
return SUCCESS;
}
public String create() {
EmployeeManager.create(employee);
return SUCCESS;
}
}
The model used in this application is the Employee class in
Listing 10.6.
Chapter 11, "Persistence Layer" explains the Data Access Object design pattern for
data access.
The EmployeeManager class is shown in
Listing 10.7.
If you click the Submit button, the create method in the action object will be invoked. A
validation file (named EmployeeAction-Employee_create-validation.xml) is used to
make sure that the first name and the last name are not empty.
EmployeeAction-Employee_create-validation.xml file.
Now, pay attention to the result elements for the Employee_create action in the
configuration file:
<action name="Employee_create" method="create"
class="app10a.EmployeeAction">
<result type="redirect-action">Employee_list</result>
<result name="input">/jsp/Employee.jsp</result>
</action>
After a successful create, the user will be redirected to the Employee_list action. Why
didn't we do a forward that would have been faster?
The Create Employee form is submitted to this URI:
/Employee_create.action
If we had used a forward, then the URI would have remained the same after the action and
result were executed. As a result, if the user clicked the browser's Refresh/Reload button,
the form (and its contents) would be submitted again and a new employee would be
created.
By redirecting, the URI after Employee_create will be the following, which will not cause
another create if the user (accidentally) reloads the page.
/Employee_list.action
Listing 10.9.
Employee_edit
Employee_update
Employee_delete
Listing 10.10.
</action>
</package>
The EmployeeAction class, shown in
The first time the Parameters interceptor is invoked, it populates the employeeId property
on the EmployeeAction object, so that the prepare method knows how to retrieve the
Employee object to be edited. After the Prepare and Model Driven interceptors are invoked,
the Parameters interceptor is called again, this time giving it the opportunity to populate the
model.
The model class (Employee) for this application is exactly the same as the one in app10a
and will not be reprinted here. However, the EmployeeManager class has been modified
and is given in
Listing 10.12.
Figure 10.2 shows the list of employees. It's similar except that there are now Edit and
Delete links for each employee.
Summary
It is often necessary to separate the action and the model, especially in an enterprise
application and in a more complex Struts application. This chapter showed how the Model
Driven and Prepare interceptors could help.
Figure 11.1.
The persistence layer provides public methods for storing, retrieving, and manipulating
value objects, and the client of the persistence layer does not have to know how the
persistence layer accomplishes this. All they care is their data is safe and retrievable.
A typical DAO class takes care of the addition, deletion, modification, and retrieval of an
object, and the searching for those objects. For example, a ProductDAO class may support
the following methods:
void addProduct(Product product)
void updateProduct(Product product)
void deleteProduct(int productId)
Product getProduct(int productId)
List<Product> findProducts(SearchCriteria searchCriteria)
There are many variants of the DAO pattern. You will learn the three most common
variants: from the most basic to the most flexible.
Figure 11.2 shows the ProductDAO class in this variant of the DAO pattern.
Figure 11.2. The simplest implementation of the DAO pattern
When a Struts action object needs to access product information, it instantiates the
ProductDAO class and calls its methods.
Figure 11.3.
Listing 11.1.
In Java EE, you obtain a DataSource object by employing a JNDI lookup using this
boilerplate code:
try {
Context context = new InitialContext();
DataSource dataSource = (DataSource)
context.lookup(dataSourceJndiName);
...
JNDI lookups are expensive operations, and, as such, obtaining a DataSource is resource
intensive. Therefore, you may want to cache this object and the ServletContext object will
be an ideal location to cache it. In app11a we use the application listener in Listing 11.3
to obtain a DataSource object and store it in the ServletContext object. Afterwards, in
the DAOBase class in
The Context element above facilitates the creation of a DataSource object from
which you can get java.sql.Connection objects from the pool. The specifics of the
DataSource object are given in the parameter elements of the
Listing 11.4.
maximumSearchResults, that do not exist in Employee. Hence, the need for another
class that encapsulates user search criteria.
try {
connection = getConnection();
statement = connection.createStatement();
resultSet = statement.executeQuery(
criteriaSql.toString());
while (resultSet.next()) {
Employee employee = new Employee();
employee.setId(resultSet.getInt("id"));
employee.setFirstName(
resultSet.getString("firstName"));
employee.setLastName(
resultSet.getString("lastName"));
employees.add(employee);
}
resultSet.close();
statement.close();
} catch (SQLException e) {
throw new DAOException();
} finally {
try {
connection.close();
} catch (SQLException ex) {
}
}
return employees;
}
}
The SQL statements for all the methods, except searchEmployees, are defined as static
final Strings because they will never change. Making them static final avoids creating the
same Strings again and again. Also, all those methods use a PreparedStatement instead
of a java.sql.Statement even though the PreparedStatement object is only executed
once. The use of PreparedStatement saves you from having to check if one of the
arguments contains a single quote. With a Statement, you must escape any single quote in
the argument.
The searchEmployees method, on the other hand, is based on a dynamic SQL statement.
This necessitates us to use a Statement object. Consequently, you must check for single
quotes in the arguments using the DbUtil class's fixSqlFieldValue method.
presents the fixSqlFieldValue method.
Listing 11.7
char c = value.charAt(i);
if (c == '\'') {
fixedValue.append("''");
} else {
fixedValue.append(c);
}
}
return fixedValue.toString();
}
}
Note
You could replace the fixSqlFieldValue method with the replaceAll method of the String
class like this.
String t= s.replaceAll("[\']", "''");
However, this method is compute intensive because it uses regular expressions and should
be avoided in applications designed to be scalable.
Listing 11.8.
return instance;
}
public EmployeeDAO getEmployeeDAO() {
if ("mysql".equalsIgnoreCase(databaseType)) {
return new EmployeeDAOMySQLImpl();
} else if ("oracle".equalsIgnoreCase(databaseType)) {
// return new EmployeeDAOOracleImpl();
} else if ("mssql".equalsIgnoreCase(databaseType)) {
// return new EmployeeDAOMsSQLImpl();
}
return null;
}
}
You can use the DAOFactory if you know the implementation classes for all your DAOs
when the application is written. This means, if you are thinking of only supporting two
databases, MySQL and Oracle, you know beforehand the type for the EmployeeDAO class
is either EmployeeDAOMySQLImpl or EmployeeDAOOracleImpl. If in the future your
application needs to support Microsoft SQL Server, you must rewrite the DAOFactory class,
i.e. add another if statement in the getCustomerDAO class.
You can add support of more databases without recompiling the DAOFactory class if you
use reflection to create the DAO object. Instead of the dbType parameter in your web.xml
file, you'd have employeeDAOType. Then, you would have the following code in your
DAOFactory class's getCustomerDAO method.
String customerDAOType = Config.getCustomerDAOType();
Class customerDAOClass = Class.forName(customerDAOType);
CustomerDAO customerDAO = customerDAOClass.newInstance();
Figure 11.5.
When you run this application for the first time, you will not see the list of existing
employees.
Hibernate
Hibernate has gained popularity in the past few years as an add-on for Java EE and other
applications. Its web site (www.hibernate.org) advertises this free product as "a
powerful, ultra-high performance object/relational persistence and query service for Java."
Using Hibernate, you do not need to implement your own persistence layer. Instead, you
use a tool to create databases and related tables and determine how your objects should be
persisted. Hibernate virtually supports all kinds of database servers in the market today,
and its Hibernate Query Language provides "an elegant bridge between the object and
relational worlds".
More people will be using Hibernate in the near future. If you have time, invest in it.
Summary
Most applications need a persistence layer for persisting value objects. The persistence layer
hides the complexity of accessing the database from its clients, notably the action objects.
The persistence layer can be implemented as entity beans, the DAO pattern, by using
Hibernate, etc.
This chapter shows you in detail how to implement the DAO pattern. There are many
variants of this pattern and which one you choose depends on the project specification. The
most flexible DAO pattern is preferable because you can extend your application easily
should it need to change in the future.
To enable the user to select a file you must have an <input type="file"> field. Here is an
example of a form used for selecting a file. In addition to a file field, the form also contains
a text box named description and a submit button.
<form action="Upload.action" enctype="multipart/form-data"
method="post">
Select file to upload <input type="file" name="filename"/><br/>
Description: <input type="text" name="description"/><br/>
<input type="submit" value="Upload"/>
</form>
Figure 12.1 shows how the file input field is rendered as a text box and a Browse
button.
Without Struts or the Java Commons FileUpload library, you would have to call the
getInputStream method on HttpServletRequest and parse the resulting InputStream
object to retrieve the uploaded file. This is a tedious and error-prone task. Luckily, Struts
makes it very easy to retrieve uploaded files.
A file tag will be rendered as the following input element in the browser:
<input type="file" name="inputName"/>
Second, create an action class with three properties. The properties must be named
according to these patterns:
[inputName] File
[inputName]FileName
[inputName]ContentType
Here [inputName] is the name of the file tag(s) on the JSP. For example, if the file tag's
name is attachment, you will have these properties in your action class:
attachmentFile
attachmentFileName
attachmentContentType
For single file upload, the type of [inputName] File is java.io.File and references the
uploaded file. The second and third properties are String and refer to the uploaded file name
and the content type, respectively.
For multiple file upload, you can either use arrays or java.util.Lists. For instance, the
following properties are arrays of Files and Strings.
private File[] attachmentFile;
private String[] attachmentFileName;
private String[] attachmentContentType;
If you decide to use Lists, you must assign an empty list to each of the properties:
You can access these properties from your action method. Normally, you would want to
save the uploaded file into a folder or a database and you would iterate over the File array,
if an array is being used:
ServletContext servletContext =
ServletActionContext.getServletContext();
String dataDir = servletContext.getRealPath("/WEB-INF");
for (int i=0; i < attachment.length; i++) {
File savedFile = new File(dataDir, attachmentFileName[i]);
attachment[i].renameTo(savedFile);
}
Since you often need to access both the uploaded file and the file name at each iteration,
using arrays is easier because an array lets you iterate over its elements by index. On the
other hand, iterating over a list would be more difficult.
maximumSize. The maximum size (in bytes) of the uploaded file. The default is
about 2MB.
allowedTypes. A comma-separated list of allowable content types.
For example, the following action imposes a size limit and the type of the uploaded file. Only
files up to 1,000,000 bytes in size and JPEG, GIF, and PNG files can be uploaded.
<action name="File_upload" class="app14a.FileUploadAction">
<interceptor-ref name="fileUpload"/>
<param name="maximumSize">1000000</param>
<param name="allowedTypes">
image/gif,image/jpeg,image/png
</param>
</interceptor-ref>
</interceptor-ref>
<interceptor-ref name="basicStack"/>
...
</action>
If the user uploaded a file that is larger than the specified maximum size or a type not in
the allowedTypes parameter, an error message will be displayed. File upload-related error
messages are predefined in the struts-messages.properties file which is included in the core
Struts JAR file. Here are the contents of the file:
struts.messages.error.uploading=Error uploading: {0}
struts.messages.error.file.too.large=File too large: {0} "{1}" {2}
struts.messages.error.content.type.not.allowed=Content-Type not
allowed: {0} "{1}" {2}
Figure 12.2.
Figure 12.2. app12a directory structure
There are two actions in this application, one for displaying a file upload form and one for
receiving the uploaded file. The action declarations are printed in
Listing 12.1.
Listing 12.4.
Figure 12.3.
Listing 12.5.
Listing 12.6.
}
}
You can start uploading multiple files by directing your browser here.
http://localhost:8080/app12b/File.action
Figure 12.4.
You can also use Lists instead of arrays. The MultipleFileUploadAction2 class in
Listing
12.8 shows how to use Lists. Note that you must instantiate a List implementation for the
List variables.
Summary
This chapter discussed file upload. Struts supports file upload through the File Upload
interceptor that incorporates the Jakarta Commons FileUpload library. Two examples that
illustrated single file upload and multiple file upload were presented in this chapter
First, you read the file as a FileInputStream and load the content to a byte array. Then, you
obtain the HttpServletResponse object's OutputStream and call its write method, passing
the byte array.
Name
Data
Type
Default
Value
Description
inputName
bufferSize
int
contentType
String text/plain
contentLength
int
1024
Take the app13a application as an example. There are two actions that are related to file
download, ViewCss and DownloadCss. ViewCss sends a CSS file to the browser and
instructs the browser to display its content. DownloadCss file sends the CSS file as a file
download. You can modify this example to work with other file types, not only CSS.
Whether the browser will show a file content or display a File Download dialog depends on
the value you set the Content-Type header. Setting it to "text/css" tells the browser that
the file is a CSS file and should be displayed. Assigning "application/octet-stream" tells the
browser that the user should be given the chance to save the file. Listing 13.1 shows the
action declarations in app13a. The Menu action displays the Menu.jsp page from which
the user can select whether to view or download a CSS file.
Listing 13.3.
Listing 13.3. The Menu.jsp file
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
<title>File Download</title>
<style type="text/css">@import url(css/main.css);</style>
</head>
<body>
<div id="global" style="width:200px">
<s:url id="url1" action="ViewCss">
<s:param name="filePath">css/main.css</s:param>
</s:url>
<s:a href="%{url1}">View CSS</s:a>
<br/>
<s:url id="url2" action="DownloadCss">
<s:param name="filePath">css/main.css</s:param>
</s:url>
<s:a href="%{url2}">Download CSS</s:a>
</div>
</body>
</html>
The Main.jsp page employs two url tags with different parameters. The URLs are then used
by the a tags on the page.
To test this example, point your browser to this URL:
http://localhost:8080/app13a/Menu.action
You'll see two links as shown in Figure 13.1. If you click the first link, the content of the
main.css file will be displayed. If you click the second link, the File Download dialog of your
browser will open and you can save the file.
Listing 13.4.
13.6.
Listing 13.5. The Product class
package app13b;
import java.io.Serializable;
public class Product implements Serializable {
private int id;
private String name;
public Product() {
}
public Product (int id, String name) {
this.id = id;
this.name = name;
}
// getters and setters not shown
}
Listing
<table>
<tr>
<th>Name</th>
<th>Picture</th>
</tr>
<s:iterator value="products" id="product">
<tr>
<td><s:property value="#product.name"/></td>
<td>
<s:url id="url" action="GetImage">
<s:param name="productId">
<s:property value="#product.id"/>
</s:param>
</s:url>
<img src="<s:property value='#url'/>"
width="100" height="50"/>
</td>
</tr>
</s:iterator>
</table>
</div>
</body>
</html>
A product may have an image stored in the images directory of the application. A product
image is named according to the product identifier in a web-friendly format (one of jpeg,
gif, or png). For product identifier 3, the image name would be 3.gif or 3.jpg or 3.png.
Because the image file name is not stored, you have to find a way to display the image.
The GetImage action flushes an image to the browser. Note that in the Product.jsp page
the iterator tag contains an img element whose source is a URL that references to the
GetImage action and passes a productId parameter.
Now, let's focus on the GetImageAction class in
Listing 13.8.
ServletResponseAware, ServletContextAware {
private String productId;
private HttpServletResponse servletResponse;
private ServletContext servletContext;
public void setServletResponse(HttpServletResponse
servletResponse) {
this.servletResponse = servletResponse;
}
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
public InputStream getInputStream() throws Exception {
String contentType = "image/gif";
String imageDirectory =
servletContext.getRealPath("images");
// The images can be a jpg or gif,
// retrieve default image if no file was found
File file = new File(imageDirectory, productId + ".gif");
if (!file.exists()) {
file = new File(imageDirectory, productId + ".jpg");
contentType = "image/jpeg";
}
if (!file.exists()) {
file = new File(imageDirectory, "noimage.jpg");
}
if (file.exists()) {
Result result = ActionContext.getContext().
getActionInvocation().getResult();
if (result != null && result instanceof StreamResult) {
StreamResult streamResult = (StreamResult) result;
streamResult.setContentType(contentType);
}
try {
return new FileInputStream(file);
} catch (IOException ex) {
}
}
return null;
}
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
}
This class is similar to the FileDownloadAction class in app13a. However, GetImage
class has a productId property that is set by the productId request parameter. The
getInputStream method retrieves the image as a file and wraps it in a FileInputStream.
You can test this application by directing your browser to this URL.
http://localhost:8080/app13b/DisplayProducts.action
You'll see something similar to
Figure 13.2.
Summary
In this chapter you have learned how file download work in web applications. You have also
learned how to select a file and sent it to the browser.
The file says that there are two roles (admin and manager) and three users (vera, chuck,
and dave). You can add as many roles and users as you want to the tomcat-users.xml file.
Protecting resources
Determining the login method for user authentication.
Protecting Resources
You enforce the security policy by using the security-constraint element in the
deployment descriptor. Here is the description of this element.
<!ELEMENT security-constraint (display-name?,
web-resource-collection+, auth-constraint?,
user-data-constraint?)>
This means that the security-constraint element can have an optional display-name
subelement, one or many web-resource-collection subelements, an optional authconstraint subelement, and an optional user-data-constraint subelement.
You specify the set of web resources that you want to protect in the web-resourcecollection element, and you use the auth-constraint element to define the user roles
allowed to access them. The subelements are described further below.
You use the web-resource-collection element to specify which resources must be
protected by specifying a URL pattern for those resources. In addition, you can also specify
what HTTP methods (GET, POST, etc) should be allowed access to the protected resources.
The web-resource-collection element can have the following subelements.
<security-constraint>
<web-resource-collection>
<web-resource-name>Manager Area</web-resource-name>
<url-pattern>/manager/*.do</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>manager</role-name>
</auth-constraint>
</security-constraint>
The security-constraint element will cause the web container to block any request that
match the pattern /manager/*.do that does not come from a user belonging to the
manager role. Because no http-method element is used, the web container will attempt to
block all requests regardless the HTTP method being used to access the resource.
In addition, you should also register all roles used to access the restricted resources by
using the security-role element. Inside a security-role element, you write a role-name
element for each role. For example, the following security-role element defines two roles,
admin and manager.
<security-role>
<role-name>admin</role-name>
<role-name>manager</role-name>
</security-role>
The auth-method element specifies the method for authenticating users. Its possible
values are BASIC, DIGEST, FORM, or CLIENT-CERT. The next section,
"Authentication
The realm-name element specifies a descriptive name that will be displayed in the
standard Login dialog when using the BASIC authentication method.
Authentication Methods
There are several authentication methods: basic, form-based, digest, Secure Socket Layer
(SSL), and client certificate authentication. With the basic authentication, the web container
asks the browser to display the standard Login dialog box which contains two fields: the
user name and the password. The standard Login dialog box will look different in different
browsers. In Internet Explorer, it looks like the one in
Figure 14.1
If the user enters the correct user name and password, the server will display the requested
resource. Otherwise, the Login dialog box will be redisplayed, asking the user to try again.
The server will let the user try to log in three times, after which an error message is sent.
The drawback of this method is that the user name and password are transmitted to the
server using base64 encoding, which is a very weak encryption scheme. However, you can
use SSL to encrypt the user's credential.
http://home.netscape.com/eng/ssl3/3-SPEC.HTM.
The following subsections provide examples of the basic and form-based authentication
methods.
Note
There are two possible error messages with regard to authentication, 401 and 403. The user
will get a 401 if he or she cannot supply the correct user name and password of any user. A
user is normally given three chances, but this is browser specific. The user will get a 403 if
he or she can enter the correct user name and password of a user but the user is not in the
allowed role list.
Listing 14.1.
Listing 14.2.
Pay attention to the sections in bold. Practically, the URLs for invoking the two actions are
protected. Using Tomcat with the following tomcat-users.xml file, you know that the
actions can be accessed by Chuck and Dave, but not by Vera.
<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
<role rolename="manager"/>
<role rolename="admin"/>
<user username="vera" password="arev" roles="manager"/>
<user username="dave" password="secret" roles="manager,admin"/>
<user username="chuck" password="chuck" roles="admin"/>
</tomcat-users>
Only users in the admin role can access it. Use this URL to test it:
http://localhost:8080/app14a/User_input.action
The first time you try to access this resource, you'll see a Basic authentication page that
prompts you to enter the user name and password. If you do not enter the user name and
password of a user in the admin role, you'll get a 403 error. The error-page section in the
web.xml file tells the servlet container to display the 403.html file upon a 403 error
occurring. Without the error-page declaration, you'll get a standard servlet container error
page, as shown in
Figure 14.2.
Figure 14.2. Tomcat default error page
<web-resource-name>JSPs</web-resource-name>
<url-pattern>/jsp/*</url-pattern>
</web-resource-collection>
<auth-constraint/>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>Admin Area</web-resource-name>
<url-pattern>/User_input.action</url-pattern>
<url-pattern>/User.action</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/login.html</form-login-page>
<form-error-page>/loginError.html</form-error-page>
</form-login-config>
</login-config>
<security-role>
<role-name>admin</role-name>
</security-role>
<error-page>
<error-code>403</error-code>
<location>/403.html</location>
</error-page>
</web-app>
For the login form, the user name field must be j_usemame, the password field must be
j_password, and the form's action must be j_security_check.
the login form used in app14b.
Like the app14a appHcation, Chuck and Dave can access the restricted resources but Vera
cannot.
The first time you request the action, you'll see the login page in
Figure 14.4.
There are two error pages provided in app14b. The loginError.html, declared in the
web.xml file, is shown if the user cannot enter the correct user name and password. The
403.html file is shown if the user can produce a correct user name and password but the
user is not on the allowed role list
Hiding Resources
An observant reader would notice that all access should go through the Struts action servlet
and JSPs should not be accessible directly. Protecting JSPs from direct access can be easily
achieved in several ways.
1. By placing the resources, i.e. JSPs, under WEB-INF, which makes the JSPs not
accessible by typing their URLs. This way, the JSPs can only be displayed if they are
a forward destination from the action servlet. However, you have also noticed that
throughout this book all JSPs are not in the WEB-INF directory. This is because
some containers (such as WebLogic) will not be able to forward control to a JSP
under WEB-INF. Storing JSPs in WEB-INF may also change how other resources,
such as image and JavaScript files, can be referenced from the JSPs.
2. By using a filter to protect the JSPs outside the WEB-INF directory. It is easy to
implement such a filter. All you need to do is apply the filter so that it will redirect
access to a user page if the URL ends with .jsp. However, this is not as easy as the
trick explained in Step 3.
3. By using the security-constraint element in the web.xml file to protect all JSPs
but without providing a legitimate user role to access them. For example, in both
app14a and app14b, you have two security-constraint elements in the web.xml
files. One to prevent all JSPs from being accessed directly, another to protect
actions.
<security-constraint>
<web-resource-collection>
<web-resource-name>
Direct Access to JSPs
</web-resource-name>
<url-pattern>*.jsp</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>none</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>Admin Area</web-resource-name>
<url-pattern>/User_input.action</url-pattern>
<url-pattern>/User.action</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
All URLs ending with .jsp can only be accessed by users in the none role. If you do not have
a user in this role, no one can access the JSPs directly.
allowedRoles. A list of roles that are allowed to access the corresponding action.
Roles can be comma-delimited.
disallowedRoles. A list of roles that are not allowed to access the corresponding
action. Roles can be comma-delimited.
The app14c application provides an example of using the roles attribute. To be specific,
you use the deployment descriptor in Listing 14.5, in which you restrict access to all
URLs ending with .action, in effect restricting access to all Struts actions.
Now, you have the following actions in the app14c application: User_input and User. You
want both to be accessible by all managers and admins. The elements shown in
Listing
14.6 shows you how to declare the actions and interceptors in both actions.
Listing 14.6. Action declarations
<package name="app14c" extends="struts-default">
<action name="User_input">
<interceptor-ref name="completeStack"/>
<interceptor-ref name="roles">
<param name="allowedRoles">admin,manager</param>
</interceptor-ref>
<result>/jsp/User.jsp</result>
</action>
<action name="User" class="app14c.User">
<interceptor-ref name="completeStack"/>
<interceptor-ref name="roles">
<param name="allowedRoles">admin,manager</param>
</interceptor-ref>
<result>/jsp/Thanks.jsp</result>
</action>
</package>
To test the app14c application, direct your browser to this URL.
http://localhost:8080/app14c/User_input.action
Programmatic Security
Even though configuring the deployment descriptor and specifying roles in the tomcatusers.xml file means that you do not need to write Java code, sometimes coding is
inevitable. For example, you might want to record all the users that logged in. The
javax.servlet.http.HttpServletRequest interface provides several methods that enable
you to have access to portions of the user's login information. These methods are
getAuthType, isUserInRole, getPrincipal, and getRemoteUser. The methods are
explained in the following subsections.
This method indicates whether the authenticated user is included in the specified role. If the
user has not been authenticated, the method returns false.
This method returns a java.security.Principal object containing the name of the current
authenticated user. If the user has not been authenticated, the method returns null.
This method returns the name of the user making this request, if the user has been
authenticated. Otherwise, it returns null. Whether the user name is sent with each
subsequent request depends on the browser and type of authentication.
Summary
In this chapter, you have learned how to configure the deployment descriptor to restrict
access to some or all of the resources in your servlet applications. The configuration means
that you need only to modify your deployment descriptor fileno programming is
necessary. In addition, you have also learned how to use the roles attribute in the action
elements in your Struts configuration file.
Writing Java code to secure Web applications is also possible through the following methods
of the javax.servlet.http.HttpServletRequest interface: getRemoteUser, getPrincipal,
getAuthType, and isUserInRole.
Managing Tokens
Struts provides the token tag that generates a unique token. This tag, which must be
enclosed in a form tag, inserts a hidden field into the form and stores the token in the
HttpSession object. If you use the debug tag on the same page as the form, you'll see a
session attribute session.token with a 32 character value.
The use of token must be accompanied by one of two interceptors, Token and Token
Session, that are capable of handling tokens. The Token interceptor, upon a double submit,
returns the result "invalid.token" and adds an action error. The default message for this
error is
The form has already been processed or no token was supplied, please
try again.
This is confusing for most users. Should they try again by resubmitting the form? Hasn't the
form been processed?
To override the message, you can create a validation file and add a value for the key
struts.messages.invalid.token. The supporting class for the Token interceptor is
org.apache.struts2.interceptor.TokenInterceptor. Therefore, to override the message, you
must place your key/value pair in a TokenInterceptor.properties file and place it under this
directory:
/WEB-INF/classes/org/apache/struts2/interceptor
The Token Session interceptor extends the Token interceptor and provides a more
sophisticated service. Instead of returning a special result and adding an action error, it
simply blocks subsequent submits. As a result, the user will see the same response as if
there were only one submit.
The following sections provide examples on both interceptors.
Figure 15.1.
There are two actions in the application, Pay_input and Pay. The declarations for these
actions are shown in Listing 15.1. Pay_input displays the Payment.jsp page, which
contains a form to take payment details. Submitting the form invokes the Pay action. The
Pay action is protected by the Token interceptor.
Click the Submit button and quickly click it again. You will see an error message displayed
on your browser.
Listing 15.7 shows the action declarations. Instead of the Token interceptor for the Pay
action, we use the Token Session interceptor. The JSPs are the same as those in app15a
and will not be reprinted here.
http://localhost:8080/app15b/Pay_input.action
Summary
Double form submits normally happen by accident or by the user's not knowing what to do
when it is taking a long time to process a form. The technique to prevent a form from being
submitted twice is by employing a token which is reset at the first submit of a form. Struts
has built-in support for handling this token, through the token tag and the Token and
Token Session interceptors.
This tag has one attribute, id, but you hardly need to use it.
The code in
The page in
If you click the [Debug] link, you'll see the stack objects and the objects in the context
map, as shown in
Figure 16.2.
You can use the debug tag to see the values of action properties and the contents of
objects such as the session and application maps. This will help you pinpoint any error in
your application quickly.
<debug>
<parameters/>
<context>
<attr/>
<report.conversion.errors>false</report.conversion.errors>
<struts.actionMapping>
<class>class
org.apache.struts2.dispatcher.mapper.ActionMapping</class>
<name>DebuggingTest</name>
<namespace>/</namespace>
</struts.actionMapping>
</context>
<request/>
<session/>
<valueStack>
<value>
<actionErrors/>
<actionMessages/>
<amount>0.0</amount>
<class>class app16a.Profiling</class>
<errorMessages/>
<errors/>
<fieldErrors/>
<locale>
<ISO3Country>USA</ISO3Country>
<ISO3Language>eng</ISO3Language>
<class>class java.util.Locale</class>
<country>US</country>
<displayCountry>United States</displayCountry>
<displayLanguage>English</displayLanguage>
<displayName>English (United States)</displayName>
<displayVariant></displayVariant>
<language>en</language>
<variant></variant>
</locale>
<transactionType>0</transactionType>
</value>
<value>
<class>class
com.opensymphony.xwork2.DefaultTextProvider</class>
</value>
</valueStack>
</debug>
Using debug=console displays a console like the one shown in Figure 16.3. You can
enter an OGNL expression to the bottom of the page and the value will be displayed.
Note
When I tested this feature, it did not work with Internet Explorer but worked perfectly with
Mozilla Firefox.
Profiling
Struts supports profiling that can potentially identify any bottleneck in your program. Struts
keeps track the time taken by its filter dispatcher, each interceptor, action execution, and
result execution with the help of a class called UtilTimerStack (a member of the
com.opensymphony.xwork2.util.profiling package). By default, however, the profiling result
is not shown. The Profiling interceptor, which is part of the default stack, can help activate
profiling. When profiling is activated for a particular action, the profiling result is printed by
an internal logger in UtilTimerStack on the container console or to a log file, depending on
the setting of your container. If you're using Tomcat, this will be the console (on Windows)
or the catalina.out file (on Unix and Linux).
Each line represents an activity. On the left of each line is the accumulated time taken to
invoke the activity. For example, the bottommost line says that executing the result took
10ms, whereas invoking the Upload2 action took Oms. Of course it does not mean that
there was no time at all to execute the action, it's just that it took less than what the timer
can measure.
The Conversion Error interceptor's accumulated time is also 10ms, which means the
invocation of this interceptor took Oms because the activities invoked after it consumed
10ms. The File Upload interceptor took 20ms to execute (40ms 20ms), and so on.
There are a few ways to activate profiling. Once it is active, it will stay active until it's
turned off or until the application is restarted.
1. Through the request parameter, by adding profiling=true or profiling=yes to the URL
that invokes the action to be profiled. For this to take effect, the struts.devMode
property must be true. For example, this URL turns on profiling.
http://localhost:8080/app16a/Test.action?profiling=true
key to pf so that you can turn on and off profiling by adding the request parameter
pf=true or pf=false.
<action name="ProfilingTest" class="app16a.Profiling">
<interceptor-ref name="profiling">
<param name="profilingKey">pf</param>
</interceptor-ref>
<interceptor-ref name="basicStack"/>
<result>/jsp/OK.jsp</result>
</action>
2. By setting the active property of the UtilTimerStack object through code in a servlet
listener or your action method.
public String execute() {
UtilTimerStack.setActive(true);
// do something
return SUCCESS;
}
3. By setting the UtilTimerStack.ACTIVATE_PROPERTY to true:
System.setProperty(UtilTimerStack.ACTIVATE_PROPERTY, "true");
You can also monitor a certain activity in your action code. To do this, you need to call the
push and pop methods on UtilTimerStack:
String activityName = "database access";
UtilTimerStack.push(activityName);
try {
// do some code
} finally {
UtilTimerStack.pop(activityName);
}
Summary
This chapter discusses two important topics that can help you make more robust
applications, debugging and profiling. For debugging you use the debug tag and the
Debugging interceptor. Profiling is a bundled feature in Struts that just needs activation.
The Profiling interceptor can be used to activate profiling. Alternatively, you can use code to
activate it.
By default n is 5 and url is the same URL used to invoke the current action.
You can create your own wait view if you don't like the default. If no wait result is found
under the action declaration, the default will be used.
The Execute and Wait interceptor can take these parameters, all optional.
The delay can be used if you don't want to send the wait result right away. For example,
you can set it to 2,000 so that the wait result will only be sent if the action takes longer
than two seconds.
Let's have a look at two examples in the section to follow.
Listing 17.1.
Listing 17.2.
Since Execute and Wait is not part of the default stack, you must declare it explicitly and it
must be the last interceptor to run. No wait result is declared and the final result is a
dispatcher that forwards to the OK.jsp page. The delay is set to 1,500 milliseconds, which
means the wait result will be sent after 1,5 seconds.
To test the example, direct your browser to this URL.
http://localhost:8080/app17a/HeavyDuty1.action
If you're interested enough to check, you'll see the source of the wait page as follows.
<html>
<head>
<meta http-equiv="refresh"
content="5;url=/app17a/HeavyDuty1.action"/>
</head>
<body>
Please wait while we process your request...
<p/>
This page will reload automatically and display your request
when it is completed.
</body>
</html>
Notice the meta tag? That's the one that forces the page to refresh every five seconds.
Listing 17.3.
The wait page is shown in Figure 17.2. Notice that it looks more like a progress meter
that indicates how much progress is being made?
Summary
This chapter discusses how you can use the Execute and Wait interceptor to handle timeconsuming tasks. The trick is to create a background thread that executes the action and
forward the user to a temporary wait page that keeps hitting the same action until the
background thread finishes its task.
Listing 18.1.
init. This method is called once right after the interceptor is created. An interceptor
author overrides this method to perform resource initialization.
intercept. This method is called every time the request for an action is invoked,
giving the interceptor a chance to do something before and after the action is
executed.
destroy. The method is called before the interceptor is destroyed. Code to release
resources should be written here.
Struts calls the intercept method of each interceptor registered for an action. Each time
this method is called, Struts passes an instance of the
com.opensymphony.xwork2.ActionInvocation interface. An ActionInvocation
represents the execution state of an action, from which an interceptor can obtain the
Action object as well as the Result object associated with the action. To let the execution
chain proceed to the next level, the interceptor calls the invoke method on
ActionInvocation.
You can also attach PreResultListener listeners to an ActionInvocation, by calling the
addPreResultListener method on the ActionInvocation. The
Listing 18.2.
the DataSource is obtained once from a JNDI lookup and is stored in a static variable.
Listing 18.4.
Using DataSourceInjectorInterceptor
Now that you have a custom interceptor, it is a good idea to put it to use. The app18a
application employs a Product_list action that uses this interceptor. Note that since this is
a custom interceptor, you must register it with the struts.xml file before you can use it. The
action and interceptor declarations for app18a are shown in
Listing 18.5.
Listing
18.7. A Product is a transfer object that encapsulates four properties, productId, name,
description, and price. The ListProductAction class implements DataSourceAware so
an instance of ListProductAction can be injected a DataSource.
}
}
}
return products;
}
}
Direct your browser to this URL to invoke the custom interceptor.
http://localhost:8080/app18a/Product_list.action
You will see the results shown in your browser, like those in Figure
depends on the content of the Products table in your database.
Summary
You can write custom interceptors by implementing the Interceptor interface or extending
the AbstractInterceptor class. In this chapter you learned how to write a custom
interceptor and how to register it in an application.
Overview
A result type must implement the com.opensymphony.xwork2.Result interface. This
interface has one method, execute, whose signature is as follows.
void execute(ActionInvocation invocation)
This method gets called when the result is executed. A result type author can write the code
that will be run when an instance of the result type executes.
Note
ActionInvocation was explained in Chapter 18, "Custom Interceptors."
The org.apache.struts2.dispatcher.StrutsResultSupport class is a base class that implements
the Result interface. Many result types extend this class instead of implementing Result
directly.
Another common use of CAPTCHA is to prevent spammers from sending messages to form
owners. CAPTCHA forms may be used to frustrate automatic programs that submit forms
because submission will only be successful if the correct word is also supplied.
The idea behind using CAPTCHA in forms is that computers are good with characters and
numbers but not so with images. Therefore, if you ask the computer what the word in the
image in Figure 19.1 reads, chances are the computer will not have a clue. Unless of
course you use a program designed to recognize images, which are already in existence but
are not so commonplace. In other words, CAPTCHA makes your login form more secure but
there's no 100% guarantee that it will protect you from the most determined people.
In a web form, CAPTCHA works by producing a pair of words. The first word is converted
into an image and the second word is produced using an algorithm in such a way that
different instances of the same word always produce the same second word. However,
knowing the second word is not good enough to find out what the first word is. Many
implementations of CAPTCHA use a hash algorithm to produce the second word.
There are several ways of producing CAPTCHA-facilitated forms. One way would be to
generate hundreds or thousands of word pairs and store them in a database. When you
send the form to the browser, you also send the image version of the first word and the
second word in a hidden field. When the form is submitted, the server matches the hidden
field value and the word typed in by the user. If the two match, the user passed the
CAPTCHA test.
Another way, one that does not require a database, is by using cookies. A Struts action
specializes in generating a word and its hash and converts the word to an image. At the
same time, the second word or the hash is sent to the browser as a cookie. When the form
is submitted, the server will match the value entered by the user and the cookie. The server
will do this by using the same algorithm that produces the word pair in the first place.
It sounds complicated, but I have written a Java library, free for download from
Returns an image representation of the specified word. The width and height arguments
specify the image size in pixel. The last argument is currently reserved for future use.
public static boolean validate(java.lang.String word,
java.lang.String hash)
Returns true if the specified hash is the hash of the specified word. Otherwise, returns false.
Now, let's see how we can create a result type that returns a CAPTCHA image with the help
of this library.
The CaptchaResult class in Listing 19.1 is the brain of the new result type. It extends
the StrutsResultSupport class and overrides its doExecute method.
Listing 19.2.
Listing 19.3.
Figure 19.1.
Summary
This chapter explained how you could write a custom result type. It also presented an
example of result type that streamed a CAPTCHA image to the browser.
Overview
Most Struts applications use JSP as the view technology. However, JSP is not the only view
technology Struts supports. Velocity and FreeMarker (discussed in Chapter 21,
"FreeMarker") can also be used to display data.
Velocity is a template language. A template is text that provides a basis for documents and
allows for words to be dynamically inserted into certain parts of it. For example, JSP can
serve as a template because it lets you insert values through the use of the Expression
Language. Since you already know JSP then it should not be hard to learn Velocity as both
are similar.
Unlike JSP, however, Velocity does not permit Java code to be used and only allows
rudimentary access to data. As such, developers are forced to separate presentation from
the business logic. In the past this "feature," the inability to use Java code, was often cited
by Velocity proponents as a reason to leave JSP and embrace Velocity. However, starting
from Servlet 2.0 you can now configure your servlet applications to disallow Java code in
JSPs and hence promote separation of presentation and logic.
Another point to note is that Velocity templates can be placed within the application or in
the class path. Contrast this with JSPs that can only be found if placed within the
application. Velocity will first search the application, if the template could not be found, it
will search the class path. In addition, Velocity templates can be loaded from a JAR while
JSPs cannot. Therefore, if you are deploying a component as a Struts plug-in, Velocity is a
great choice because you can include the templates in the same JAR as the other part of the
component.
Velocity supports simple control structures such as loops and if-else statements, though.
The dollar sign ($) has a special meaning in Velocity. It is used to indicate what follows is a
variable name that needs to be replaced at run-time.
The struts-default.xml file already defines the velocity result type, you can use Velocity in
Struts without writing additional configurations.
<result-type name="velocity"
class="org.apache.struts2.dispatcher.VelocityResult"/>
You just need to make sure that the following JAR files are copied to your WEB-INF/lib
directory: velocity-VERSION.jar, velocity-dep-VERSION.jar, and velocity-tools-VERSION.jar.
Name
Description
stack
action
response
res
request
req
session
Tags
Velocity in Struts extends the tags in the Struts tag library. Velocity tags are similar to the
Struts tags but the syntax for using them is slightly different. To start, you don't need this
taglib directive that you need when using JSP:
<%@ taglib prefix="s" uri="/struts-tags" %>
In JSP, a start tag is enclosed with < and > and an end tag with </ and >. In Velocity a
start tag starts with #s followed by the tag name. Most tags are inline and do not need an
end tag. For example:
#stextfield
Velocity tag attributes are enclosed in brackets. Each attribute name/value are enclosed in
double quotes and separated by an equal sign.
#stagName ("attribute-1=value-1" "attribute-2=value-2" ... )
For example:
#stextfield ("name=userName" "label=User Name")
Velocity Example
The app20a application illustrates the use of Velocity in Struts. It features two actions,
Product_input and Product_save, as declared using the action elements in
20.1.
Listing
Listing 20.4.
Figure 20.1.
If you click the Add Product button, you will see the content of the Details.vm template.
Summary
JSP is not the only view technology that can be used in Struts. Velocity and FreeMarker can
too, and so can XSLT. This chapter explained how you can use Velocity as a view
technology.
Overview
To use FreeMarker in Struts, you don't need to install additional software. The JAR file that
contains the FreeMarker engine, the freemarker-VERSION.jar file, is already included in
Struts deployment. In fact, without this file your Struts application won't work because
FreeMarker is the default template for the Struts tag library.
FreeMarker templates can be placed within the application directory or the class path. The
application directory will be searched first. The fact that the FreeMarker engine also
searches the class path makes this technology perfect for Struts because it enables
FreeMarker templates to be packaged in JAR files. As you'll learn in Chapter 23, "Plugins", plug-ins are distributed as JAR files. You cannot package JSPs in a JAR and hope the
web container will translate and compile them.
In Struts the FreeMarker engine searches for data in this order:
1.
2.
3.
4.
5.
6.
Built-in variables
The Value Stack
The action context
Request scope
Session scope
Application scope
Just like JSP, FreeMarker allows access to important objects such as the ServletContext
and HttpServletRequest.
Name
Description
Stack
action
Name
Description
response
res
request
req
session
FreeMarker Tags
Struts provides FreeMarker tags that are extensions to the tags in the Struts tag library.
The syntax is very similar to that in JSP. You use <@s.tag as the start tag and </@s.tag>
as the end tag, where tag is the tag name. For example, here is the form tag:
<@s.form action="...">
</@s.form>
<@s.form action="Product_save">
<@s.textfield name="name" label="Product Name"/>
<@s.textfield name="description" label="Description"/>
<@s.textfield name="price" label="Price"/>
<@s.submit value="Add Product"/>
</@s.form>
FreeMarker supports dynamic attributes, a feature missing in JSP. In JSP, you can use the
param tag to pass values to the containing tag. For instance:
<s:url value="myResource">
<s:param name="userId" value="%{userId}"/>
</s:url>
In FreeMarker you don't need to pass the parameter using the param tag. Instead, you can
treat the parameter as a dynamic attribute. The FreeMarker equivalent of the url tag above
will be:
<@s.url value="myResource" userId="${userId}"/>
Example
As an example, consider the app21a application that has two actions, Product_input and
Product_save. The application uses FreeMarker templates instead of JSPs.
The actions are declared in the struts.xml as shown in
Listing 21.1.
21.2. This is
Listings 21.3 and 21.4 shows two templates that sport FreeMarker tags.
Listing 21.3. The Product.ftl template
<html>
<head>
<title>Add Product</title>
<style type="text/css">@import url(css/main.css);</style>
</head>
<body>
<div id="global" style="width:330px">
<h3>Add Product</h3>
<@s.form action="Product_save">
<@s.textfield name="name" label="Product Name"/>
<@s.textfield name="description" label="Description"/>
<@s.textfield name="price" label="Price"/>
<@s.submit value="Add Product"/>
</@s.form>
</div>
</body>
</tr>
<tr>
<td>Description:</td>
<td>${description}</td>
</tr>
<tr>
<td>Price:</td>
<td>${price}</td>
</tr>
</table>
</div>
</body>
Note that to access an action property, you can use the property tag or the notation ${ ...
}.
To test the application, direct your browser to this URL.
http://localhost:8080/app21a/Product_input.action
Figure 21.1.
Submitting the form invokes the Product_save action that forwards to the Details.ftl
template. The result is shown in
Figure 21.2.
Summary
FreeMarker is the template language used to render tags in the Struts tag library. It is also
a good alternative to JSP and allows templates to reside in the class path, in addition to a
directory under the application directory. Because of this feature, FreeMarker templates can
be deployed in a JAR file, which makes FreeMarker suitable for plug-ins.
Overview
XML documents are used for easy data exchange. Unlike proprietary databases where data
is stored in proprietary formats that make exchanging data difficult, XML documents are
plain text and can be understood by just reading the documents. For example, this XML
document is self-explanatory, it contains information about an employee.
<employee>
<employeeId>34</employeeId>
<firstName>Jen</firstName>
<lastName>Goodhope</lastName>
<birthDate>2/25/1980</birthDate>
<hiredDate>3/22/2006</hiredDate>
</employee>
If you send this XML document, the receiving party can easily understand it and probably
manipulate it with their own tools. However, it's probably not as straightforward as you may
think. The other party may have XML documents containing details on employees, but the
format is slightly different. Instead of employeeId they might use id and instead of
employee they might call it worker.
<worker>
<id>50</employeeId>
<firstName>Max</firstName>
<lastName>Ocean</lastName>
<birthDate>12/13/1977</birthDate>
<hiredDate>10/5/2005</hiredDate>
</worker>
If the data from the first XML document is to be merged into the second XML document, for
example, there must be some kind of transformation that converts <employee> to
<worker> and <employeeId> to <id>. This is where XSLT plays a role.
Figure 22.1 shows how XSLT works. At the core is an XSLT processor that reads the
source XML and uses a stylesheet to transform an XML document into something else.
An XSL stylesheet is an XML file with an xsl or xslt extension. The root element of an XSL
stylesheet is either <xsl:stylesheet> or <xsl:transform>. Here is the skeleton of an XSL
stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
...
</xsl:stylesheet>
The xsl:stylesheet element has two attributes in this case. The first attribute declares the
version, which currently is 1.0. The second attribute declares the XML namespace. It points
to the official W3C XSLT namespace. The prefix xsl is preferred for an XSL stylesheet but
could be anything you like.
The list of elements can be found in the specification. Here are some of the more important
ones:
xsl:template. Defines a template. Its match attribute associates the template with
an element in the source XML. For example, this xsl:template element matches the
root of the source XML:
<xsl:template match="/">
xsl:value-of. Reads the value of an XML element and appends it to the output
stream of the transformation. You select an XML element by using the select
attribute. For instance, the following prints the value of the name element under
<result>.
<xsl:value-of select="/result/name"/>
xsl:for-each. Iterates over a node set. Again, use the select attribute to specify an
XML element. For example, this xsl:for-each element iterates over the
result/supplier elements and prints the details of each supplier and formats them
in an HTML table.
<table>
<xsl:for-each select="/result/supplier">
<tr>
<td><xsl:value-of select="supplierName"/></td>
<td><xsl:value-of select="address"/></td>
</tr>
</xsl:for-each>
</table>
Note there is also a deprecated location parameter that does the same thing as stylesheetLocation.
Note
By default XSLT stylesheets are cached. In development mode it's easier if they are not. You can change this behavior by setting
struts.xslt.nocache to true in the struts.properties file.
Consider the Product action class in Listing 22.1. The supplier property of Product is of type Supplier, shown in Listing 22.2.
<result>
<actionErrors></actionErrors>
<actionMessages></actionMessages>
<description>
<#text>Super printer</#text>
</description>
<errorMessages></errorMessages>
<errors></errors>
<fieldErrors></fieldErrors>
<locale>
<ISO3Country>
<#text>USA</#text>
</ISO3Country>
<ISO3Language>
<#text>eng</#text>
</ISO3Language>
<country>
<#text>US</#text>
</country>
<displayCountry>
<#text>United States</#text>
</displayCountry>
<displayLanguage>
<#text>English</#text>
</displayLanguage>
<displayName>
<#text>English (United States)</#text>
</displayName>
<displayVariant>
<#text></#text>
</displayVariant>
<language>
<#text>en</#text>
</language>
<variant>
<#text></#text>
</variant>
</locale>
<name>
<#text>Epson</#text>
</name>
<price>
<#text>12.34</#text>
</price>
<productId>
<#text>345</#text>
</productId>
<supplier>
<address>
<#text>Oakville, Ontario</#text>
</address>
<name>
<#text>Online Business Ltd. </#text>
</name>
<supplierId>
<#text>20a</#text>
</supplierId>
</supplier>
<texts>
<#text>null</#text>
</texts>
</result>
Example
As an example, the app22a application features an action that uses an XSLT result. The
action, XSLT, converts the Product action to XHTML. The Product class is the same class
shown in
22.4.
Listing 22.4. The Product.xsl template
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<product>
<productName>
<xsl:value-of select="/result/name"/>
</productName>
<productDescription>
<xsl:value-of select="/result/description"/>
</productDescription>
<price>
<xsl:value-of select="/result/price"/>
</price>
<supplierName>
<xsl:value-of select="/result/supplier/name"/>
</supplierName>
</product>
</xsl:template>
</xsl:stylesheet>
Listing
You can test the application by directing your browser to this URL:
http://localhost:8080/app22a/XSL.action
Summary
The XSLT result type transforms action objects to XML. This result type is not as common as
Dispatcher but may be used in applications that require XML outputs, such as web services.
In this chapter you learned how it works and how to use it in your Struts applications.
Overview
Struts has been designed to be extensible through plug-ins. Using a plug-in is as easy as
copying the plug-in JAR file to the WEB-INF/lib directory. Unlike an ordinary JAR file, a
plug-in may contain a struts-plugin.xml file that complies with the same rules as a
struts.xml file. It is possible to include configuration settings in a plug-in because Struts
loads configuration files in this order:
1. The struts-default.xml in the struts2-core- VERSION.jar file.
2. All struts-plugin.xml files in plug-ins deployed in the application.
3. The struts.xml file.
This means, you can override values defined in the struts-default.xml file in your strutsplugin.xml, even though the application will have the final say since anything defined in
the struts.xml file overrides similar settings in other configuration files.
You can distribute any type of Struts component in your plug-in, including new packages,
new result types, custom interceptors, actions, new tag libraries, and others.
At my last visit there were close to forty plug-ins available. I suspect there are others that
are not listed here.
Chapter 19, "Custom Result Types." Please read Chapter 19 now if you haven't done
so.
The CAPTCHA result type is based on the CaptchaResult class that extends
StrutsResultSupport. In order for the result type to be easily used in applications, you
need to package it as a plug-in. Since it is a result type, you need to register it in a strutsplugin.xml.
Now, create a JAR. The standard way, albeit not the easiest, is to use the jar program that
comes with your JDK by following these steps. This assumes that your JDK has been added
to the path directory so that you can invoke the jar program from anywhere in your
computer.
1. Change directory to the directory where the struts-plugin.xml resides. This
directory will also contain the com directory.
2. Type this command and press Enter.
jar -cvf captchaplugin.jar *
AJAR named captchaplugin.jar will be created. This JAR is your plug-in.
Listing 23.2.
Listing 23.3.
19.
Figure 23.2.
Summary
Struts provides an elegant way to distribute code: through plug-ins. This chapter showed
how easy it is to write and use one.
http://tiles.apache.org/. The classes that make up Tiles are deployed in three JAR
files, tiles-core- VERSION, tiles-api- VERSION.jar, and tiles-jsp- VERSION.jar. In
addition, to use Tiles with Struts, you need the struts2-tiles-plugin- VERSION..jar. All
these JARs are deployed with Struts 2. You must copy these JARs to your WEB-INF/lib
directory
To achieve a consistent look, each of your JSPs must contain a layout table such as this.
<html>
<head><title>Page title</title></head>
<body>
<table>
<tr>
<td colspan="3"><%@include file="header.jsp"%></td>
</tr>
<tr>
<td width="120"> <%@include file="menu.jsp"%></td>
<td>
body content
</td>
<td width="120"> <%@include file="ad.jsp"%></td>
</tr>
<tr>
<td colspan="3"><%@include file="footer.jsp"%></td>
</tr>
</table>
</body>
</html>
Note
A layout table is used just for illustration. You should always use CSS instead.
With this approach, what differentiates one JSP from another is the body content.
Now, what if you want to change the layout? For example, what if you want to make the
menu wider by 30 pixels? Or, what if you want the ad to appear on top of the menu? This
would require changing all your JSPs, which of course is a tedious and error-prone chore.
Tiles can help solve this problem.
Table 24.1.
Attribute Type
Description
name
String
value
String
flush
boolean A value of true causes the current page output stream to be flushed
before insertion.
ignore
role
String
preparer String
Specifies the role that the current user must belong to in order for
this tag to be executed.
The fully qualified name of the preparer.
The getAsString tag specifies a variable whose String value will be passed by objects
referencing the layout JSP. You would imagine that the getAsString tag in
would be passed a different page title by each JSP using this layout.
The complete list of getAsString attributes is given in
Listing 24.1
Table 24.2.
Attribute Type
Description
name
String
ignore
Attribute Type
role
String
Description
Specifies the role that the current user must belong to in order for
this tag to be executed.
Tiles Definitions
The second thing you need to grasp before you can use Tiles is definitions. A definition is a
layer between a layout page and a JSP using the layout. In Struts a Tiles definition
corresponds to a view. The view is normally a JSP, but Velocity or FreeMarker can also be
used.
By analogy, a layout page is like a Java interface and a definition page is a base class that
provides default method implementations of the interface. Any Java class that needs to
implement the interface can extend the base class, so that the class does not need to
implement a method unless it needs to override the default. By the same token, a JSP
references a definition page instead of a layout JSP. The diagram in Figure
comparison between Java inheritance and Tiles' layout and definition pages.
12.2 provides
Figure 24.2. Comparing Java inheritance and Tiles' layout and definition
Tiles definitions are defined in a tiles.xml file located in the WEB-INF directory of your
Struts application. A tiles.xml file must comply with the DTD file defined in the following
DOCTYPE declaration that must precede the root element.
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 2.0//EN"
"http://struts.apache.org/dtds/tiles-config_2_0.dtd">
The root element for a tiles definition file is tiles-definition. Under it you write one or more
definition element, each of which defines a definition.
Here is a definition that references the MyLayout.jsp page.
<definition name="MyDefinition" template="/jsp/MyLayout.jsp"/>
The name attribute specifies a name that will be used by a view to refer to this definition.
The template attribute specifies the template or layout page. In the example above, the
definition name is MyDefinition and the layout page is MyLayout.jsp.
The Product definition passes "Product Info" to the getAsString tag in the MyLayout.jsp
page and inserts the Header.jsp, Footer.jsp, and Product.jsp to the header, footer, body
insertAttribute tags, respectively. The Thanks definition passes "Thanks You" to the
getAsString tag and inserts the Header.jsp, Footer.jsp, and Thanks.jsp to the header,
footer, body insertAttribute tags, respectively.
A Struts result that needs to forward to a definition can refer to it by its name like this.
<action name="Product_input">
<result name="success" type="tiles">Product</result>
</action>
<action name="Product_add">
<result name="success" type="tiles">Thanks</result>
</action>
Contrast these tiles results with dispatcher results that forward to a JSP.
3. Extend the tiles-default package in your package or define the following in your
package:
<result-types>
<result-type name="tiles"
class="org.apache.struts2.views.tiles.TilesResult"/>
</result-types>
4. Use tiles results in your actions.
Now let's look at an example.
Listing 24.2.
Figure
The actions look like any ordinary actions, except that their results are of type tiles. Also,
instead of forwarding to JSPs, these results forward to definitions. The Product and Thanks
definitions are defined in the tiles.xml file shown in
Listing 24.3.
The same consistent layout is used for the Product_add action, as shown in
24.5.
Figure
Summary
Tiles helps Struts developers create a consistent look throughout an application. Tiles, which
is vastly superior to JSP includes, allows you to write layout and definition pages. This
chapter is meant to be a brief introduction to Tiles 2. For more details on Tiles, consult the
documentation at its website
http://tiles.apache.org/.
Plot
This abstract class is the main member of the org.jfree.chart.plot package. An instance of
Plot represents a plot that draws a chart. There are many subclasses of Plot that you can
use, one of which you'll see in the app25a application.
1. Download the JFreeChart component and copy the jfreechart- VERSION.jar and
jcommon- VERSION.jar files to your application's WEB-INF/lib directory.
2. Copy the struts-jfreechart-plugin-VERSION.jar file to the WEB-INF/lib directory.
3. Make sure that your Struts package extends jfreechart-default.
4. Use chart as the result type and pass the width and height parameters to the result.
5. Your action class must have a chart property that returns the JFreeChart object to be
displayed.
The plug-in sends the chart as a PNG image. You may want to use an img element to
request the chart so that you can include the chart in an HTML page.
The plug-in accepts two parameters, width and height, to give you a chance to change the
chart size, which by default is 200px X 150px.
As an example, the app25a application shows an action that uses JFreeChart. The action
declarations for the application are given in
Listing 25.1.
25.3.
aThere are two things in the JFreeChart plug-in that I did not really like and prompted me to
write my own plug-in, the BrainySoftware JFreeChart plug-in. The first is the fact that
jfreechart-default does not extend struts-default. The second is the fact that changing a
chart size requires updating the Struts configuration file. The exact size is often in the
graphic designer's hand and if he or she could resize the image without having to bother the
application administrator, it would be a much coveted feature.
1. Download the JFreeChart component and copy the jfreechart- VERSION.jar and
jcommon-VERSION.jar files to the WEB-INF/lib directory.
Application app25b shows an action that uses Brainy Software's JFreeChart plug-in. The
action declarations are shown in
Listing 25.4.
25.2,
200);
300);
500);
700);
700);
900);
XYSeriesCollection xyDataset =
new XYSeriesCollection(xySeries);
// create XYPlot
XYPlot xyPlot = new XYPlot(xyDataset, xAxis, yAxis,
new StandardXYItemRenderer(
StandardXYItemRenderer.LINES));
chart = new JFreeChart(xyPlot);
return SUCCESS;
}
// getters and setters not shown
}
Summary
JFreeChart is a powerful open-source library for generating charts. To use it in Struts, you
need a plug-in. At least two free JFreeChart plug-ins are available, the standard one that
comes with Struts and the one downloadable from
showed how to use both.
Conventions
Since you will not have a configuration file if you decide to go the zero configuration way,
you will need to tell Struts how to find your action classes. You do this by telling Struts the
Java packages of the action classes used in your application by including, in your web.xml
file, an actionPackages initial parameter to the Struts filter dispatcher. Like this.
<filter>
<filter-name>struts2</filter-name>
<filter-class>
org.apache.struts2.dispatcher.FilterDispatcher
</filter-class>
<init-param>
<param-name>actionPackages</param-name>
<param-value>app26a,com.example</param-value>
</init-param>
</filter>
The value of the actionPackages parameter is a comma-delimited list of packages that
Struts needs to scan for action classes. In the example above, Struts will scan the app26a
package and its sub-packages as well as the com.example package and its sub-packages.
An action class of a zero configuration application must either implement the
com.opensymphony.xwork2.Action interface (or by extending
com.opensymphony.xwork2.ActionSupport) or has an Action suffix on its name. For
example, a POJO class named CustomerAction will comply. A child class of ActionSupport
named User will also be acceptable.
Now, since without a struts.xml file you cannot give an action a name, you rely on Struts to
do that. What name does Struts give your action? The action name will be the same as the
name of the action class after the first letter of the class name is converted to lower case
and its Action suffix, if any, is removed. Therefore, the action name for an action class
named EmployeeAction will be employee, and you can invoke it using the URI
employee.action.
Of course you must also take into account the namespace. If an action class is not a
member of a package passed to the actionPackages parameter, but rather a member of its
sub-package, the part of the subpackage name is not in the actionPackages parameter will
be the namespace. For instance, if com.example is passed to the actionPackages parameter,
the action class com.example.action.CustomerAction will be accessible through this URI:
/action/customer.action
Annotations
By following the conventions explained in the previous section, you can invoke action
classes in your zero configuration application. But hold on, Struts does not know yet what
results are associated with those action classes. This time you need to annotate, using the
annotation types discussed in this section.
@Result
The org.apache.struts2.config.Result annotation type is used to define an action result.
It has these elements, of which only value is required.
name. The name of the result that corresponds to the return value of the action
method.
params. An array of Strings used to pass parameters to the result.
type. The class of the result type whose instance will handle the result.
value. The value passed to the result.
Note
When going zero configuration, you need to get familiar with the underlying classes for the
bundled result types, not only their short names. You can look up the class names in
Appendix A.
@Results
If an action method may return one of two values, say "success" or "input," you cannot use
two Result annotations. Instead, use @Results. The syntax for this annotation type is as
follows.
@Results({ @Result-l, @Result-2, ... @Result-n })
@Namespace
Use this annotation type to override the namespace convention. It has one element, value,
which specifies the namespace for the annotated class.
For example, the actionPackages value of app26a is app26a. By convention, the
namespace of the action class app26a.admin.action.EditCustomer will be
/admin/action, and the class can be invoked using this URI:
/admin/action/editCustomer.action. To override this, use the Namespace annotation
type.
As an example, the EditCustomer class in Listing 26.3 is annotated @Namespace.
Since the value of the annotation is "/," it can be invoked using this URI:
/editCustomer.action.
Consequently, you can no longer use this URL to invoke the editCustomer action.
http://localhost:8080/app26a/admin/action/editCustomer.action
@ParentPackage
Use this annotation type to inherit an XWork package other than struts-default. For
example, this annotation indicates that the action belongs to the captcha-default package:
@ParentPackage(value="struts-default")
26.4.
Listing
userName.equals("don")
password.equals("secret")) {
SUCCESS;
INPUT;
}
// getters and setters not shown
}
The action method (execute) returns either "input" or "success." As such, the forward JSP
will have to be either login-input.jsp or login-success.jsp. These JSPs are shows in
Listings 26.6 and 26.7. Note that in Listing 26.6, because there's no explicit action
declaration, you need to pass a URI and not an action name to the form's action attribute.
Figure 26.1.
When you submit the form, the field values are sent to this URL:
http://localhost:8080/app26b/login.action
Summary
This chapter discussed the zero configuration feature in Struts that can match a URL with an
action class. This feature does not match actions and results, however, and for the latter
you need the CodeBehind plug-in.
The Struts Dojo plug-in itself is not included in the lib directory of the Struts distribution and
must be extracted from the Showcase application that comes with Struts. Unfortunately, the
version of Dojo in this plug-in is 0.4, which is a much older version than what is available at
the time of writing (version 1.01). Version 0.4 is very slow compared with its successors.
The next release of the Struts Dojo plug-in is expected to bring Dojo 1.01 or later to the
table.
Another unfortunate fact is that Dojo 1.0 or later is not backward compatible with version
0.4, which means any code you write that uses this plug-in may not work with a future
version of the plug-in. Having said that, the plug-in is still great software that can help you
write AJAX applications easily.
Note
Another popular JavaScript framework is Prototype (http://prototypejs.org/), which provides
a set of JavaScript objects with a very small footprint, enabling fast download. In addition,
Scriptaculous (http://script.aculo.us/) provides AJAX components that are based on
Prototype.
AJAX Overview
AJAX is a name coined by Jesse James Garrett of Adaptive Path for two old technologies,
JavaScript and XML. AJAX applications asynchronously connect to the server to collect more
data that can be displayed in the current web page. As a result, new information can be
shown without page refresh. Google was the first to popularize this strategy with their Gmail
and Google Maps applications. However, Google was not the first to make full use of the
XMLHttpRequest object, the engine that makes asynchronous connections possible.
Microsoft added it to Internet Explorer 5 and seasoned developers discovered ways to reap
its benefits. Soon afterwards Mozilla browsers also had their own version of this object. Prior
to XMLHttpRequest, people used DHTML and HTML frames and iframes to update pages
without refresh.
Despite advance in client-side technologies, writing JavaScript code, hence AJAX
applications, is still intimidating. Even though IDEs are available for writing JavaScript code,
programmers still have to overcome the biggest challenge in writing client-side applications:
browser compatibility. It is a fact of life that every browser implements JavaScript slightly
differently from each other. Even the same browser does not interpret JavaScript in the
same way in different operating systems. As a result, you have to test your script in various
operating systems using various browsers and write multiple versions of code that work in
all browsers.
This is where a JavaScript framework like Dojo comes to rescue. With Dojo you only need to
write and test once and let it worry about browser compatibility. Needless to say, using the
Struts Dojo plug-in as your AJAX platform saves an awful lot of time.
The topic name can be anything. As long as the other parties know a topic name, they can
subscribe to the topic.
In AJAX programming, you normally subscribe to a topic because you want something to be
done upon a message publication to that topic. As such, when you subscribe to a topic, you
also define what you need to do or what function to call. Here is the method to subscribe to
a topic in Dojo. Again, this is Dojo 0.4 we're talking here.
Dojo.event.topic.subscribe(topicName, functionName)
The tags in the Struts Dojo plug-in make it even easier to work with topics. Most tags can
subscribe and publish a topic without JavaScript code. For instance, the a tag has an
errorNotifyTopics attribute you can use to list the topics to publish when the tag raises an
error. The div tag has a startTimerListenTopics attribute to accept a list of topics that will
cause the rendered div element to start its internal timer.
Topic-based messaging system will become clearer after you learn about the tags.
2. Copy the Struts Dojo plug-in to your WEB-INF/lib directory. This plug-in is included
in the lib directory of this book.
3. Write the head tag on each JSP.
Let's now look at the tags in the Struts Dojo plug-in.
Name
Data
Type
baseRelativePath String
cache
Default
Value
Description
boolean true
Name
Data
Type
compressed
boolean true
debug
boolean false
extraLocales
String
locale
String
parseContent
boolean false
Default
Value
Description
The compressed attribute, which is true by default, indicates whether or not the
compressed version of Dojo files should be used. Using the compressed version saves
loading time, but it is hard to read. In development mode you may want to set this attribute
to false so that you can easily read the code rendered by the tags discussed in this chapter.
In development mode you should also set the debug attribute to true and the cache
attribute to false. Turning on the debug attribute makes Dojo display warnings and error
messages at the bottom of the page.
Here is how your head tag may look like in development mode.
<sx:head debug="true" cache="false" compressed="false" />
Table 27.2.
Name
Data
Type
afterNotifyTopics
String
autoStart
boolean true
beforeNotifyTopics
String
closable
boolean false
delay
integer
errorNotifyTopics
String
errorText
String
executeScripts
boolean false
Default
Value
Description
Name
Data
Type
formFilter
String
formId
String
handler
String
highlightColor
String
highlightDuration
integer 2000
href
String
indicator
String
javascriptTooltip
boolean false
listenTopics
String
loadingText
String
notifyTopics
String
Default
Value
Description
Name
Data
Type
Default
Value
Description
String
parseContent
boolean true
preload
boolean true
refreshOnShow
boolean false
separateScripts
boolean true
showLoadingText
boolean false
startTimerListenTopics String
stopTimerListenTopics
String
transport
String
Name
Data
Type
updateFreq
integer
Default
Value
Description
Example 1
The Div1.jsp page in Listing 27.1 uses a div tag that updates itself every three
seconds. The href attribute is used to specify the server location that will return the content
and the updateFreq attribute specifies the update frequency in milliseconds. The internal
timer starts automatically because by default the value of the autoStart attribute is true.
Listing27.1.
http://localhost:8080/app27a/Div1.action
Example 2
The Div2.jsp page in Listing 27.2 showcases a div tag whose startTimerListenTopics
attribute is set to subscribe to a startTimer topic. Upon publication of this topic, the div's
internal timer will start. A submit button is used to publish a startTimer topic.
Example 3
This div tag in the Div3.jsp page in
publish a topic.
The function associated with the updateCounter topic increments a counter and changes
the content of a second div tag.
To test this example, direct your browser to this URL.
http://localhost:8080/app27a/Div3.action
The a Tag
The a tag renders an HTML anchor that, when clicked, makes an AJAX request. The targets
attribute of the tag is used to specify elements, normally div elements, that will display the
AJAX response. If nested within a form, this tag will submit the form when clicked.
Table
Name
Data
Type
afterNotifyTopics
String
ajaxAfterValidation
boolean false
beforeNotifyTopics
String
errorNotifyTopics
String
errorText
String
executeScripts
boolean false
formFilter
String
Default
Value
Description
Name
Data
Type
formId
String
handler
String
highlightColor
String
highlightDuration
integer 2000
href
String
indicator
String
javascriptTooltip
boolean false
listenTopics
String
loadingText
String
notifyTopics
String
openTemplate
String
Default
Value
Description
Name
Data
Type
Default
Value
Description
rendered HTML
parseContent
boolean true
separateScripts
boolean true
showLoadingText
boolean false
targets
String
transport
String
validate
boolean false
Like the a tag, submit has a targets attribute to specify elements that will display the
result of the form submit.
Name
Data
Type
Default
Value
Description
afterNotifyTopics
String
ajaxAfterValidation
boolean false
beforeNotifyTopics
String
errorNotifyTopics
String
errorText
String
executeScripts
boolean false
formFilter
String
formId
String
handler
String
Name
Data
Type
Default
Value
Description
highlightColor
String
highlightDuration
integer 2000
href
String
indicator
String
javascriptTooltip
boolean false
listenTopics
String
loadingText
String
method
String
notifyTopics
String
parseContent
boolean true
separateScripts
boolean true
Name
Data
Type
Default
Value
Description
showLoadingText
boolean false
src
String
targets
String
transport
String
type
String
input
validate
boolean false
The submit tag can be nested within the form it submits or stand independently. This
submit tag is nested within a form.
<s:div id="div1">
<s:form action="ServerTime.action">
<s:submit targets="div1"/>
</s:form>
</s:div>
And this is a submit tag outside the form it submits. In this case, you use the formId
attribute to specify the form to submit.
Table 27.5
Name
Data
Type
Default
Value
Description
afterNotifyTopics
String
ajaxAfterValidation
boolean false
beforeNotifyTopics
String
errorNotifyTopics
String
errorText
String
Name
Data
Type
Default
Value
Description
events
String
executeScripts
boolean false
formFilter
String
formId
String
handler
String
highlightColor
String
highlightDuration
integer 2000
href
String
indicator
String
listenTopics
String
Name
Description
Data
Type
Default
Value
loadingText
String
notifyTopics
String
separateScripts
boolean true
showLoadingText
boolean false
sources
String
targets
String
transport
String
validate
boolean false
As an example, the following bind tag attaches the b1 submit button's onclick event with
an AJAX call to MyAction.action and the response to the div element div1.
<sx:bind id="binder"
href="MyAction.action"
sources="b1"
events="onclick"
targets="div1" />
<s:submit id="b1" theme="simple" type="submit" />
The following bind tag causes the onclick event of the b2 button to publish the myTopic
topic.
<input id="b2" type="button">
<sx:bind
id="binder"
beforeNotifyTopics="myTopic"
sources="b2"
events="onclick"/>
Figure 27.1
Table 27.6.
Name
Data
Type
Default
Value
Description
adjustWeeks
boolean false
dayWidth
String
displayFormat
String
displayWeeks
integer 6
endDate
Date
formatLength
String
short
language
String
startDate
Date
staticDisplay
boolean false
toggleDuration
integer 100
toggleType
String
narrow
plain
Name
Data
Type
Description
Default
Value
String
date
valueNotifyTopics String
weekStartsOn
integer 0
The acceptable date and time patterns for the displayFormat attribute can be found here:
http://www.unicode.org/reports/tr35/tr35-4.html#Date_Format_Patterns
The adjustWeeks attribute plays an important role in the display. If the value of
adjustWeeks is false, there are always six rows for each month. For example, in
Figure
27.2 the picker on the left is displaying January 2008 and has its adjustWeeks attribute
set to false. The one on the right, on the other hand, has its adjustWeeks attribute set to
true and, as a result, the second week of February 2008 is not shown.
You can view the example by directing your browser to this URL.
http://localhost:8080/app27a/DateTimePicker.action
Table 27.7.
Name
Data
Type
Default
Value
Description
afterNotifyTopics
String
ajaxAfterValidation
boolean false
beforeNotifyTopics
String
errorNotifyTopics
String
Name
Data
Type
Default
Value
Description
errorText
String
executeScripts
boolean false
formFilter
String
formId
String
handler
String
highlightColor
String
highlightDuration
integer 2000
href
String
indicator
String
javascriptTooltip
boolean false
Name
Data
Type
Default
Value
Description
listenTopics
String
loadingText
String
notifyTopics
String
parseContent
boolean true
separateScripts
boolean true
showLoadingText
boolean false
targets
String
transport
String
validate
boolean false
Chapter 5, "Form
For example, the following tabbedpanel tag contains two div elements as its panels.
<sx:tabbedpanel id="test">
<sx:div label="Server Time" cssStyle="height:200px"
href="ServerTime.action">
Server Time
</sx:div>
<sx:div label="Closable" closable="true">
This pane can be closed.
</sx:div>
</sx:tabbedpanel>
integer
rows integer
wrap boolean
Description
Name
afterNotifyTopics
Data
Type
Default Value
Description
String
beforeNotifyTopics String
autoComplete
Name
Data
Type
Default Value
Description
dataFieldName
String
value in the
The name of the field in the returned
name attribute JSON object that contains the data array
delay
integer 100
dropdownHeight
integer 120
dropdownWidth
emptyOption
boolean false
errorNotifyTopics
String
forceValidOption
boolean false
formFilter
String
formId
String
headerKey
String
headerValue
String
href
String
iconPath
String
indicator
String
Name
Data
Type
Default Value
Description
boolean false
keyName
String
list
String
listKey
String
listValue
String
listenTopics
String
loadMinimumCount integer 3
maxlength
integer
notifyTopics
String
Name
Data
Type
Default Value
Description
preload
boolean true
resultsLimit
integer 30
searchType
String
showDownArrow
boolean true
transport
String
valueNotifyTopics
String
startstring
XMLHttp
Transport
Note
For more information on JSON, visit
http://json.org
Like other form tags, the autocompleter tag should be nested within a form. When the
user submits the form, two key/value pairs associated with the autocompleter will be sent
as request parameters. The key for the first request parameter is the value of the
autocompleter tag's name attribute. The key for the second request parameter is by
default the value of the name attribute plus the suffix Key. That is, if the value of the
name attribute is searchWord, the key of the second request parameter will be
searchWordKey. You can override the second key name using the keyName attribute.
The keyName attribute is the one that should be mapped with an action property. Its value
will be the value of the selected option.
The attributes for autocompleter are given in
Table 27.9.
Three examples illustrate the use of autocompleter. All examples use the
AutoCompleterSupport class in
Listing 27.5.
Example 1
This example shows how you can populate an autocompleter by assigning a List to its list
attribute. The JSP in
When the containing form is submitted, the selected option will be sent as the request
parameter carMakeKey.
Example 2
This example shows how to populate an autocompleter by assigning a JSON object. The
location of the server that returns the object must be assigned to its href attribute and, for
security reasons, it must be the same location as the origin of the page.
The AutoCompleter2.jsp page in
http://localhost:8080/app27a/AutoCompleter2.action
Example 3
This example is similar to Example 2 and the JSP is shown in
Listing 27.9.
Listing 27.10 shows the JSP that formats the options as a JSON object.
Table 27.11.
Table 27.10. tree tag attributes
Name
blankIconSrc
Data
Type
String
Default
Value
Description
The source for the blank icon
childCollectionProperty String
collapsedNotifyTopics String
errorNotifyTopics
String
expandIconSrcMinus
String
expandIconSrcPlus
String
Name
Data
Type
Default
Value
Description
expandedNotifyTopics String
gridIconSrcC
String
gridIconSrcL
String
gridIconSrcP
String
gridIconSrcV
String
gridIconSrcX
String
gridIconSrcY
String
href
String
iconHeight
String
18px
iconWidth
String
19px
javascriptTooltip
boolean false
nodeIdProperty
nodeTitleProperty
openTemplate
String
Data
Type
Name
Default
Value
Description
rootNode
String
selectedNotifyTopics
String
showGrid
boolean true
showRootGrid
boolean true
toggle
String
toggleDuration
integer 150
fade
Name
Data
Type
Description
Default
Value
openTemplate
String
Example 1
This example shows how to build a tree statically, by adding all nodes to the page. This is a
simple example that is pretty much self-explanatory. The Tree1.jsp page in
Listing
27.11 shows the tree and treenode tags used for the tree.
Listing 27.11. The Tree1.jsp page
<%@ taglib prefix="sx" uri="/struts-dojo-tags" %>
<html>
<head>
<title>Tree</title>
<sx:head debug="true"/>
</head>
<body>
<sx:tree id="root" label="Root">
<sx:treenode id="F1" label="F1" />
<sx:treenode id="F2" label="F2">
<sx:treenode id="F2a" label="F2a" />
<sx:treenode id="F2b" label="F2b" />
</sx:treenode>
<sx:treenode id="F3" label="F3" />
</sx:tree>
</body>
</html>
To test the example, direct your browser to this URL.
http://localhost:8080/app27a/Tree1.action
Figure 27.6.
Example 2
This example shows how you can construct a tree dynamically. At minimum, the tree tag
must have the following attributes: rootNode, nodeTitleProperty, nodeIdProperty,
childCollectionProperty. In addition, you must also create a model object to back up your
view.
The Tree2 action, the action for this example, is associated with the TreeSupport action
class in Listing 27.12. The class provides the rootNode property that maps to the
rootNode attribute of the tree tag.
Listing 27.13. It is a simple JavaBean class with three properties, id, title, and
children. The children property returns the children for the tree node. A static counter is
used so that it does not loop indefinitely.
topic to indicate to Dojo that selecting a node must publish the topic. A JavaScript function
subscribes to the topic.
Summary
Struts comes with a plug-in that provides custom tags to construct AJAX components. This
plug-in, the Struts Dojo plug-in, is part of Struts 2.1 and later and is based on Dojo 0.4.
This chapter showed how you can use the tags.
The root element of a struts.xml file is struts. This section explains elements that may
appear under the struts element, either directly or indirectly. The following elements can be
direct sub-elements of <struts>.
package
include
bean
constant
Attribute
Description
name*
class
An action may or may not specify an action class. Therefore, an action element may be as
simple as this.
<action name="MyAction">
An action that does not specify an action class will be given an instance of the default action
class.
If an action has a non-default action class, however, you must specify the fully class name
using the class attribute. In addition, you must also specify the name of the action method,
which is the method in the action class that will be executed when the action is invoked.
Here is an example.
<action name="Address_save" class="app.Address" method="save">
If the class attribute is present but the method attribute is not, execute is assumed for
the method name. In other words, the following action elements mean the same thing.
<action name="Employee_save" class="app.Employee" method="execute">
<action name="Employee_save" class="app.Employee">
Attribute
Description
class*
type
name
scope
The bean scope. Allowable values are default, singleton, request, session, and
thread.
Attribute
static
Description
Indicates whether or not to inject static methods.
Table A.3. Both the name and value attributes are required.
Attribute
Description
name*
value*
For example, the struts.devMode setting determines whether or not the Struts application
is in development mode. By default, the value is false, meaning the application is not in
development mode. The following constant element sets struts.devMode to true.
<constant name="struts.devMode" value="true"/>
<default-action-ref name="Main"/>
Table A.4.
Table A.4. exception-mapping element attributes
Attribute
name
Description
The name for this mapping.
You can nest one or more exception-mapping elements under your action declaration. For
example, the following exception-mapping element catches all exceptions thrown by the
User_save action and executes the error result.
<action name="User_save" class="...">
<exception-mapping exception="java.lang.Exception"
result="error"/>
<result name="error">/jsp/Error.jsp</result>
<result>/jsp/Thanks.jsp</result>
</action>
See
<struts>
<include file="module-1.xml" />
<include file="module-2.xml" />
...
<include file="module-n.xml" />
</struts>
Each module.xml file would have the same DOCTYPE element and a struts root element.
Here is an example:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<!-- file module-n.xml -->
<struts>
<package name="test" extends="struts-default">
<action name="Test1" class="test.Test1Action">
<result>/jsp/Result1.jsp</result>
</action>
<action name="Test2" class="test.Test2Action">
<result>/ajax/Result2.jsp</result>
</action>
</package>
</struts>
Attribute
Description
name*
class*
For instance, the following interceptor element registers the File Upload interceptor.
<interceptor name="fileUpload"
class="org.apache.struts.interceptor.FileUploadInterceptor"/>
extends="struts-default">
name="alias" class="..."/>
name="i18n" class="..."/>
name="validation" class="..."/>
name="logger" class="..."/>
<interceptor-stack name="basicStack">
<interceptor-ref name="exception"/>
<interceptor-ref name="servlet-config"/>
<interceptor-ref name="prepare"/>
<interceptor-ref name="checkbox"/>
<interceptor-ref name="params"/>
<interceptor-ref name="conversionError"/>
</interceptor-stack>
To use these interceptors, you just need to reference the stack:
<action name="..." class="...">
<interceptor-ref name="basicStack"/>
<result name="input">/jsp/Product.jsp</result>
<result>/jsp/ProductDetails.jsp</result>
</action>
Table A.6.
Attribute
Description
name*
The package name that must be unique throughout the struts.xml file.
extends
Attribute
abstract
Description
Indicates whether or not this is an abstract package.
A package element must specify a name attribute and its value must be unique
throughout the struts.xml file. It may also specify a namespace attribute. If namespace
is not present, the default value "/" will be assumed. If the namespace attribute has a nondefault value, the namespace must be added to the URI that invokes the actions in the
package. For example, the URI for invoking an action in a package with a default
namespace is this:
/context/actionName.action
To invoke an action in a package with a non-default namespace, you need this URI:
/context/namespace/actionName.action
A package element almost always extends the struts-default package defined in strutsdefault.xml. The latter is the default configuration file included in the Struts core JAR and
defines the standard interceptors and result types. A package that extends struts-default
can use the interceptors and result types without re-registering them. The content of the
struts-default.xml file is given in the next section.
Used within an action element, param can be used to set an action property. For example,
the following param element sets the siteId property of the action.
<action name="customer" class="...">
<param name="siteId">california01</param>
</action>
And the following param element sets the excludeMethod of the validation interceptorref:
<interceptor-ref name="validation">
<param name="excludeMethods">input,back,cancel</param>
</interceptor-ref>
Table A.7.
Attribute
Description
name
The result name, associated with the action method's return value.
type
For instance, the following action element contains two result elements.
<action name="Product_save" class="app.Product" method="save">
<result name="success" type="dispatcher">
/jsp/Confirm.jsp
</result>
<result name="input" type="dispatcher">
/jsp/Product.jsp
</result>
</action>
Table A.8.
Attribute
Description
name
class
default
Specifies whether or not this is the default result type for the package.
For instance, these two result-type elements register the Dispatcher and FreeMarket result
types in the struts-default package. Note that the default attribute of the first resulttype element is set to true.
<result-type name="dispatcher" default="true"
class="org.apache.struts2.dispatcher.ServletDispatcherResult"/>
<result-type name="freemarker" class="org.apache.struts2.views.
freemarker.FreemarkerResult"/>
Listing A.1.
class="com.opensymphony.xwork2.interceptor.I18nInterceptor"/>
<interceptor name="logger"
class="com.opensymphony.xwork2.interceptor.LoggingInterceptor"/>
<interceptor name="model-driven"
class="com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor"/>
<interceptor name="scoped-model-driven"
class="com.opensymphony.xwork2.interceptor.ScopedModelDrivenInterceptor"/>
<interceptor name="params"
class="com.opensymphony.xwork2.interceptor.ParametersInterceptor"/>
<interceptor name="prepare"
class="com.opensymphony.xwork2.interceptor.PrepareInterceptor"/>
<interceptor name="static-params"
class="com.opensymphony.xwork2.interceptor.StaticParametersInterceptor"/>
<interceptor name="scope"
class="org.apache.struts2.interceptor.ScopeInterceptor"/>
<interceptor name="servlet-config"
class="org.apache.struts2.interceptor.ServletConfigInterceptor"/>
<interceptor name="sessionAutowiring"
class="org.apache.struts2.spring.interceptor.SessionContextAutowiringIntercep
tor"/>
<interceptor name="timer"
class="com.opensymphony.xwork2.interceptor.TimerInterceptor"/>
<interceptor name="token"
class="org.apache.struts2.interceptor.TokenInterceptor"/>
<interceptor name="token-session"
class="org.apache.struts2.interceptor.TokenSessionStoreInterceptor"/>
<interceptor name="validation"
class="com.opensymphony.xwork2.validator.ValidationInterceptor"/>
<interceptor name="workflow"
class="com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor"/>
<interceptor name="store"
class="org.apache.struts2.interceptor.MessageStoreInterceptor"/>
<interceptor name="checkbox"
class="org.apache.struts2.interceptor.CheckboxInterceptor"/>
<interceptor name="profiling"
class="org.apache.struts2.interceptor.ProfilingActivationInterceptor"/>
<!-- Basic stack -->
<interceptor-stack name="basicStack">
<interceptor-ref name="exception"/>
<interceptor-ref name="servlet-config"/>
<interceptor-ref name="prepare"/>
<interceptor-ref name="checkbox"/>
<interceptor-ref name="params"/>
<interceptor-ref name="conversionError"/>
</interceptor-stack>
<!-- Sample validation and workflow stack -->
<interceptor-stack name="validationWorkflowStack">
<interceptor-ref name="basicStack"/>
<interceptor-ref name="validation"/>
<interceptor-ref name="workflow"/>
</interceptor-stack>
<!-- Sample file upload stack -->
<interceptor-stack name="fileUploadStack">
<interceptor-ref name="fileUpload"/>
<interceptor-ref name="basicStack"/>
</interceptor-stack>
<!-- Sample model-driven stack -->
<interceptor-stack name="modelDrivenStack">
<interceptor-ref name="model-driven"/>
<interceptor-ref name="basicStack"/>
</interceptor-stack>
<!-- Sample action chaining stack -->
<interceptor-stack name="chainStack">
<interceptor-ref name="chain"/>
<interceptor-ref name="basicStack"/>
</interceptor-stack>
<!-- Sample i18n stack -->
<interceptor-stack name="i18nStack">
<interceptor-ref name="i18n"/>
<interceptor-ref name="basicStack"/>
</interceptor-stack>
<!-- An example of the params-prepare-params trick. This
stack is exactly the same as the defaultStack,
except that it \includes one extra interceptor
before the prepare interceptor: the params
interceptor.
This is useful for when you wish to apply
parameters directly to an object that you wish to
load externally (such as a DAO or database or
service layer), but can't load that object until at
least the ID parameter has been loaded. By loading
the parameters twice, you can retrieve the object
in the prepare() method, allowing the second params
interceptor to apply the values on the object. -->
<interceptor-stack name="paramsPrepareParamsStack">
<interceptor-ref name="exception"/>
<interceptor-ref name="alias"/>
<interceptor-ref name="params"/>
<interceptor-ref name="servlet-config"/>
<interceptor-ref name="prepare"/>
<interceptor-ref name="i18n"/>
<interceptor-ref name="chain"/>
<interceptor-ref name="model-driven"/>
<interceptor-ref name="fileUpload"/>
<interceptor-ref name="checkbox"/>
<interceptor-ref name="static-params"/>
<interceptor-ref name="params"/>
<interceptor-ref name="conversionError"/>
<interceptor-ref name="validation">
<param name="excludeMethods">
input,back,cancel
</param>
</interceptor-ref>
<interceptor-ref name="workflow">
<param name="excludeMethods">
input,back,cancel
</param>
</interceptor-ref>
</interceptor-stack>
<!-- A complete stack with all the common interceptors
in place.
Generally, this stack should be the one you use,
though it may do more than you need. Also, the
ordering can be switched around (ex: if you wish to
have your servlet-related objects applied before
prepare() is called, you'd need to move servletconfig interceptor up.
This stack also excludes from the normal validation
and workflow the method names input, back, and
cancel. These typically are associated with
requests that should not be validated. -->
<interceptor-stack name="defaultStack">
<interceptor-ref name="exception"/>
<interceptor-ref name="alias"/>
<interceptor-ref name="servlet-config"/>
<interceptor-ref name="prepare"/>
<interceptor-ref name="i18n"/>
<interceptor-ref name="chain"/>
<interceptor-ref name="debugging"/>
<interceptor-ref name="profiling"/>
<interceptor-ref name="scoped-model-driven"/>
<interceptor-ref name="model-driven"/>
<interceptor-ref name="fileUpload"/>
<interceptor-ref name="checkbox"/>
<interceptor-ref name="static-params"/>
<interceptor-ref name="params"/>
<interceptor-ref name="conversionError"/>
<interceptor-ref name="validation">
<param name="excludeMethods">
input,back,cancel,browse
</param>
</interceptor-ref>
<interceptor-ref name="workflow">
<param name="excludeMethods">
input,back,cancel,browse
</param>
</interceptor-ref>
</interceptor-stack>
<!-- The completeStack is here for backwards
compatibility for applications that still refer to
the defaultStack by the old name -->
<interceptor-stack name="completeStack">
<interceptor-ref name="defaultStack"/>
</interceptor-stack>
<!-- Sample execute and wait stack.
Note: execAndWait should always be the *last*
interceptor. -->
<interceptor-stack name="executeAndWaitStack">
<interceptor-ref name="execAndWait">
<param name="excludeMethods">
input,back,cancel
</param>
</interceptor-ref>
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="execAndWait">
<param name="excludeMethods">
input,back,cancel
</param>
</interceptor-ref>
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="defaultStack"/>
</package>
</struts>
The auto-wiring logic when using the SpringObjectFactory. Valid values are name (the
default), type, auto, and constructor.
struts.objectFactory.spring.useClassCache = true
The default save directory for file upload. The default value is the directory indicated by
javax.servlet.context.tempdir.
struts.multipart.maxSize = 2097152
The action mapper to handle how request URLs are mapped to and from actions. The
default value is org.apache.struts2.dispatcher.mapper.DefaultActionMapper.
struts.action.extension = action
Indicates whether or not Struts should serve static content from inside its JAR. A value of
false indicates that the static content must be available at <contextPath>/struts.
struts.serve.static.browserCache = true
Indicates if the filter dispatcher should write out headers for static contents that will be
cached by web browsers. A value of true is suitable for development mode. This key will be
ignored if struts.serve.static is false.
struts.enable.DynamicMethodInvocation = true
Indicates if dynamic method invocation is enabled. The default value is true, but for security
reasons its value should be false. Dynamic method invocation is discussed in Chapter 2.
struts.enable.SlashesInActionNames = false
Indicates if the alternative expression evaluation syntax that requires %{ ... } is allowed.
struts.devMode = false
Indicates if development mode should be enabled. When the value is true, Struts will reload
the application struts.xml file, validation files, and resource bundles on every request, which
means you do not need to reload the application if any of these files changes. In addition, a
value of true will raise the level of debug or ignorable problems to errors. For example, in
development mode a form field with no matching action property will throw an exception. In
production mode, it will be ignored.
struts.ui.theme = xhtml
struts.ui.templateDir = template
The default template type. Other values in addition to ftl (FreeMarker) are vm (Velocity) and
jsp (JSP).
struts.configuration.xml.reload=false
Indicates if Struts should select the namespace to be everything before the last slash.
In addition, there's no easy way to use Struts custom tags to print a request header. With
EL, it's easy. For instance, the following EL expression prints the value of the host header:
${header.host}
For example, to write the expression x+y, you use the following construct:
${x+y}
Reserved Words
The following words are reserved and must not be used as identifiers:
ne le false empty
not It
${header["host"]}
${header.host}
However, to access the accept-language header, you can only use the [] operator
because accept-language is not a legal Java variable name. Using the . operator to access
it will throw an exception.
If an object's property happens to return another object that in turn has a property, you can
use either [] or . to access the property of the second object. For example, the
pageContext implicit object represents the PageContext object of the current JSP. It has
the request property, which represents the HttpServletRequest object. The
HttpServletRequest object has the servletPath property. The following expressions are
equivalent and result in the value of the servletPath property of the HttpServletRequest
object in pageContext:
${pageContext["request"]["servletPath"]}
${pageContext.request["servletPath"]}
${pageContext.request.servletPath}
${pageContext["request"].servletPath}
Accessing JavaBeans
You can use either the . operator or the [] operator to access a bean's property. Here are
the constructs:
${beanName["propertyName"]}
${beanName.propertyName}
For example, to access the property called secret on a bean named myBean, you use the
following expression:
${myBean.secret}
If the property is an object that in turn has a property, you can access the property of the
second object too, again using the . or [] operator. Or, if the property is a Map, a List, or an
array, you can use the same rule explained in the preceding section to access the Map's
values or the members of the List or the element of the array.
EL Implicit Objects
From a JSP, you can use JSP scripts to access JSP implicit objects. However, from a scriptfree JSP page, it is impossible to access these implicit objects. The EL allows you to access
various objects by providing a set of its own implicit objects. The EL implicit objects are
listed in
Table B.1.
Table B.1. The EL Implicit Objects
Object
Description
pageContext
initParam
param
Object
Description
paramValues
header
A Map containing the request headers with the header names as the
keys. The value for each key is the first header of the specified header
name. In other words, if a header has more than one value, only the
first value is returned. To obtain multi-value headers, use the
headerValues object instead.
headerValues
A Map containing all request headers with the header names as the
keys. The value for each key is an array of strings containing all the
values for the specified header name. If the header has only one
value, it returns a one-element array.
cookie
A Map containing all Cookie objects in the current request object. The
cookies' names are the Map's keys, and each key is mapped to a
Cookie object.
applicationScope A Map that contains all attributes in the ServletContext object with
the attribute names as the keys.
sessionScope
requestScope
pageScope
A Map that contains all attributes with the page scope. The attributes'
names are the keys of the Map.
pageContext
The pageContext object represents the current JSP's javax.sefvlet.isp.PageContext
object. It contains all the other JSP implicit objects. These implicit objects are given in
Table B.2.
Table B.2. JSP Implicit Objects
Object
request
javax.servlet.http.HttpServletRequest
response
javax.servlet.http.HttpServletResponse
out
javax.servlet.jsp.JspWriter
session
javax.servlet.http.HttpSession
application javax.servlet.ServletContext
config
javax.servlet.ServletConfig
pageContext javax.servlet.jsp.PageContext
page
javax.servlet.jsp.HttpJspPage
exception
java.lang.Throwable
For example, you can obtain the current ServletRequest object using one of the following
expressions:
${pageContext.request}
${pageContext["request"]
And, the request method can be obtained using one of the following expressions:
${pageContext["request"]["method"]}
${pageContext["request"].method}
${pageContext.request["method"]}
${pageContext.request.method}
Request parameters are accessed more frequently than other implicit objects; therefore,
two implicit objects, param and paramValues, are provided. The param and paramValues
implicit objects are discussed in the sections "param" and "paramValues."
initParam
The initParam implicit object is used to retrieve the value of a context parameter. For
example, to access the context parameter named password, you use the following
expression:
${initParam.password}
or
${initParam["password"]
param
The param implicit object is used to retrieve a request parameter. This object represents a
Map containing all the request parameters. For example, to retrieve the parameter called
userName, use one of the following:
${param.userName}
${param["userName"]}
paramValues
You use the paramValues implicit object to retrieve the values of a request parameter.
This object represents a Map containing all request parameters with the parameters' names
as the keys. The value for each key is an array of strings containing all the values for the
specified parameter name. If the parameter has only one value, it still returns an array
having one element. For example, to obtain the first and second values of the
selectedOptions parameter, you use the following expressions:
${paramValues.selectedOptions[0]}
${paramValues.selectedOptions[1]}
header
The header implicit object represents a Map that contains all request headers. To retrieve
a header value, you use the header name as the key. For example, to retrieve the value of
the accept-language header, use the following expression:
${header["accept-language"]}
If the header name is a valid Java variable name, such as connection, you can also use the
. operator:
${header.connection}
headerValues
The headerValues implicit object represents a Map containing all request headers with the
header names as the keys. Unlike header, however, the Map returned by the
headerValues implicit object returns an array of strings. For example, to obtain the first
value of the accept-language header, use this expression:
${headerValues["accept-language"][0]}
cookie
You use the cookie implicit object to retrieve a cookie. This object represents a Map
containing all cookies in the current HttpServletRequest object. For example, to retrieve
the value of a cookie called jsessionid, use the following expression:
${cookie.jsessionid.value}
because the aim of the EL is to facilitate the authoring of script-free JSPs, these EL
operators are of limited use, except for the conditional operator.
The EL operators are given in the following subsections.
Arithmetic Operators
There are five arithmetic operators:
Addition (+)
Subtraction (-)
Multiplication (*)
Division (/ and div)
Remainder/modulo (% and mod)
The division and remainder operators have two forms, to be consistent with XPath and
ECMAScript.
Note that an EL expression is evaluated from the highest to the lowest precedence, and
then from left to right. The following are the arithmetic operators in the decreasing lower
precedence:
* / div % mod
+This means that *, /, div, %, and mod operators have the same level of precedence, and +
has the same precedence as - , but lower than the first group. Therefore, the expression
${1+2*3}
Relational Operators
The following is the list of relational operators:
For instance, the expression ${3==4} returns false, and ${"b"<"d"} returns true.
Logical Operators
Here is the list of logical operators:
If statement evaluates to true, the output of the expression is A. Otherwise, the output is
B.
For example, you can use the following EL expression to test whether the HttpSession
object contains the attribute called loggedIn. If the attribute is found, the string "You have
logged in" is displayed. Otherwise, "You have not logged in" is displayed.
${(sessionScope.loggedIn==null)? "You have not logged in" :
"You have logged in"}
If X is null or if X is a zero-length string, the expression returns true. It also returns true if
X is an empty Map, an empty array, or an empty collection. Otherwise, it returns false.
This section discusses how to enforce script-free JSPs and how to disable the EL in JSP 2.0.
Note
There can be only one jsp-config element in the deployment descriptor. If you have
specified a jsp-property-group for deactivating the EL, you must write your jspproperty-group for disabling scripting under the same jsp-config element.
The default value of the isELIgnored attribute is false. Using the isELIgnored attribute is
recommended if you want to deactivate EL evaluation in one or a few JSPs.
Second, you can use the jsp-property-group element in the deployment descriptor. The
jsp-property-group element is a subelement of the jsp-config element. You use jspproperty-group to apply certain settings to a set of JSPs in the application.
To use the jsp-property-group element to deactivate the EL evaluation, you must have
two subelements: url-pattern and el-ignored. The url-pattern element specifies the URL
pattern to which the EL deactivation will apply. The el-ignored element must be set to
true.
As an example, here is how you deactivate the EL evaluation in a JSP named noEl.jsp.
<jsp-config>
<jsp-property-group>
<url-pattern>/noEl.jsp</url-pattern>
<el-ignored>true</el-ignored>
</jsp-property-group>
</jsp-config>
You can also deactivate the EL evaluation in all the JSPs in an application by assigning *.jsp
to the url-pattern element, as in the following:
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<el-ignored>true</el-ignored>
</jsp-property-group>
</jsp-config>
The EL evaluation in a JSP will be deactivated if either the isELIgnored attribute of its
page directive is set to true or its URL matches the pattern in the jsp-property-group
element whose el-ignored subelement is set to true. For example, if you set the page
directive's isELIgnored attribute of a JSP to false but its URL matches the pattern of JSPs
whose EL evaluation must be deactivated in the deployment descriptor, the EL evaluation of
that page will be deactivated.
In addition, if you use a deployment descriptor that is compliant to Servlet 2.3 or earlier,
the EL evaluation is already disabled by default, even though you are using a JSP 2.0
container.
Summary
The EL is one of the most important features in JSP 2.0. It can help you write shorter and
more effective JSPs, as well as helping you author script-free pages. In this chapter you
have seen how to use the EL to access JavaBeans and implicit objects. Additionally, you
have seen how to use EL operators. In the last section of this chapter, you learned how to
use the application settings related to the EL in JSP 2.0 and later versions.
Appendix C. Annotations
A new feature in Java 5, annotations are notes in Java programs to instruct the Java
compiler to do something. You can annotate any program elements, including Java
packages, classes, constructors, fields, methods, parameters, and local variables. Java
annotations are defined in JSR 175 (http://www.jcp.org/en/jsr/detail?id=175).
Java 5 provided three standard annotations and four standard meta-annotations. Java 6
added dozens of others.
This appendix is for you if you are new to annotations. It tells you everything you need to
know about annotations and annotation types. It starts with an overview of annotations,
and then teaches you how to use the standard annotations in Java 5 and Java 6. It
concludes with a discussion of custom annotations.
An Overview of Annotations
Annotations are notes for the Java compiler. When you annotate a program element in a
source file, you add notes to the Java program elements in that source file. You can
annotate Java packages, types (classes, interfaces, enumerated types), constructors,
methods, fields, parameters, and local variables. For example, you can annotate a Java
class so that any warnings that the javac program would otherwise issue be suppressed.
Or, you can annotate a method that you want to override to get the compiler to verify that
you are really overriding the method, not overloading it. Additionally, you can annotate a
Java class with the name of the developer. In a large project, annotating every Java class
can be useful for the project manager or architect to measure the productivity of the
developers. For example, if all classes are annotated this way, it is easy to find out who is
the most or the least productive programmer.
The Java compiler can be instructed to interpret annotations and discard them (so those
annotations only live in source files) or include them in resulting Java classes. Those that
are included in Java classes may be ignored by the Java virtual machine, or they may be
loaded into the virtual machine. The latter type is called runtime-visible and you can use
reflection to inquire about them.
Retention, and Target. These four annotation types are used to annotate annotations, and
you will learn about them in the section "Custom
chapter. Java 6 adds many annotations of its own.
Annotation Syntax
In your code, you use an annotation differently from using an ordinary interface. You
declare an annotation type by using this syntax.
@AnnotationType
or
@AnnotationType(elementValuePairs)
The first syntax is for marker annotation types and the second for single-value and multivalue types. It is legal to put white spaces between the at sign (@) and annotation type, but
this is not recommended.
For example, here is how you use the marker annotation type Deprecated:
@Deprecated
And, this is how you use the second element for multi-value annotation type Author:
@Author(firstName="Ted",lastName="Diong")
There is an exception to this rule. If an annotation type has a single key/value pair and the
name of the key is value, then you can omit the key from the bracket. Therefore, if the
fictitious annotation type Stage has a single key named value, you can write
@Stage(value=1)
or
@Stage(1)
In addition, any implementation of Annotation will override the equals, hashCode, and
toString methods from the java.lang.Object class. Here are their default
implementations.
public boolean equals(Object object)
Returns true if object is an instance of the same annotation type as this one and all
members of object are equal to the corresponding members of this annotation.
public int hashCode()
Returns the hash code of this annotation, which is the sum of the hash codes of its
members
public String toString()
Returns a string representation of this annotation, which typically lists all the key/value
pairs of this annotation.
You will use this class when learning custom annotation types later in this chapter.
Standard Annotations
Java 5 comes with three built-in annotations, all of which are in the java.lang package:
Override, Deprecated, and SuppressWarnings. They are discussed in this section.
Override
Override is a marker annotation type that can be applied to a method to indicate to the
compiler that the method overrides a method in a superclass. This annotation type guards
the programmer against making a mistake when overriding a method.
For example, consider this class Parent:
class Parent {
public float calculate(float a, float b) {
return a * b;
}
}
Suppose, you want to extend Parent and override its calculate method. Here is a subclass
of Parent:
public class Child extends Parent {
public int calculate(int a, int b) {
return (a + 1) * b;
}
}
The Child class compiles. However, the calculate method in Child does not override the
method in Parent because it has a different signature, namely it returns and accepts ints
instead of floats. In this example, a programming mistake like this is easy to spot because
you can see both the Parent and Child classes. However, you are not always this lucky.
Sometimes the parent class is buried somewhere in another package. This seemingly trivial
error could be fatal because when a client class calls the calculate method on an Child
object and passes two floats, the method in the Parent class will be invoked and a wrong
result will be returned.
Using the Override annotation type will prevent this kind of mistake. Whenever you want
to override a method, declare the Override annotation type before the method:
public class Child extends Parent {
@Override
public int calculate(int a, int b) {
return (a + 1) * b;
}
}
This time, the compiler will generate a compile error and you'll be notified that the
calculate method in Child is not overriding the method in the parent class.
It is clear that @Override is useful to make sure programmers override a method when
they intend to override it, and not overload it.
Deprecated
Deprecated is a marker annotation type that can be applied to a method or a type
(class/interface) to indicate that the method or type is deprecated. A deprecated method or
type is marked so by the programmer to warn the users of his code that they should not
use or override the method or use or extend the type. The reason why a method or a type
is marked deprecated is usually because there is a better method or type and the method or
type is retained in the current software version for backward compatibility.
C.2 shows the DeprecatedTest2 class that uses the serve method in
On top of that, you can use @Deprecated to mark a class or an interface, as shown in
Listing C.3.
Listing C.3. Marking a class deprecated
@Deprecated
public class DeprecatedTest3 {
public void serve() {
}
}
SuppressWarnings
SuppressWarnings is used, as you must have guessed, to suppress compiler warnings.
You can apply @SuppressWarnings to types, constructors, methods, fields, parameters,
and local variables.
You use it by passing a String array that contains warnings that need to be suppressed. Its
syntax is as follows.
where string-1 to string-n indicate the set of warnings to be suppressed. Duplicate and
unrecognized warnings will be ignored.
The following are valid parameters to @SuppressWarnings:
unchecked. Give more detail for unchecked conversion warnings that are mandated
by the Java Language Specification.
path. Warn about nonexistent path (classpath, sourcepath, etc) directories.
serial. Warn about missing serialVersionUID definitions on serializable classes.
finally. Warn about finally clauses that cannot complete normally.
fallthrough. Check switch blocks for fall-through cases, namely cases, other than
the last case in the block, whose code does not include a break statement, allowing
code execution to "fall through" from that case to the next case. As an example, the
code following the case 2 label in this switch block does not contain a break
statement:
switch (i) {
case 1:
System.out.println("1");
break;
case 2:
System.out.println("2");
// falling through
case 3:
System.out.println("3");
}
Standard Meta-Annotations
Meta annotations are annotations that are applied to annotations. There are four metaannotation types that come standard with Java 5 that are used to annotate annotations;
they are Documented, Inherited, Retention, and Target. All the four are part of the
java.lang.annotation package. This section discusses these annotation types.
Documented
Documented is a marker annotation type used to annotate the declaration of an
annotation type so that instances of the annotation type will be included in the
documentation generated using Javadoc or similar tools.
For example, the Override annotation type is not annotated using Documented. As a
result, if you use Javadoc to generate a class whose method is annotated @Override, you
will not see any trace of @Override in the resulting document.
For instance, Listing C.5 shows the OverrideTest2 class that uses @Override to
annotate the toString method.
Inherited
You use Inherited to annotate an annotation type so that any instance of the annotation
type will be inherited. If you annotate a class using an inherited annotation type, the
annotation will be inherited by any subclass of the annotated class. If the user queries the
annotation type on a class declaration, and the class declaration has no annotation of this
type, then the class's parent class will automatically be queried for the annotation type. This
process will be repeated until an annotation of this type is found or the root class is
reached.
Retention
@Retention indicates how long annotations whose annotated types are annotated
@Retention are to be retained. The value of @Retention can be one of the members of the
java.lang.annotation.RetentionPolicy enum:
Target
Target indicates which program element(s) can be annotated using instances of the
annotated annotation type. The value of Target is one of the members of the
java.lang.annotation.ElementType enum:
As an example, the Override annotation type declaration is annotated the following Target
annotation, making Override can only be applied to method declarations.
@Target(value=METHOD)
You can have multiple values in the Target annotation. For example, this is from the
declaration of SuppressWarnings:
@Target(value={TYPE,FIELD, METHOD, PARAMETER,CONSTRUCTOR, LOCAL_VARIABLE})
Returns this element's annotation for the specified annotation type, if present. Otherwise,
returns null.
public java.lang.annotation.Annotation[] getAnnotations()
Indicates whether an annotation for the specified type is present on this class
The com.brainysoftware.jdk5.app18.custom package includes three test classes,
Test1, Test2, and Test3, that are annotated Author.
employs reflection to query the test classes.