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. Convenience. It is very convenient to put everything in JSPs. This is okay if your application is a very simple application consisting of only one or two pages and will never grow in complexity. Shortsightedness. Writing code and HTML in JSPs seems to be a more rapid way of development. However, in the long run, there is a hefty price to pay for building your application this way. Maintenance and code readability are two main problems. Lack of knowledge.
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. more rapid to build easier to test easier to maintain easier to extend
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: page navigation management user input validation consistent layout extensibility internationalization and localization support for AJAX
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 8, "Input Validation" discusses input validation in detail. Chapter 9, "Message Handling" covers message handling, which is also one of the
most important tasks in application development. Today it is often a requirement that applications be able to display internationalized and localized messages. Struts has been designed with internationalization and localization from the outset.
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. build AJAX components.
Chapter
27, "AJAX" shows Struts' support for AJAX and explains how to use AJAX custom tags to Appendix A, "Struts Configuration" is a guide to writing Struts configuration files. Appendix B, "The JSP Expression Language" introduces the language that may help
when OGNL and the Struts custom tags do not offer the best solution.
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
To get the application to save a product, the URI would be: http://domain/appName/Product_save.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
The application is capable of performing these two actions: 1. Display the "Add Product" form. This action sends the entry form in Figure 1.2 to the browser. The URI to invoke this action must contain the string Product_input.action. 2. Save the product and returns the confirmation page in Figure 1.3. The URI to invoke this action must contain the string Product_save.action. The application consists of the following components: 1. A Product class that is the template for the action objects. An instance of this class contains product information. 2. A ControllerServlet class, which is the controller of this Model 2 application. 3. Two JSPs (ProductForm.jsp and ProductDetails.jsp) as the views. 4. A CSS file that defines the styles of the views. This is a static resource. The directory structure of this application is shown 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.
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> The deployment descriptor defines the app01a.ControllerServlet servlet and names it Controller. The servlet can be invoked by any URL pattern that ends with *.action. Requests for static resources, such as images and CSS files, bypass the controller and are handled directly by the container. In this application, as is the case for most Model 2 applications, you need to prevent the JSPs from being accessed directly from the browser. There are a number of ways to achieve this, including: 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
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).
// 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); } } else if (uri.indexOf("/css/") != -1 && req.getHeader("referer") == null) { res.sendError(HttpServletResponse.SC_FORBIDDEN); } else { // other static resources, let it through filterChain.doFilter(request, response); } } } The doFilter method performs what the process method in app01a did, namely 1. Instantiate the relevant action class, if any. 2. If an action object exists, populate the action's properties with request parameters. 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). Note that since the filter captures all requests, including those for static requests, we can easily add extra processing for CSS files. By checking the referer header for requests for CSS files, a user will see an error message if he or she types in the URL to the CSS file: http://localhost:8080/app01b/css/main.css The deployment descriptor is given in
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: Struts provides a filter dispatcher, saving you writing one. Struts employs an XML-based configuration file to match URIs with actions. Since XML documents are text files, many changes can be made to the application without recompilation. Struts instantiates the action class and populates action properties with user inputs. If you don't specify an action class, a default action class will be instantiated. Struts validates user input and redirects user back to the input form if validation failed. Input validation is optional and can be done programmatically or declaratively. On top of that, Struts provides built-in validators for most of the tasks you may encounter when building a web application. Struts invokes the action method and you can change the method for an action through the configuration file. Struts examines the action result and executes the result. The most common result type, Dispatcher, forwards control to a JSP. However, Struts comes with various result types that allow you to do things differently, such as generate a PDF, redirect to an external resource, send an error message, etc.
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
Chapter 20, "Velocity" explains the Velocity templating language and Chapter 20, "FreeMarker" discusses FreeMarker.
can also be used. 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.
Description Converts similar parameters that may have different names between requests. When used with the Chain result type, this interceptor makes the previous action's properties available to the current action. See Chapter 3, "Actions and Results" for details. Handles check boxes in a form so that unchecked check boxes can be detected. For more information, see the discussion of the checkbox tag in Chapter 5, "Form Tags." Adds a cookie to the current action. Adds conversion errors to the action's field errors. See Chapter 7, "Type conversion" for more details. Creates an HttpSession object if one does not yet exist for the current user. Supports debugging. See Chapter 16, "Debugging and Profiling." Executes a long-processing action in the background and sends the user to an intermediate waiting page. This interceptor is explained in Chapter 17, "Progress Meters." Maps exceptions to a result. See Chapter 3, "Actions and Results" for details. Supports file upload. See Chapter 12, "File Upload" for details. Supports internationalization and localization. See Chapter
Chaining (chain)
Checkbox (checkbox)
Cookie (cookie) Conversion Error (conversionError) Create Session (createSession) Debugging (debugging)
Exception (exception)
I18n (i18n)
Interceptor
Outputs the action name. Stores and retrieves action messages or action errors or field errors for action objects whose classes implement ValidationAware. Supports for the model driven pattern for action classes that implement ModelDriven. See Chapter 10, "The Model Driven Pattern" for details. Similar to the Model Driven interceptor but works for classes that implement ScopedModelDriven. Populates the action's properties with the request parameters. Supports action classes that implement the Preparable interface. See Chapter 10, "The Model Driven Pattern" for more details. Provides a mechanism for storing action state in the session or application scope. Provides access to the Maps representing HttpServletRequest and HttpServletResponse. Maps static properties to action properties.
Prepare (prepare)
Scope (scope)
Description Outputs the time needed to execute an action. Verifies that a valid token is present. See Chapter 15, "Preventing Double Submits" for details. Verifies that a valid token is present. See Chapter 15, "Preventing Double Submits" for details. Supports input validation. See Chapter 8, "Input Validation" for details. Calls the validate method in the action class. Removes parameters from the list of those available to the action. Supports action profiling. See Chapter 16, "Debugging and Profiling" for details.
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
Appendix
A, "Struts Configuration."
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:
<?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>
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>
is the same as this: <result> <param name="location">/test.jsp</param> </result> You'll learn more about the param element later in this section.
<package name="main" extends="struts-default"> <interceptors> <interceptor name="validation" class="..."/> <interceptor name="logger" class="..."/> </interceptors> </package>
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="..."/>
<action name="Product_delete" class="..."> <interceptor-ref name="alias"/> <interceptor-ref name="i18n"/> <interceptor-ref name="validation"/> <interceptor-ref name="logger"/> <result>/jsp/main.jsp</result> </action> <action name="Product_save" class="..."> <interceptor-ref name="alias"/> <interceptor-ref name="i18n"/> <interceptor-ref name="validation"/> <interceptor-ref name="logger"/> <result name="input">/jsp/Product.jsp</result> <result>/jsp/ProductDetails.jsp</result> </action> </package>
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>
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>
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
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"; } }
Figure 1.2 in your browser. Enter values in the fields and submit the form. Your browser will display a confirmation message similar to Figure 1.3.
You will see something like Congratulations. You've just seen Struts in 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.
Returns the ServletContext object. You can obtain the HttpSession object by calling one of the getSession methods on the HttpServletRequest object. The HttpSession object will be created automatically if you use the basicStack or defaultStack interceptor stack. Note You should not call the methods on the ServletActionContext from an action class's constructor because at this stage the underlying ActionContext object has not been passed to it. Calling ServletActionContext.getServletContext from an action's constructor will return null. As an example, Listing 3.2 shows an action method that retrieves the HttpServletRequest and HttpSession objects through ServletActionContext.
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)
Implementing ServletRequestAware allows you access to the HttpServletRequest object from within your action class. When an action is invoked, Struts checks to see if the action class implements this interface and, if it does, calls its setServletRequest method, passing the current HttpServletRequest object. Struts does this before it populates the action properties and before it executes the action method. In the implementation of the setServletRequest method, you need to assign the passed HttpServletRequest object to a class variable: private HttpServletRequest servletRequest; public void setServletRequest(HttpServletRequest servletRequest) { this.servletRequest = 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)
In an implementing setSession method you assign the Map to a class variable: private Map session; void setSession(Map map) { this.session = 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(); }
SessionMap.invalidate is better than HttpSession.invalidate because the former does not throw an exception if the underlying HttpSession object is null. Note Unfortunately, the SessionMap class does not provide access to the session identifier. In the rare cases where you need the identifier, use the ServletActionContext to obtain the HttpSession object. Note For this interface to work, the Servlet Config interceptor must be enabled. Since this interceptor is part of the default stack, by default it is already on.
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
<action ... > <result name="success"> ... </result> <result name="input"> ... </result> </action>
A result element can have these attributes: 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.
Description Used for action chaining The default result type, used for JSP forwarding Used for FreeMarker integration
Result Type HttpHeader (httpheader) Redirect (redirect) Redirect Action (redirectaction) Stream (stream) Velocity (velocity) XSLT (xslt) PlainText (plaintext)
Description Used to send HTTP headers back to the browser Used to redirect to another URL Used to redirect to another action
Used to stream an InputStream to the browser Used for Velocity integration Used for XML/XSLT integration Used to send plain text, normally to show a JSP's source.
Table 3.1, many third party developers deploy plug-ins that encapsulate new result types. You too can write your own and Chapter 19, "Custom
In addition to the ones in Result Types" teaches you how. Each of the result types is explained below.
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>
action1 in package1 is chained to action2, which in turn is chained to action3 in a different package. Chaining to an action in a different package is allowed as long as you specify the namespace parameter of the target action. If action-x is chained to action-y, action-x will be pushed to the Value Stack, followed by action-y, making action-y the top object in the Object Stack. As a result, both actions can be accessed from the view. If action-x and action-y both have a property that shares the same name, you can access the property in action-y (the top object) using this OGNL expression: [0].propertyName or propertyName
You can access the property in action-x using this expression: [1].propertyName Use action chaining with caution, though. Generally action chaining is not recommended as it may turn your actions into spaghetti code. If action1 needs to be forwarded to action2, for example, you need to ask yourself if there's code in action2 that needs to be pushed into a method in a utility class that can be called from both action1 and action2.
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 location. Specifies the redirection target. parse. Indicates whether or not the value of location should be parsed for OGNL expressions. The default value for parse is true.
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>
And this to an internal resource: <action name="..." class="..."> <result name="success" type="redirect"> /jsp/Product.jsp </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. exception, that represents the Exception object thrown exceptionStack, that contains the value from the stack trace.
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>
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>
can be replaced by this one: <package name="wildcardMappingTest" namespace="/wild" extends="struts-default"> <action name="*_add" class="app03a.{1}" method="add"> <result>/jsp/{1}.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 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
Of course, this can also be written as [0] ["message"] or [0] ['message']. To read the time property of the second object in the stack, you can use [1].time or [1]["time"] or [1]['time']. For example, the property tag, one of the many tags you'll learn in Chapter 5, "Form Tags," is used to print a value. Using the property tag to print the time property of the first stack object, you can write any of the following: <s:property value="[0].time"/> <s:property value="[0]['time']"/> <s:property value='[0]["time"]'/>
An important characteristic of the OGNL implementation in Struts is that if the specified property is not found in the specified object, search will continue to the objects next to the specified object. For example, if the top object does not have a name property, the following expression will search the subsequent objects in the Object Stack until the property is found or until there's no more object in the stack: [0].name
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']
As an example, this expression accesses the static field DECEMBER in java.util.Calendar: @java.util.Calendar@DECEMBER
To call the static method now in the app04.Util class (shown in @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"}
This returns the first element in the string array. {"Alaska", "California", "Washington"}[0]
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()
You can use isEmpty or isEmpty to find out if a Map is empty. cities.isEmpty cities.isEmpty()
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"]
You can create a Map by using this syntax: #{ key-1:value-1, key-2:value-2, ... key-n:value-n }
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
Description
The CSS class for the rendered element. The CSS style for the rendered element. Specifies the HTML title attribute. Specifies the HTML disabled attribute. Specifies the label for a form element in the xhtml and ajax theme. Specifies the label position in the xhtml and ajax theme. Allowed values are top and left (default). The name of the property this input field represents. It is a shortcut for the name and label attributes Specifies the required label position of a form element in the xhtml and ajax theme. Possible values are left and right (default). Specifies the HTML name attribute that in an input element
labelPosition*
String
key
String
requiredposition* String
name
String
Name
Data Type
Description
maps to an action property. required* boolean In the xhtml theme this attribute indicates whether or not an asterisk (*) should be added to the label. String String Specifies the HTML tabindex attribute. Specifies the value of a form element.
tabIndex value
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"
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
Table 5.1, there are also attributes related to templates, JavaScript, and tooltips. These attributes are given in Table 5.2, Table 5.3, and Table 5.4, respectively.
In addition to the common attributes in
Name
Data Type
Description The directory in which the template resides The theme name The template name
Description Javascript onclick attribute Javascript ondblclick attribute Javascript onmousedown attribute Javascript onmouseup attribute Javascript onmouseover attribute Javascript onmouseout attribute Javascript onfocus attribute Javascript onblur attribute Javascript onkeypress attribute Javascript onkeyup attribute
onmouseover String onmouseout onfocus onblur onkeypress onkeyup String String String String String
Description Javascript onkeydown attribute Javascript onselect attribute Javascript onchange attribute
Name
Description
tooltip
tooltipIconPath String The path to a tooltip icon. The default value is /struts/static/tooltip/tooltip.gif tooltipDelay 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
Default Value
Description
acceptcharset String
Name
Data Type
Default Value
Description
accepted for this form. action enctype method namespace String String String post current namespace String current action The action to submit this form to The form enctype attribute The form method The namespace of the action
onsubmit
Javascript onsubmit attribute Template to use for opening the rendered form The portlet mode to display after the form submit The form target attribute Indicates if client-side validation should be performed in xhtml/ajax themes The window state to display after the form submit
openTemplate String
portletMode String
target validate
windowState String
By default a form tag is rendered as an HTML form laid out in a table: <form id="..." name="..." method="POST" action="..." onsubmit="return true;"> <table class="wwFormTable"> </table> </form>
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>
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
Default Value
Description
maxlength integer
The maximum number of characters the rendered element can accept Indicates if the input is read-only The size attribute
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
Default Value
Description
The HTML action attribute The HTML align attribute The method attribute input The type of the rendered element. The value can be input, button, or image.
For example, the following is a submit button whose value is "Login": <s:submit value="Login"/>
Table 5.8.
Name
Data Type
Default Value
Description
The HTML action attribute The HTML align attribute. The method attribute input The type of the rendered element. The value can be input or button.
Table 5.9.
Table 5.10.
Description The HTML cols attribute. Indicates if the textarea is read only. The HTML rows attribute. The HTML wrap attribute
Listing 5.3 has a property that is mapped to a textarea tag on the TextArea.jsp page in Listing 5.4.
For example, the TextAreaTestAction class in
</body> </html> To test this example, direct your browser to this URL: http://localhost:8080/app05a/TextArea.action
Name
fieldValue String
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
But it does not. And here lies the problem: There's no way for the server to know if a checked checkbox has been unchecked. Consider an object in the HttpSession that has a boolean property linked with a checkbox. A value of "on" (when the check box is checked) would invoke the property setter and set the value to true. An unchecked checkbox would not invoke the property setter and, as a result, if the previous value was true, it would remain true. The checkbox tag overcomes this limitation by creating an accompanying hidden value. For example, the following checkbox tag <s:checkbox label="inStock" key="inStock"/> is rendered as <input type="checkbox" name="inStock" value="true" id="ActionName_inStock"/> <input type="hidden" name="__checkbox_inStock" value="true"/>
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."
Listing 5.5 has boolean properties that are mapped to three checkbox tags on the CheckBox.jsp page in Listing 5.6.
As an example, the CheckBoxTestAction class in
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
<input type="radio" name="city" value="l"/>Atlanta <input type="radio" name="city" value="2"/>Chicago <input type="radio" name="city" value="3"/>Detroit
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 elements also need options. This select element (shown in same options as the radio set.
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'}"/>
This select tag will be rendered as <select> <option value="Atlanta">Atlanta</option> <option value="Chicago">Chicago</option> <option value="Detroit">Detroit</option> </select>
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'}
For example, the following select tag: <s:select list="#{'1':'Atlanta', '2':'Chicago', '3':'Detroit'}"/>
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"/>
Or, if cities is an application attribute, you use this code. <s:select list="#application.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
Default Value
Description
list* listKey
An iterable source to populate from The property of the object in the list that will supply the option values. The property of the object in the list that will supply the option labels.
listValue String
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
Indicates whether or not to insert an empty option after the header. The key for the first item in the list. The value for the first item in the list. An iterable source to populate from The property of the object in the list that will supply the option values. The property of the object in the list that will supply the option labels. Indicates whether or not multiple selection is allowed The number of options to show
headerKey
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
Map in the ServletContext object. You normally put a selection of options in a ServletContext if you intend to use the options from many different points in your application. You use the ServletContextListener in
Listing 5.12.
"Vancouver");
cities[1] = new City(5, "Toronto"); cities[2] = new City(6, "Montreal"); } else if (country == 3) { cities = new City[2]; cities[0] = new City(7, "Mexico City"); cities[1] = new City(8, "Tijuana"); } else { cities = new City[0]; } return cities; } public int getCity() { return city; } public void setCity(int city){ this.city = city; } public int getCountry() { return country; } public void setCountry(int country) { this.country = country; } } class City { private int id; private String name; public City(int id, String name) { this.id = id; this.name = name; } public int getId() { return id; } public void setld(int id) { this.id = id; } public String getName () { return name; } public void setName(String name) { this.name = name; } } The JSP used for this example is given in
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
Default Value
Description
list* listKey
An iterable source to populate from The property of the object in the list that will supply the option values. The property of the object in the list that will supply the option labels.
listValue String
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
Default Value
Description
list* listKey
An iterable source to populate from The property of the object in the list that will supply the option values. The property of the object in the list that will supply the option labels.
listValue String
A checkboxlist tag is mapped to an array of strings or an array of primitives. If no checkbox on the list is selected, the corresponding property will be assigned an empty array, not null. The following example shows how you can use the checkboxlist tag. The property underlying the checkboxlist is an array of integers. The options come from a List of Interest objects.
Listing 5.16 shows the CheckBoxListTestAction class, the action class for this
example, and the Interest class.
public int[] getInterests() { return interests; } public void setInterests(int[] interests) { this.interests = interests; } public List<Interest> getInterestOptions() { return interestOptions; } } class Interest { private int id; private String description; public Interest(int id, String description) { this.id = id; this.description = description; } // getters and setters not shown }
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
Indicates if an empty option should be inserted. The key for headerValue, should be -1. Text that will be added as a select option but is not intended to be selected An iterable source to populate from The property of the object in the list that will supply
headerValue String
list* listKey
String String
Name
Data Type
Description
listValue
String
The property of the object in the list that will supply the option labels. The HTML maxlength attribute. Indicates if the rendered element is read only. The size of the rendered element.
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.
Listing 5.18 is an action class that provides a property (make) linked to the combobox tag on the JSP in Listing 5.19.
As an example, the ComboBoxTestAction class in
<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
Indicates whether the move down button will be displayed. Indicates whether the move up button will be displayed. Indicates whether the select all button will be displayed. Indicates whether an empty (--) option should be inserted after the header option. The key for the first item on the list. The value for the first item on the list. Iterable source to populate from. The property of the object in the list that will supply the option values. The property of the object in the list that will supply the option labels.
allowMoveUp
boolean true
allowSelectAll
boolean true
emptyOption
boolean false
listValue
String
V ^
Text to display on the move down button. Text to display on the move up button. Indicates if a multiple select should be created. Text to display on the select all button.
Name
Default Value
Description
size
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.
Figure 5.13.
Name
Default Value
Description
addAllToLeftLabel addAllToLeftOnclick
The label for the Add All To Left button The Javascript function to invoke when the Add All To Left button is clicked. The label for the Add All To Right button The Javascript function to invoke when the Add All To Right button is clicked. The label for the Add To Left button. The Javascript function to invoke when the Add To Left button is clicked. The label for the Add To Right button. The Javascript function to invoke when the
addAllToRightLabel addAllToRightOnclick
String String
addToLeftLabel addToLeftOnclick
String String
addToRightLabel addToRightOnclick
String String
Name
Data Type
Default Value
Description
Add To Right button is clicked. allowAddAllToLeft boolean true Indicates whether or not to enable the Add All To Left button. Indicates whether or not to enable the Add All To Right button. Indicates whether or not to enable the Add To Left button. Indicates whether or not to enable the Add To Right button. Indicates whether or not to enable the Select All button. Indicates whether or not to enable moving options up and down on the left select element. Indicates whether or not to enable moving options up and down on the right select element. The CSS class for the buttons. The CSS style for the buttons. The CSS class for the second list. The CSS style for the second list.
allowAddAllToRight
boolean true
allowAddToLeft
boolean true
allowAddToRight
boolean true
allowSelectAll
boolean true
allowUpDownOnLeft
boolean true
Name
Data Type
Default Value
Description
doubledDisabled
boolean false
Indicates if the second list should be disabled. Indicates if an empty option should be inserted to the second list. The header key for the second list. The header value for the second list. The identifier for the second list. The iterable source to populate the second list. The property of the object in the second list that will supply the option values. The property of the object in the second list that will supply the option labels. Indicates if the second list should allow multiple selection. The name for the second component. The size attribute for the second list. Indicates if an empty option should be inserted to the first list. The name of the form containing this component.
doubleEmptyOption
boolean false
doubleListKey
String
doubleListValue
String
doubleMultiple
boolean false
formName
String
Name
Default Value
Description
The header key for the first list. The header value for the first list. The label for the left Down button. The title for the left selection. The label for the left Up button. The iterable source to populate the first list.. The property of the object in the first list that will supply the option values. The property of the object in the first list that will supply the option labels. Indicates if multiple selection is allowed for the first select element. The label for the right Down button. The title for the selection on the right. The label for the right Up button. The label for the Select All button. The Javascript function to invoke when the Select All button is clicked. The number of elements to show in the
listKey
String
listValue
String
multiple
boolean
size
integer
Name
Data Type
Description
upDownOnLeftOnclick String
The Javascript function that will be invoked when the left Up/Down button is clicked. The Javascript function that will be invoked when the right Up/Down button is clicked.
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
Default Value
Description
The CSS class for the second select element. The CSS style for the second select element. Indicates if the second select element should be disabled.
booelan false
Name
Data Type
Description
doubleEmptyOption
Indicates whether an empty option should be inserted to the second select element. The header key for the second select element. The header value for the second select element. The identifier for the second select element. The iterable object for populating the second select element. The property of the object in the second list that will supply the option labels. The property of the object in the second list that will supply the option labels. Indicates whether the second select element should allow multiple selection. The name for the second selection. The number of options to be shown in the second select element. The value for the second select element. Indicates whether or not an empty options should be inserted to the first select element. The name of the containing form.
doubleHeaderKey
String
doubleHeaderValue String
doubleId doubleList*
String String
doubleListKey
String
doubleListValue
String
doubleMultiple
boolean false
doubleName* doubleSize
String interger
doubleValue emptyOption
formName
String
Name
Default Value
Description
The header key for the first select element. The header value for the first select element. The iterable object that will populate the first select element.
listKey
String
The property of the object in the first list that will supply the option values. The property of the object in the first list that will supply the option labels. Indicates whether or not the first element should allow multiple selection. The number of options to be displayed in the first element.
listValue
String
multiple
boolean False
size
Integer
As an example, the DoubleSelectTestAction class in Listing 5.24 is an action class with two properties linked to the doubleselect tag in the DoubleSelect.jsp page in
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.
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 The following are the control tags: 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 The default value if value is null Whether HTML special characters are escaped
For instance, this property tag prints the value of the customerId action property: <s:property value="customerId"/>
The following prints the value of the session attribute userName. <s:property value="#session.userName"/>
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
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 executeResult
Default
Description Indicates whether the action result should be executed/rendered. Indicates whether the writer should be flushed at the end of the action
flush
boolean true
Name
Type
Default
Whether request parameters are to be included when the action is invoked. The name of the action to be invoked, without the .action suffix.
name*
String
namespace
String
the namespace The namespace of the action to be from where the tag invoked. is used The name to be used to reference the action added to the context map.
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 The name of the parameter to be passed to the containing tag. The value of the parameter to be passed to the containing tag.
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"/>
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 The fully qualified class name of the JavaBean to be created. The name used to reference the value pushed into the Value Stack's context map.
Listing 6.3 provides methods to convert Celcius to Fahrenheit and vice versa. The Bean.jsp page in Listing 6.4 uses the
In the following example, the DegreeConverter class in bean tag to instantiate the class.
Table 6.6.
Name
Description
Whether to apply nice formatting. The name used to reference the value pushed to the value stack.
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
Table 6.8.
Description The key of the attribute to be created The object to be referenced by the key.
scope String default The scope of the target variable. The value can be application, session, request, page, or default.
The following example, based on the SetTestAction class in benefit of using set.
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.
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.
Description The action that the created URL will target. The anchor for the created URL Whether to encode parameters. Indicates whether to escape the ampersand character (&) Indicates whether the actual context should be included One of these values: one, get, all.
includeParams String
get
Description The method of the action. The target namespace. The resulting portlet mode. Indicates if the created URL should be a portlet render or an action URL. The scheme ??? The target value to use, if not using action ??? When used in a portlet environment, specifies the portlet window state.
portletlUrlType 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.
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.
Type
Default
status org.apache.struts2.views.jsp. IteratorStatus var String The variable to reference the current element of the iterable object.
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.
Type
Description
integer The zero-based index of each iteration integer The current iteration or index + 1. boolean The value is true if the current element is the first element in the iterable object. boolean The value is true if the current element is the last element in the iterable object. boolean The value is true if count is an even number boolean The value is true if count is an odd number This property takes an integer and returns the modulus of count.
last
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.
interestOptions.add(new Interest(1, "Automotive")); interestOptions.add(new Interest(2, "Games")); interestOptions.add(new Interest(3, "Sports")); } public int[] getInterests() { return interests; } public void setInterests(int[] interests) { this.interests = interests; } public List<Interest> getInterestOptions() { return interestOptions; } } class Interest { private int id; private String description; public Interest(int id, String description) { this.id = id; this.description = description; } // getters and setters not shown }
First 4 prime number <ul> <s:iterator value="{2, 3, 5, 7}"> <li><s:property/></li> </s:iterator> </ul> <s:set name="car" value="{ 'Chrysler', 'Ford', 'Kia'}"/> Cars: <s:iterator value="#car" status="status"> <s:property/><s:if test="!#status.last">,</s:if> </s:iterator> <p> <h3>Interest options</h3> <table> <tr> <th>Id</th> <th>Description</th> </tr> <s:iterator value="interestOptions" status="status"> <s:if test="#status.odd"> <tr class="oddRow"> </s:if> <s:if test="#status.even"> <tr class="evenRow"> </s:if> <td><s:property value="id"/></td> <td><s:property value="description"/></td> </tr> </s:iterator> </table> </div> </body> </html> To test this example, direct your browser to this URL: http://localhost:8080/app06a/Iterator.action
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>
This is the same as writing <table> <tr> <td><s:textfield <td><s:textfield </tr> <tr> <td><s:textfield <td><s:textfield </tr> <tr> <td><s:textfield <td><s:textfield </tr> </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>
Table 6.14.
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
Table 6.15.
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 The converter to convert the String entry parsed from val into an object. The maximum number of elements in the iterator.
converter Converter
count
Integer
Name
Type
Default
Description The separator for separating the val into entries of the iterator. The source to be parsed into an iterator. The variable that references the resulting iterator.
separator* String
val* var
String 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 The comparator that will be used in the sorting. The iterable source to sort. The variable that will be created to reference the new iterator.
comparator* java.util.Comparator
source var
String 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.
Listing 6.20 provides a property of type Comparator that is used by the sort tag in the Sort.jsp page (See Listing 6.21.)
For example, the SortTestAction class in
</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 The number of entries in the resulting iterator. An implementation of the SubsetIteratorFilter.Decider interface that determines if an entry is to be included in the resulting subset. The source iterator to subset. The starting index of the source iterator to be included in the subset. The variable to be created to reference to the subset.
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.
// getters and setters not shown } Note We could use java.util.Currency for amount, but using a double serves as a good example for the type conversions in this example. There are two actions in this example, Transaction1 and Transaction2. The following are the declarations for the actions in the struts.xml file.
<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);
The parameters are as follows. 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
value. The value to be converted. toType. The type to which the value is to be converted.
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);
To obtain the ServletContext, HttpServletRequest, and the HttpServletResponse objects, use the static finals defined in the org.apache.struts2.StrutsStatics interface: context.get(StrutsStatics.SERVLET_CONTEXT); context.get(StrutsStatics.HTTP_REQUEST); context.get(StrutsStatics.HTTP_RESPONSE);
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; }
Implementing TypeConverter is not as easy as extending the DefaultTypeConverter class, a default implementation of TypeConverter. DefaultTypeConverter, shown in
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.
NumberFormat formatter = new DecimalFormat("#,##0.00"); return formatter.format((Double) value); } else if (toType == Double.class || toType == Double.TYPE) { try { String[] s = (String[]) value; String doubleValue = s[0]; // remove commas, // we could use a one-line regular expression, // String doubleValue = s[0].replaceAll("[,]", ""); // but regular expressions are comparatively // much slower return Double.parseDouble( replaceString(doubleValue, ',', "")); } catch (NumberFormatException e) { System.out.println("Error:" + e); throw new TypeConversionException("Wrong"); } } return null; } public static String replaceString(String s, char c, String with) { if (s == null) { return null; } int length = s.length(); StringBuilder sb = new StringBuilder(s.length() * 2); for (int i = 0; i < length; i++) { char c2 = s.charAt(i); if (c2 == c) { sb.append(with); } else { sb.append(c2); } } return sb.toString(); } } The date converter is encapsulated in the MyDateConverter class in Listing 7.7. Only conversions from String to Date are catered for. Date to String is not important since you can use the date tag to format and print a Date property.
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.
The implementation of convertValue in StrutsTypeConverter calls either convertFromString or convertToString, depending on which direction type conversion must be performed. In addition, the performFallbackConversion method will be called if the object to be converted is not a String or the target type (toClass) is not a String or a String array. The app07c application illustrates the use of StrutsTypeConverter by featuring a converter for converting Color objects to Strings and vice versa. The user can specify a color by defining its red, green, and blue components in a comma-delimited String. For instance, blue is 0,0,255 and green is 0,255,0. Each component value must be an integer in the range of 0 and 255. A Color object is an instance of the Color class shown in method that returns the hexadecimal code of the color.
Listing
7.10. A color consists of red, green, and blue components and have a getHexCode
Listing 7.10. The Color class
package app07c; import com.opensymphony.xwork2.ActionSupport; public class Color extends ActionSupport { private int red; private int green; private int blue; // getters and setters not shown public String getHexCode() { return (red < 16? "0" : "") + Integer.toHexString(red) + (green < 16? "0" : "") + Integer.toHexString(green) + (blue < 16? "0" : "") + Integer.toHexString(blue); } } The directory structure of app07c is shown in Figure 7.6. There are two actions defined in it, Design1 and Design2, as described in the struts.xml file accompanying app07c. The action declarations are printed in
Listing 7.11.
and blue components and constructs a Color object. Its convertToString method takes a Color object and constructs a String.
To use MyColorConverter, you must configure it. The xwork-conversion.properties file in Listing 7.14 is the class-based configuration file. There is only one entry in this file, mapping the Color class with MyColorConverter. If you're mapping more than one class, feel free to add more entries in this file.
To test the color converter, direct your browser to this URL: http://localhost:8080/app07c/Design1.action You will see a form with two text fields like the one in and a color.
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.
Listing 7.16) has two properties, adminId and employee, adminId is a String, but employee is of type Employee, another class (shown in Listing 7.17) with its own properties (firstName, lastName, and birthDate). With one HTML
The Admin class (See 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
label="Employee Last Name"/> <s:textfield name="employee.birthDate" label="Employee Birth Date (yyyy-MM-dd)"/> <s:submit/> </s:form> </div> </body> </html> The Confirmation.jsp page in Listing 7.19 shows how to display the adminId property as well as the properties of the employee property.
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.27. Note that the employees property is a Map. The Employee class is presented in Listing 7.28 and is a template for employees.
The Admin class is given in
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.
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
Figure 7.14. Enter values in the text fields and submit Figure 7.15.
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:
1. Determine the action whose input is to be validated. 2. Write a validator configuration file. The file name must follow one of these two patterns:
90 ActionClass-validation.xml ActionClass-alias-validation.xml
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>
A field-validator element must have a type attribute, which points to a validator. In addition, it can have a short-circuit attribute. The value of short-circuit is either true or false (default). A value of true indicates that if the current validator fails, the next validators for the same field will not be executed. For example, in the configuration below, if the required validator fails, the email validator will not be executed. <field name="userEmail"> <field-validator type="required" short-circuit="true"> </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.
</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
Data Type
Description
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
The maximum length allowed. If this parameter is not present, there will be no maximum length restriction for the associated field. The minimum length allowed for the associated field. If this parameter is not present, there will be no minimum length restriction for the field.
maxLength int
Name
Data Type
Description
trim
boolean Indicates whether or not trailing spaces will be trimmed prior to validation.
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
Description
min
The maximum value allowed. If this parameter is not present, there's no maximum value. The minimum value allowed. If this parameter is not present, there's no minimum value.
max int
As an example, consider the IntTestAction class in Listing 8.10. It exposes one property, year, which is an int representing the year part of a date.
</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
Data Type
Description
max date
The maximum value allowed. If this parameter is not present, there will be no maximum value. The minimum value allowed. If this parameter is not present, there will be no minimum value.
min
date
Note The date pattern used to validate a date is dependant on the current locale.
Listing 8.13 is used to test the date validator. The DateTestAction-validation.xml configuration file in Listing 8.14 assigns the date
For example, the DateTestAction class in validator to the birthDate field.
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
Description
expression* String
caseSensitive boolean Indicates whether or not the matching should be done in a case sensitive way. The default value is true. trim boolean Indicates whether or not the field should be trimmed prior to validation. The default value is true.
Table 8.6.
Name
Data Type
expression* String
There are two examples in this section. The first one deals with the expression validator, the second with the fieldexpression validator.
Listing 8.22 has two properties, min and max, that will be used in the OGNL expression of an expression validator instance. Listing 8.23
The ExpressionTestAction class in 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.
Listing 8.26 specifies an OGNL expression for the fieldexpression validator. Listing 8.27 shows the JSP used in this example.
validator configuration file in
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]
Listing 8.28 has one property, age, which is an int. The validator configuration file in Listing 8.29 configures the conversion
For example, the ConversionTestAction class in 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.
Figure 8.11. The Customer class and the Address class are shown in Listings 8.31 and 8.32, respectively.
given in app08b and its directory structure is shown in
Listing
8.33. Note that you can specify the validators for the properties in the Address object
Listing 8.33. The Customer-validation.xml
<!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="firstName"> <field-validator type="requiredstring"> <message>First Name must not be empty</message> </field-validator> </field> <field name="lastName"> <field-validator type="requiredstring"> <message>Last Name must not be empty</message> </field-validator> </field> <field name="address.streetName"> <field-validator type="requiredstring"> <message>Street Name must not be empty</message> </field-validator> </field> <field name="address.streetNumber"> <field-validator type="requiredstring"> <message>Street Number must not be empty</message> </field-validator> </field> <field name="address.city"> <field-validator type="requiredstring"> <message>City must not be empty</message> </field-validator> </field> <field name="address.state"> <field-validator type="requiredstring"> <message>State must not be empty</message> </field-validator> </field> <field name="address.zipCode"> <field-validator type="requiredstring">
<message>Zip Code must not be empty</message> </field-validator> </field> </validators> The Customer.jsp page in Listing properties in the Customer action.
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.
Adds a field error. From your validate method you call the addActionError when a plain validator fails or the addFieldError when a field validator fails.
FieldValidatorSupport extends ValidatorSupport and adds two properties, propertyType and fieldName.
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.
The supporting class for strongpassword is the app08e.validator.StrongPasswordValidator class. This class extends the FieldValidatorSupport class and is shown in Listing 8.42. The validate method uses the isPasswordStrong method to test the strength of a password.
if (value == null || value.length() == 0) { // use a required validator for these return; } if ((minLength > -1) && (value.length() < minLength)) { addFieldError(fieldName, object); } else if (!isPasswordStrong(value)) { addFieldError(fieldName, object); } } private static final String GROUP_1 = "abcdefghijklmnopqrstuvwxyz"; private static final String GROUP_2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static final String GROUP_3 = "0123456789"; protected boolean isPasswordStrong(String password) { boolean ok1 = false; boolean ok2 = false; boolean ok3 = false; int length = password.length(); for (int i = 0; i < length; i++) { if (ok1 && ok2 && ok3) { break; } String character = password.substring(i, i + 1); if (GROUP_1.contains(character)) { ok1 = true; continue; } if (GROUP_2.contains(character)) { ok2 = true; continue; } if (GROUP_3.contains(character)) { ok3 = true; } } return (ok1 && ok2 && ok3); } } The validators.xml file in
Now that you've registered your custom validator, you can use it the same way you would a
Listing 8.44 has a password property that can only be assigned a strong password. The User-validation.xml file in Listing 8.45 configures the validators for the User class.
bundled validator. For example, the User class in
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 de el en es fr hi it ja nl pt ru zh German Greek English Spanish French Hindi Italian Japanese Dutch Portuguese Russian Chinese
Language
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 Australia Brazil Canada China Egypt France Germany India Mexico Switzerland Taiwan United Kingdom United States AU BR CA CN EG FR DE IN MX CH TW GB US
Code
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:
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
The key of the message to be retrieved. The name of the variable that references the value to pushed to the stack context.
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}
You can use this text tag to pass a parameter. <s:text name="greetings"> <s:param>Visitor</s:param> </s:text>
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
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.
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.
The locale will be retained throughout the session. As an example, the app09d application illustrates how you can create an application that lets the user select a language. The actions in this application are declared in
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
Listing 10.2 implements ModelDriven and its getModel method returns an instance of the Product class (given in Listing 10.3).
example, the ProductAction class in
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: Employee_list that shows all employees in the system Employee_create that is used to add a new employee
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.
database. In this application, EmployeeManager provides a simple repository of Employee objects in a List. Note
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.
Listing 10.10.
public String edit() { return SUCCESS; } public String update() { EmployeeManager.update(employee); return SUCCESS; } public String delete() { EmployeeManager.delete(employeeId); return SUCCESS; } public int getEmployeeId() { return employeeId; } public void setEmployeeId(int employeeId) { this.employeeId = employeeId; } } Note that the prepare method in the EmployeeAction class will create a new Employee object only if employeeId is 0. If an action invocation populates the employeeId property of the action object, the prepare method will attempt to find an Employee object through the EmployeeManager class. This is why the Employee_edit action uses the paramsPrepareParamsStack stack that calls the Params interceptor twice, as shown below: <interceptor-stack name="paramsPrepareParamsStack"> ... <interceptor-ref name="params"/> ... <interceptor-ref name="prepare"/> <interceptor-ref name="model-driven"/> ... <interceptor-ref name="params"/> ... </interceptor-stack>
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
ResourceParams element. The username and password parameters specify the user name and password used to access the database, the driverClassName parameter specifies the JDBC driver, and the url parameter specifies the database URL for accessing the MySQL database. The url parameter indicates that the database server resides in the same machine as Tomcat (the use of localhost in the URL) and the database the DataSource object references is the test database. Also, for your DAO implementation, you may want to extend the java.lang.Exception class to have your own DAO-specific exception. Methods in DAO objects can throw this specific exception so that you can provide code that deals with data access and data manipulation failures. A simple DAO-specific exception class, named DAOException, is given in
Listing 11.4.
maximumSearchResults, that do not exist in Employee. Hence, the need for another class that encapsulates user search criteria.
private static final String UPDATE_EMPLOYEE_SQL = "UPDATE employees SET firstName=?, lastName=? WHERE id = ?"; public void updateEmployee(Employee employee) throws DAOException { Connection connection = null; PreparedStatement pStatement = null; try { connection = getConnection(); pStatement = connection.prepareStatement( UPDATE_EMPLOYEE_SQL); pStatement.setString(1, employee.getFirstName()); pStatement.setString(2, employee.getLastName()); pStatement.setInt(3, employee.getId()); pStatement.executeUpdate(); pStatement.close(); } catch (SQLException e) { throw new DAOException(); } finally { try { connection.close(); } catch (SQLException ex) { } } } private static final String GET_EMPLOYEE_SQL = "SELECT firstName, lastName FROM employees WHERE id = ?"; public Employee getEmployee(int employeeId) throws DAOException { Connection connection = null; PreparedStatement pStatement = null; ResultSet rs = null; Employee employee = new Employee(); try { connection = getConnection(); pStatement = connection.prepareStatement( GET_EMPLOYEE_SQL); pStatement.setInt(1, employeeId); rs = pStatement.executeQuery(); if (rs.next()) { employee.setFirstName(rs.getString("firstName")); employee.setLastName(rs.getString("lastName")); employee.setId(employeeId); } rs.close(); pStatement.close(); } catch (SQLException ex) { throw new DAOException(); } finally { try { connection.close(); } catch (SQLException ex) { } } return employee; }
private static final String DELETE_EMPLOYEE_SQL = "DELETE FROM employees WHERE id = ?"; public void deleteEmployee(int employeeId) throws DAOException { Connection connection = null; PreparedStatement pStatement = null; try { connection = getConnection(); pStatement = connection.prepareStatement(DELETE_EMPLOYEE_SQL); pStatement.setInt(1, employeeId); pStatement.executeUpdate(); pStatement.close(); } catch (SQLException e) { throw new DAOException(); } finally { try { connection.close(); } catch (SQLException ex) { } } } private static final String SEARCH_EMPLOYEES_SQL = "SELECT id, firstName, lastName FROM employees WHERE "; public List<Employee> searchEmployees( EmployeeSearchCriteria searchCriteria) throws DAOException { List<Employee> employees = new ArrayList<Employee>(); Connection connection = null; Statement statement = null; ResultSet resultSet = null; // Build the search criterias StringBuilder criteriaSql = new StringBuilder(512); criteriaSql.append(SEARCH_EMPLOYEES_SQL); if (searchCriteria.getFirstName() != null) { criteriaSql.append("firstName LIKE '%" + DBUtil.fixSqlFieldValue(searchCriteria.getFirstName()) + "%' AND "); } if (searchCriteria.getLastName() != null) { criteriaSql.append("lastName LIKE '%" + DBUtil.fixSqlFieldValue(searchCriteria.getLastName()) + "%' AND "); } // Remove unused 'And' & 'WHERE' if (criteriaSql.substring(criteriaSql.length() - 5). equals(" AND ")) criteriaSql.delete(criteriaSql.length() - 5, criteriaSql.length() - 1); if (criteriaSql.substring(criteriaSql.length() - 7). equals(" WHERE ")) criteriaSql.delete(criteriaSql.length() - 7, criteriaSql.length() - 1);
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();
public static void create(Employee employee) { EmployeeDAO employeeDAO = DAOFactory.getInstance().getEmployeeDAO(); try { employeeDAO.createEmployee(employee); } catch (DAOException e) { } } public static void delete(int employeeId) { EmployeeDAO employeeDAO = DAOFactory.getInstance().getEmployeeDAO(); try { employeeDAO.deleteEmployee(employeeId); } catch (DAOException e) { } } public static Employee find(int employeeId) { EmployeeDAO employeeDAO = DAOFactory.getInstance().getEmployeeDAO(); try { return employeeDAO.getEmployee(employeeId); } catch (DAOException e) { } return null; } public static void update(Employee employee) { EmployeeDAO employeeDAO = DAOFactory.getInstance().getEmployeeDAO(); try { employeeDAO.updateEmployee(employee); } catch (DAOException e) { } } public static List<Employee> search( EmployeeSearchCriteria criteria) { EmployeeDAO employeeDAO = DAOFactory.getInstance().getEmployeeDAO(); try { return employeeDAO.searchEmployees(criteria); } catch (DAOException e) { } return null; } }
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:
private List<File> attachmentFile = new ArrayList<File>(); private List<String> attachmentFileName = new ArrayList<String>(); private List<String> attachmentContentType = new ArrayList<String>();
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.
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}
To override the messages here, create a struts-messages.properties file that contains values that you want to override the default values and place the file under WEBINF/classes/org/apache/struts2. If you create a new struts-messages.properties file, the default one will not be examined. This means, if you override one message key and decide to use the other default ones, you must copy the latter to your properties file.
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 List variables.
Listing
12.8 shows how to use Lists. Note that you must instantiate a List implementation for the
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
For instance, this code sends a file to the browser. FileInputStream fis = new FileInputStream(file); BufferedInputStream bis = new BufferedInputStream(fis); byte[] bytes = new byte[bis.available()]; response.setContentType(contentType); OutputStream os = response.getOutputStream(); bis.read(bytes); os.write(bytes);
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
String inputStream The name of the action class property that returns the InputStream object to be flushed to the browser. int 1024 The buffer size used when reading the InputStream and the OutputStream used for flushing data to the browser. Sets the Content-Type response header Sets the Content-Length response header Sets the Content-Disposition response header
bufferSize
contentType contentLength
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.
A product is represented by the Product class in Listing 13.5 and DisplayProducts obtains a list of products and displays the details of each product. The DisplayProductsAction class, the action class for DisplayProducts, is given in
Listing
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 }
<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.
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
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. web-resource-name. A resource identifier. This element is required. decription. A description of the resource. This element is optional. url-pattern. Specifies a URL pattern which the restriction must be applied to. There can be zero or more url-pattern elements in a web-resource-collection element. For example, if you want to protect the resources in the members and trading directories, you need two url-pattern elements. http-method. Specifies the restricted method. For example, if the value of the http-method element is GET, then all GET requests will be restricted.
The auth-constraint element can have the following subelements. description. A description. This is an optional element. role-name. The user role allowed access to the restricted resource. There can be zero to many role-name elements in an auth-constraint element.
The user-data-constraint element can contain the following elements: description. A description. This is an optional subelement. transport-guarantee. The possible values are NONE, INTEGRAL, CONFIDENTIAL. NONE means the application does not require any transport guarantees. INTEGRAL means the data must be transported in such a way that it cannot be changed in transit. CONFIDENTIAL means that the transmitted data must be encrypted.
<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.
The form-login-config element is used when the value of <auth-method> is FORM. It specifies the login page to be used and the error page to be displayed if authentication failed. Here is a login-config element. <login-config> <auth-method>BASIC</auth-method> <realm-name>User Basic Authentication</realm-name> </login-config> Authentication methods are the subject of discussion on the next section.
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.
Form-based authentication is similar to Basic authentication. However, you specify a login page yourself. This gives you a chance to customize the look and feel of your login dialog. This authentication method will also display a custom Error page written by the developer on a failed attempt to login. Again, you can use SSL to encrypt users' credentials. Digest authentication works like Basic authentication; however, the login information is not transmitted. Instead, the hash of the passwords is sent. This protects the information from malicious sniffers. Basic and digest authentication methods are specified in RFC 2617, which you can find at ftp://ftp.isi.edu/in-notes/rfc2617.txt. More information about SSL can be found at
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
You can use the following URL to test the application. http://localhost:8080/app14a/displayAddOrderForm.do
<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.
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).
Here is an example of a profiling result for an action that uploads a file. INFO: [80ms] - FilterDispatcher_doFilter: [40ms] - Handling request from Dispatcher [0ms] - create DefaultActionProxy: [0ms] - create DefaultActionInvocation: [0ms] - actionCreate: SingleUpload2 [40ms] - invoke: [40ms] - interceptor: fileUpload [20ms] - invoke: [20ms] - interceptor: exception [20ms] - invoke: [20ms] - interceptor: servletConfig [20ms] - invoke: [20ms] - interceptor: prepare [20ms] - invoke: [20ms] - interceptor: checkbox [20ms] - invoke: [20ms] - interceptor: params [10ms] - invoke: [10ms] - interceptor: conversionError [10ms] - invoke: [0ms] - invokeAction: Upload2 [10ms] - executeResult: success
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. threadPriority. The priority to assign the thread. The default value is Thread.NORM_PRIORITY. delay. The number of milliseconds to wait before the user is forwarded the wait result. The default is 0. delaySleepInterval. Specifies the number of milliseconds the main thread (the one that creates a background thread to handle the action) has to wake up to check if the background process has been completed. The default is 100 and this parameter only takes effect if the delay is not zero.
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.
private int complete = 0; public int getComplete() { complete += 10; return complete; }
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.
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
com.opensymphony.xwork2.interceptor.PreResultListener interface allows you to do something after the action is executed but before the execution of the result. This interface has one callback method, beforeResult: void beforeResult(com.opensymphony.xwork2.ActionInvocation invocation, java.lang.String resultCode)
The AbstractInterceptor class implements Interceptor and provides empty implementations of the init and destroy methods. Since not all interceptors need to initialize resources or do anything when they are destroyed, extending AbstractInterceptor saves your from implementing init and destroy. The AbstractInterceptor class is shown in
Listing 18.2.
Chapter 11, "The Persistence Layer"). The DataSourceInjectorInterceptor class is presented in Listing 18.3. In this example,
discussion of the DAO pattern in 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 a random word of a specified length. public static String getHash(java.lang.String word)
Returns an irreversible hash of the specified word. public static java.awt.image.BufferedImage getCaptchaImage( java.lang.String word, int width, int height, int type)
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.
To test the application, direct your browser to this URL: http://localhost:8080/app19a/Login_input.action You'll see a CAPTCHA image similar to the one in
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.
In addition, Velocity relies on the Digester project, so the commons-digester-VERSION.jar file, included with Struts deployment, is also needed. The default.properties file specifies the following entry that indicates that Velocity configuration file must be named velocity.properties. struts.velocity.configfile = velocity.properties
Description The value stack The action object The HttpServletResponse object The alias for response The HttpServletRequest object The alias for request The HttpSession object
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
Some tags, including form, require an #end. #sform ... #stextfield ... #ssubmit ... #end
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" ... )
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
Listing
20.1.
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.
Description The HttpServletResponse object The alias for response The HttpServletRequest object The alias for request The HttpSession object
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>
Now, compare these JSP tags <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>
<@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>
stylesheetLocation. The location of the stylesheet file. excudingPattem. Specifies excluded elements. Note that there's a typo (there is no 1 in excluding) that has not been fixed until Struts version 2.0.9. matchingPattern. Specifies the matching pattern. By default it matches everything. parse. Indicates whether or not the stylesheetLocation parameter should be parsed for OGNL expressions. The default value is false.
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>
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
Listing
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>
You can test the application by directing your browser to this URL: http://localhost:8080/app22a/XSL.action
The result is this: <?xml version="1.0" encoding="UTF-8"?> <product> <productName>Epson</productName> <productDescription>Super printer</productDescription> <price>12.34</price> <supplierName>Online Business Ltd.</supplierName> </product> Note A modified org.apache.struts2.views.xslt.XSLTResult class is included in the app22a example. For debugging purpose, I added a method that prints the raw XML to the console or the Catalina.out file. The XSLTResult class is the underlying class of the XSLT result type.
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.
Listing 23.4 and the Thanks.jsp, the page you'll see after a successful login, in Listing 23.5
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.
Description The name of the attribute to insert. It will be ignored if the value attribute is present. The attribute object to render.
value flush
String
boolean A value of true causes the current page output stream to be flushed before insertion. boolean A value of true indicates that no exception will be thrown if the attribute specified by the name attribute cannot be found. The default value for this attribute is false. 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.
ignore
role
preparer String
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.
boolean A value of true indicates that no exception will be thrown if the attribute specified by the name attribute cannot be found. The default value for this attribute is false.
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.
A definition element is only useful if it contains one or several put-attribute elements. A put-attribute element is used to pass a value to the layout page referenced by the definition. For example, the definition elements below use the MyLayout.jsp page and pass four values: <definition name="Product" template="/jsp/MyLayout.jsp"> <put-attribute name="pageTitle" value="Product Info"/> <put-attribute name="header" value="/jsp/Header.jsp"/> <put-attribute name="footer" value="/jsp/Footer.jsp"/> <put-attribute name="body" value="/jsp/Product.jsp"/> </definition> <definition name="Thanks" template="/jsp/MyLayout.jsp"> <put-attribute name="pageTitle" value="Thank You"/> <put-attribute name="header" value="/jsp/Header.jsp"/> <put-attribute name="footer" value="/jsp/Footer.jsp"/> <put-attribute name="body" value="/jsp/Thanks.jsp"/> </definition>
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.
Figure
Listing 24.2.
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
Figure
24.5.
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.
2. Copy the brainyjfreechartplugin.jar file to the WEB-INF/lib directory. 3. Make sure that your Struts package extends brainyjfreechart-default. 4. Use brainyjfreechart as the result type. 5. Your action class must have a chart property that returns the JFreeChart object to be displayed. Optionally, you can have chartWidth and chartHeight properties to determine the chart size.
Application app25b shows an action that uses Brainy Software's JFreeChart plug-in. The action declarations are shown in
Listing 25.4.
25.2,
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")
Listing
26.4.
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
Default Value
Description
/struts/dojo The path to the Dojo distribution folder Indicates if Dojo files should be cached by the browser.
boolean true
Name
Data Type
Default Value
Description
compressed
boolean true
Indicates whether or not the compressed version of Dojo files should be used. Indicates whether or not Dojo should be in debug mode. Comma delimited list of locales to be used by Dojo. Overrides Dojo locale. Indicates whether or not to parse the whole document for widgets.
debug
boolean false
extraLocales
String
locale parseContent
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
Default Value
Description
afterNotifyTopics
Comma delimited topics to be published after the request, if the request is successful. Whether or not to start the timer automatically. Comma delimited topics to be published before the request. Whether or not to show a Close button when the div is inside a tabbed panel The number of milliseconds that must elapse before the content is fetched Comma delimited topics to be published after the request, if the request fails. The text to be displayed if the request fails. Indicates whether or not JavaScript code in the fetched content should be executed.
autoStart
boolean true
beforeNotifyTopics
String
closable
boolean false
delay
integer
errorNotifyTopics
String
errorText
String
executeScripts
boolean false
Name
Default Value
Description
formFilter
The function to be used to filter the form fields. The identifier of the form whose fields will be passed as request parameters. The JavaScript function that will make the request. The color to highlight the elements specified in the targets attribute. The duration in milliseconds the elements specified in the targets attribute will be highlighted. This attribute will only take effect if the hightlightColor attribute has a value. The URL to call to fetch the content. The identifier of the element that will be displayed while making the request. Indicates whether or not to use JavaScript to generate tooltips. The topics that will trigger the remote call. Loading... The text to display while content is being fetched. Comma delimited topics to be published
formId
String
handler
String
highlightColor
String
highlightDuration
integer 2000
href indicator
String String
javascriptTooltip
boolean false
listenTopics
String
loadingText
String
notifyTopics
String
Name
Data Type
Default Value
Description
before and after the request and upon an error occurring. openTemplate String The template to use for opening the rendered HTML Whether or not to parse the returned content for widgets. Whether or not to load content when the page is loaded. Whether or not to load content when the div becomes visible. This attribute takes effect only if the div is inside a tabbed panel. Whether or not to run the script in a separate scope that is unique for each tag. Whether or not errors will be shown. Whether or not loading text will be shown on targets Topics that will start the timer Topics that will stop the timer XMLHttp The transport for making the request Transport
parseContent
boolean true
preload
boolean true
refreshOnShow
boolean false
separateScripts
boolean true
Name
Default Value
Description
updateFreq
The div tag also inherits the common attributes specified in Three examples are given for this tag.
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
Default Value
Description
afterNotifyTopics
Comma delimited topics to be published after the request, if the request is successful. Indicates whether or not to make an asynchronous request if validation succeeds. This attribute will only take effect if the validate attribute is set to true. Comma delimited topics to be published before the request. Comma delimited topics to be published after the request, if the request fails. The text to be displayed if the request fails. Indicates whether or not JavaScript code in the fetched content should be executed. The function to be used to filter the form fields.
ajaxAfterValidation
boolean false
beforeNotifyTopics
String
errorNotifyTopics
String
errorText
String
executeScripts
boolean false
formFilter
String
Name
Default Value
Description
formId
The identifier of the form whose fields will be passed as request parameters. The JavaScript function that will make the request. The color to highlight the elements specified in the targets attribute. The duration in milliseconds the elements specified in the targets attribute will be highlighted. This attribute will only take effect if the hightlightColor attribute has a value. The URL to call to fetch the content. The identifier of the element that will be displayed while making the request. Indicates whether or not to use JavaScript to generate tooltips. The topics that will trigger the remote call Loading... The text to display while content is being fetched Comma delimited topics to be published before and after the request and upon an error occurring The template to use for opening the
handler
String
highlightColor
String
highlightDuration
integer 2000
href indicator
String String
javascriptTooltip
boolean false
listenTopics loadingText
String String
notifyTopics
String
openTemplate
String
Name
Data Type
Default Value
Description
rendered HTML parseContent boolean true Whether or not to parse the returned content for widgets. Whether or not to run the script in a separate scope that is unique for each tag. Whether or not errors will be shown. Whether or not loading text will be shown on targets Comma delimited identifiers of the elements whose content will be updated XMLHttp The transport for making the request Transport Whether or not AJAX validation should be performed
separateScripts
boolean true
targets
String
transport
String
validate
boolean false
The a tag also inherits the common attributes specified in For instance, the A.jsp page in div1 and div2.
Like the a tag, submit has a targets attribute to specify elements that will display the result of the form submit. The submit tag attributes are listed in the common attributes specified in
Table 27.4. In addition, the submit tag inherits Chapter 5, "Form Tags."
Name
Default Value
Description
afterNotifyTopics
Comma delimited topics to be published after the request, if the request is successful. Indicates whether or not to make an asynchronous request if validation succeeds. This attribute will only take effect if the validate attribute is set to true. Comma delimited topics to be published before the request. Comma delimited topics to be published after the request, if the request fails. The text to be displayed if the request fails. Indicates whether or not JavaScript code in the fetched content should be executed. The function to be used to filter the form fields. The identifier of the form whose fields will be passed as request parameters. The JavaScript function that will make the request.
ajaxAfterValidation
boolean false
beforeNotifyTopics
String
errorNotifyTopics
String
errorText
String
executeScripts
boolean false
formFilter
String
formId
String
handler
String
Name
Default Value
Description
highlightColor
The color to highlight the elements specified in the targets attribute. The duration in milliseconds the elements specified in the targets attribute will be highlighted. This attribute will only take effect if the hightlightColor attribute has a value. The URL to call to fetch the content. The identifier of the element that will be displayed while making the request. Indicates whether or not to use JavaScript to generate tooltips. The topics that will trigger the remote call. Loading... The text to display while content is being fetched. The method attribute. Comma delimited topics to be published before and after the request and upon an error occurring. Whether or not to parse the returned content for widgets. Whether or not to run the script in a
highlightDuration
integer 2000
href indicator
String String
javascriptTooltip
boolean false
listenTopics
String
loadingText
String
method notifyTopics
String String
parseContent
boolean true
separateScripts
boolean true
Name
Data Type
Default Value
Description
separate scope that is unique for each tag. showErrorTransportText boolean true showLoadingText boolean false Whether or not errors will be shown. Whether or not loading text will be shown on targets The image source for a submit button of type image. Comma delimited identifiers of the elements whose content will be updated XMLHttp The transport for making the request Transport input The type of the submit button. Possible values are input, image, and button. Whether or not AJAX validation should be performed
src
String
targets
String
transport
String
type
String
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.
<s:form id="loginForm" action="..."> <s:textfield name="userName" label="User Name"/> <s:password name="password" label="Password"/> </s:form> <sx:submit formId="loginForm"/>
Table 27.5
Name
Default Value
Description
afterNotifyTopics
Comma delimited topics to be published after the request, if the request is successful. Indicates whether or not to make an asynchronous request if validation succeeds. This attribute will only take effect if the validate attribute is set to true. Comma delimited topics to be published before the request. Comma delimited topics to be published after the request, if the request fails. The text to be displayed if the request fails.
ajaxAfterValidation
boolean false
beforeNotifyTopics
String
errorNotifyTopics
String
errorText
String
Name
Default Value
Description
events
Comma delimited event names to attach to Indicates whether or not JavaScript code in the fetched content should be executed. The function to be used to filter the form fields. The identifier of the form whose fields will be passed as request parameters. The JavaScript function that will make the request. The color to highlight the elements specified in the targets attribute. The duration in milliseconds the elements specified in the targets attribute will be highlighted. This attribute will only take effect if the hightlightColor attribute has a value. The URL to call to fetch the content. The identifier of the element that will be displayed while making the request. The topics that will trigger the remote call.
executeScripts
boolean false
formFilter
String
formId
String
handler
String
highlightColor
String
highlightDuration
integer 2000
href indicator
String String
listenTopics
String
Name
Default Value
Description
loadingText
Loading... The text to display while content is being fetched. Comma delimited topics to be published before and after the request and upon an error occurring. Whether or not to run the script in a separate scope that is unique for each tag. Whether or not errors will be shown. Whether or not loading text will be shown on targets Comma delimited identifiers of the elements to attach to Comma delimited identifiers of the elements whose content will be updated XMLHttp The transport for making the request Transport Whether or not AJAX validation should be performed
notifyTopics
String
separateScripts
boolean true
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
Whether or not to adjust the number of rows in each month. If this attribute value is false, there are always six rows in each month. Determines the day names in the header. Possible values are narrow, abbr, and wide. The date and time pattern according to Unicode Technical Standard #35 The number of weeks to display
dayWidth
String
narrow
displayFormat
String
displayWeeks endDate
integer 6 Date
2941-10- The last available date 12 short The formatting type for the display. Possible values are short, medium, long, and full. Indicates whether or not to use JavaScript to generate tooltips. The language to use. The default language is the browser's default language. 1492-10- The first available date 12 Whether or not only the dates in the current month can be viewed and selected The toggle duration in milliseconds The toggle type for the dropdown. Possible values
formatLength
String
language
String
startDate
Date
staticDisplay
boolean false
toggleDuration toggleType
Name
Data Type
Default Value
Description
are plain, wipe, explode, and fade. type String date Whether this widget will be rendered as a date picker or a time picker. Allowed values are date and time. Comma delimited topics that will be published when a value is selected. The first day of the week. 0 is Sunday and 6 is Saturday.
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.
For instance, the following is an example of the datetimepicker tag. <sx:datetimepicker adjustWeeks="true" displayFormat="MM/dd/yyyy" toggleType="explode" />
You can view the example by directing your browser to this URL. http://localhost:8080/app27a/DateTimePicker.action
Table 27.7.
Name
Default Value
Description
afterNotifyTopics
Comma delimited topics to be published after the request, if the request is successful. Indicates whether or not to make an asynchronous request if validation succeeds. This attribute will only take effect if the validate attribute is set to true. Comma delimited topics to be published before the request. Comma delimited topics to be published after the request, if the request fails.
ajaxAfterValidation
boolean false
beforeNotifyTopics
String
errorNotifyTopics
String
Name
Default Value
Description
errorText
The text to be displayed if the request fails. Indicates whether or not JavaScript code in the fetched content should be executed. The function to be used to filter the form fields. The identifier of the form whose fields will be passed as request parameters. The JavaScript function that will make the request. The color to highlight the elements specified in the targets attribute. The duration in milliseconds the elements specified in the targets attribute will be highlighted. This attribute will only take effect if the hightlightColor attribute has a value. The URL to call to fetch the content. The identifier of the element that will be displayed while making the request. Indicates whether or not to use JavaScript to generate tooltips.
executeScripts
boolean false
formFilter
String
formId
String
handler
String
highlightColor
String
highlightDuration
integer 2000
href indicator
String String
javascriptTooltip
boolean false
Name
Default Value
Description
listenTopics
The topics that will trigger the remote call. Loading... The text to display while content is being fetched. Comma delimited topics to be published before and after the request and upon an error occurring. Whether or not to parse the returned content for widgets. Whether or not to run the script in a separate scope that is unique for each tag. Whether or not errors will be shown. Whether or not loading text will be shown on targets Comma delimited identifiers of the elements whose content will be updated XMLHttp The transport for making the request Transport Whether or not AJAX validation should be performed
loadingText
String
notifyTopics
String
parseContent
boolean true
separateScripts
boolean true
targets
String
transport
String
validate
boolean false
The tabbedpanel tag also inherits the common attributes specified in Tags." In addition, the id attribute is mandatory for tabbedpanel.
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>
Chapter 5, "Form Tags," the textarea tag adds three more attributes, which are given in Table 27.8.
In addition to the common attributes discussed in
Description The cols attribute of the rendered textarea The rows attribute of the rendered textarea
Name
Default Value
Description
afterNotifyTopics
Comma delimited topics to be published after the request, if the request is successful.
autoComplete beforeNotifyTopics String Comma delimited topics to be published before the request.
Name
Default Value
Description
dataFieldName
value in the The name of the field in the returned name attribute JSON object that contains the data array The delay in milliseconds before making the search The height of the dropdown in pixels
delay
integer 100
dropdownHeight dropdownWidth
integer 120
integer the same as the The width of the dropdown in pixels textbox boolean false String Whether or not to insert an empty option Comma delimited topics to be published after the request, if the request fails. Whether or not only an included option can be selected The function to be used to filter the form fields. The identifier of the form whose fields will be passed as request parameters. The key for the first item in the list The value for the first item in the list The URL to call to fetch the content. Path to the icon used for the dropdown The identifier of the element that will be
emptyOption errorNotifyTopics
forceValidOption
boolean false
formFilter
String
formId
String
Name
Data Type
Default Value
Description
displayed while making the request. javascriptTooltip boolean false Indicates whether or not to use JavaScript to generate tooltips. The property to which the selected key will be assigned. An iterable source to populate from The property of the object in the list that will supply the option values The property of the object in the list that will supply the option labels The topics that will trigger the remote call. The minimum number of characters that must be entered to the textbox before options will be loaded Whether or not to reload options every time a character is entered to the texbox Corresponds to the HTML maxlength attribute Comma delimited topics to be published before and after the request and upon an error occurring.
keyName
String
list listKey
String String
listValue
String
listenTopics
String
loadMinimumCount integer 3
maxlength
integer
notifyTopics
String
Name
Data Type
Default Value
Description
preload
boolean true
Whether or not to reload options when the page loads The maximum number of options. -1 indicates no limit. Search type, possible values are startstring, startword, and substring. Whether or not to show the down arrow The transport for making the request
resultsLimit
integer 30
searchType
String
startstring
showDownArrow transport
valueNotifyTopics
String
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
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
Default Value
Description The source for the blank icon The name of the property that returns a collection of child nodes Comma separated topics to be published when a node is collapsed Comma delimited topics to be published after the request, if the request fails. The source for the expand icon The source for the expand icon
childCollectionProperty String
collapsedNotifyTopics String
errorNotifyTopics
String
expandIconSrcMinus expandIconSrcPlus
String String
Name
Data Type
Default Value
Description Comma delimited topics to be published when a node is expanded Image source for under child item child icons Image source for the last child grid Image source for under parent item child icons Image source for vertical line Image source for grid for sole root item Image source for grid for last root item The URL to call to fetch the content.
expandedNotifyTopics String
The icon height The icon width Indicates whether or not to use JavaScript to generate tooltips. The name of the property whose value is to be used as the node id The name of the property whose value is to be used as the node title
boolean false
nodeIdProperty
nodeTitleProperty
openTemplate
String
Name rootNode
Default Value
Description The name of the property whose value is to be used as the root Comma delimited topics to be published when a node is selected. An object with a property named node will be passed to the subscribers. Whether or not to show the grid The showRootGrid property The toggle property. Possible values are fade or explode. Toggle duration in milliseconds
selectedNotifyTopics
String
toggleDuration
integer 150
Name
Data Type
Default Value
Description
Indicates whether or not to use JavaScript to generate tooltips. The template to use for opening the rendered HTML
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
Description The action name. The action class associated with this action.
method The action method. converter The converter for this action.
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 class*
Description The Java class to be instantiated or whose static methods to be made available. The primary interface the Java class implements. A unique name for referring to this bean. The bean scope. Allowable values are default, singleton, request, session, and thread.
Attribute static
Table A.3. Both the name and value attributes are required.
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
Description
exception* Specifies the exception type to be caught. result* Specifies a result that will be executed if an exception is caught. The result may be in the same action or in the global-results element.
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>
<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>
Description The name to refer to the interceptor. The Java class for this interceptor.
For instance, the following interceptor element registers the File Upload interceptor.
<action name="Product_save" class="..."> <interceptor-ref name="alias"/> <interceptor-ref name="i18n"/> <interceptor-ref name="validation"/> <interceptor-ref name="logger"/> <result name="input">/jsp/Product.jsp</result> <result>/jsp/ProductDetails.jsp</result> </action> </package>
<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.
Description The package name that must be unique throughout the struts.xml file. The parent package extended by this package.
Attribute abstract
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.
Description The result name, associated with the action method's return value. The registered result type associated with this result.
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.
Description The name to refer to this result type. The Java class for this result type. 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 default object factory. The value must be a subclass of com.opensymphony.xwork2.ObjectFactory. A short-hand notation, such as spring that represents SpringObjectFactory, is supported. struts.objectFactory.spring.autoWire = name
The auto-wiring logic when using the SpringObjectFactory. Valid values are name (the default), type, auto, and constructor. struts.objectFactory.spring.useClassCache = true
Indicates to the Struts-Spring integration module if Class instances should be cached. struts.objectTypeDeterminer
Specifies the object type determiner. The value must be an implementation of com.opensymphony.xwork2.util.ObjectTypeDeterminer. Shorthand notations such as tiger or notiger are supported. struts.multipart.parser=Jakarta
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
A comma separated list of VelocityContext class names to chain to the StrutsVelocityContext. struts.velocity.toolboxlocation
The HTTP port number to be used when building URLs. struts.url.https.port = 443
Indicates if workaround for applications that don't handle HttpServletRequest.getParameterMap() should be enabled. struts.freemarker.manager.classname
The FreeMarker Manager class to be used. It must be a child of org.apache.struts2.views.freemarker.FreemarkerManager. struts.xslt.nocache = false
Specifies if the XSLTResult class should use stylesheet caching. struts.configuration.files = struts-default.xml,struts-plugin.xml,struts.xml
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}
It is also common to concatenate two expressions. A sequence of expressions will be evaluated from left to right, coerced to Strings, and concatenated. If a+b equals 8 and c+d equals 10, the following two expressions produce 810: ${a+b}${c+d}
And ${a+b}some$c+d} results in 8some10text. If an EL expression is used in an attribute value of a custom tag, the expression will be evaluated and the resulting string coerced to the attribute's expected type: <my:tag someAttribute="${expression}"/>
The ${ sequence of characters denotes the beginning of an EL expression. If you want to send the literal ${ instead, you need to escape the first character: \${.
Reserved Words
The following words are reserved and must not be used as identifiers:
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
Description The javax.servlet.jsp.PageContext object for the current JSP. A Map containing all context initialization parameters with the parameter names as the keys. A Map containing all request parameters with the parameters names as the keys. The value for each key is the first parameter value of the specified name. Therefore, if there are two request parameters with the same name, only the first can be retrieved using the param object. For accessing all parameter values that share the same name, use the params object instead.
param
Object paramValues
Description A Map containing all request parameters with the parameter 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. 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. 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. 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.
header
headerValues
cookie
applicationScope A Map that contains all attributes in the ServletContext object with the attribute names as the keys. sessionScope A Map that contains all the attributes in the HttpSession object in which the attribute names are the keys. A Map that contains all the attributes in the current HttpServletRequest object with the attribute names as the keys. A Map that contains all attributes with the page scope. The attributes' names are the keys of the Map.
requestScope
pageScope
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
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}
To obtain the path value of the jsessionid cookie, use this: ${cookie.jsessionid.path}
The sessionScope, requestScope, and pageScope implicit objects are similar to applicationScope. However, the scopes are session, request, and page, respectively.
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: equality (== and eq) non-equality (!= and ne) greater than (> and gt) greater than or equal to (>= and ge) less than (< and lt) less than or equal to (<= and le)
For instance, the expression ${3==4} returns false, and ${"b"<"d"} returns true.
Logical Operators
Here is the list of logical operators: AND (&& and and) OR (| | and or) NOT (! and not)
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:
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"); } As an example, the SuppressWarningsTest class in Listing C.4 uses the SuppressWarnings annotation type to prevent the compiler from issuing unchecked and fallthrough warnings.
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: SOURCE. Annotations are to be discarded by the Java compiler. CLASS. Annotations are to be recorded in the class file but not be retained by the JVM. This is the default value. RUNTIME. Annotations are to be retained by the JVM so you can query them using reflection.
For example, the declaration of the SuppressWarnings annotation type is annotated @Retention with the value of SOURCE. @Retention(value=SOURCE) public @interface SuppressWarnings
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: ANNOTATION_TYPE. The annotated annotation type can be used to annotate annotation type declaration. CONSTRUCTOR. The annotated annotation type can be used to annotate constructor declaration. FIELD. The annotated annotation type can be used to annotate field declaration. LOCAL_VARIABLE. The annotated annotation type can be used to annotate local variable declaration. METHOD. The annotated annotation type can be used to annotate method declaration. PACKAGE. The annotated annotation type can be used to annotate package declarations. PARAMETER. The annotated annotation type can be used to annotate parameter declarations. TYPE. The annotated annotation type can be used to annotate type declarations.
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})
By default, all annotation types implicitly or explicitly extend the java.lang.annotation.Annotation interface. In addition, even though you can extend an annotation type, its subtype is not treated as an annotation type.
Returns this element's annotation for the specified annotation type, if present. Otherwise, returns null. public java.lang.annotation.Annotation[] getAnnotations()
Returns true if this class is an annotation type. public boolean isAnnotationPresent(Class<? extends java.lang.annotation.Annotation> annotationClass)
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.
Test1. Author:John Guddell Test2. Author:John Guddell Test3. Author:Lesley Nielsen CustomAnnotationTest. Author unknown