Вы находитесь на странице: 1из 16

Advanced Struts Topics

In the previous chapter, we were able to tackle the basics of Struts. We have learned how to include the Struts framework in our application by configuring the provided ActionServlet to handle our requests. Also, we have learned how to create instances of Action classes that serve as action handlers for form submissions and other user requests. We have seen how to create ActionForm classes that provide an easy way of transferring data from our forms into the designated ActionHandlers. Finally, we have seen how to use the included tag libraries to help tie in the HTML forms in our JSP pages more firmly into the framework. In this chapter, we will cover some advanced techniques and features of the Struts framework. First, we will see how we can make use of DynaActionForms to minimize the number of classes we need to do for the framework. Next, we'll look at how the Validator framework eases the task of providing server-side validation into our application. And finally, we'll be introduced to the Tiles framework, with which we can compartmentalize our presentation layer, as well as providing access to a templating mechanism for our pages.

Especially in large applications, the number of classes that needs to be created and maintained can become staggeringly high. Struts support classes can contribute a lot to this number, especially with regards to its ActionForms, which require a solid implementation for nearly all the forms in an application. A lot of developers chafed under this restriction since ActionForms are mostly simple JavaBeans with get and set methods for each of the form fields it needs to represent. Struts came up with a solution in its 1.1 version release, called DynaActionForms. DynaActionForms behave exactly like ActionForms, in that an instance of it can be obtained and its methods called upon by the Action handlers that need its data. The main difference is, each DynaActionForm is not defined or declared as a separate class. A DynaActionForm is simply configured from within the struts-config.xml file. Below is an example of how to configure and declare DynaActionForms. We have made use of our example in the previous chapter, creating here an ActionForm that will handle the data required for logging in.

<?xml version=1.0?> <!DOCTYPE struts-config PUBLIC -//Apache Software Foundation//DTD Struts Configuration 1.1//EN http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd > <struts-config> <form-beans> <form-bean name=loginForm type="org.apache.struts.action.DynaActionForm"> <form-property name=loginName type=java.lang.String/> <form-property name=password type=java.lang.String/> </form-bean> </form-beans> <action-mappings> <action name=loginForm path=/login scope=request type=login.LoginAction> <forward name=success path=/success.jsp/> <forward name=failure path=/failure.jsp/> </action> </action-mappings> </struts-config> As we can see, creating a DynaActionForm is very simple. In some cases declaring a DynaActionForm from within the configuration file is simpler and faster compared to writing an actual ActionForm instance. We no longer have to list down all of the form properties and create get and set methods for each of them. With DynaActionForms, we simply declare the property name and type. It is then the framework's responsibility to provide a working instance based on this information. Providing this configuration information is the only thing necessary to make use of DynaActionForms. There are no changes that need to be made to any of our Action instances. As far as they are concerned, they still have a valid ActionForm instance that they can retrieve data from. The following are the Java types supported by DynaActionForm:

java.lang.BigDecimal java.lang.BigInteger boolean and java.lang.Boolean char and java.lang.Character double and java.lang.Double float and java.lang.Float int and java.lang.Integer long and java.lang.Long short and java.lang.Short java.lang.String java.lang.Date java.lang.Time java.sql.TimeStamp

While using DynaActionForms may be more convenient, they are not always the best solution. There are still cases where using ActionForms are more appropriate.

DynaActionForms only support a limited set of Java types. If our form needs to store information expressed as a data type other than those supported, then ordinary ActionForms are still the way to go. An example of this would be if we were to use custom Java objects as form properties. DynaActionForms do not support the concept of inheritance. There is no way to create a base definition for a form that can be extended later on to provide for specifics.

Validation is an activity that should be performed for all cases of data input. By validation, we mean the checking of the correctness of the format and content of usergiven values. Users, after all, do not always enter correct input: letters might be entered into a numeric only field and vice-versa; a field that requires 3 digits is given only 2, and so on. It is the application's responsibility to be aware of and handle such input errors aside from any errors resulting from processing of business logic (password does not match for given login, etc.). Struts alleviates the developer's burden in performing this validation by providing a validation framework called the Validator Framework. Usage of this framework provides a couple of benefits:

The framework provides several pre-defined validation rules. There are common set of checks performed in any number of applications such as format checking, length checking, checking for existence, etc. The framework provides the components necessary such that developers no longer need to create code that will perform these types of validation. Generally, the components the framework provides are enough for most applications, though custom validators can also be created. It eliminates redundancy in validation code. The framework separates the components performing the validation from the components needing validation. Instead of having multiple components incorporating the same validation code, the functionality can be externalized into a separate validation component that can be reused throughout the whole application. It provides a single point of maintenance. Developers no longer need to go all over their application to check on the validation rules that they enforce on their various components. All such rules are declared in configuration files provided by the framework.

There are several steps required in including Validator functionality within an existing Struts application:

Configure the Validator Plug-in. Declare the forms requiring validation and the type of validation they require. Create the messages that will be displayed in case of validation failure. Modify the struts-config file to enable automatic validation.

Configuring the Validator Plug-In

This step is required to make the Struts framework aware of our usage of the Validator framework. All that needs to be done is to add a few lines into our struts-config.xml file. Below is a sample: ... <forward name=success path=/success.jsp/> <forward name=failure path=/failure.jsp/> </action> </action-mappings> <plug-in className="org.apache.struts.validator.ValidatorPlugIn"> <set-property property="pathnames" value="/WEB-INF/validator-rules.xml, /WEB-INF/validation.xml"/> </plug-in> </struts-config> The pathnames property informs the framework where it can find the configuration files that it needs. There are two configuration files: validator-rules.xml and validator.xml.

This configuration file defines the classes implementing the validation code. The framework comes with a copy of this file with the pre-defined validator classes already configured. The following is a list of the logical names of the validators that came shipped in with the framework:

required properties marked as required must have at least one character aside from any whitespace. mask properties subjected to the mask validator must match the regular expression that we submit using the mask parameter. minlength if applied to a property, the property must have a length equal to or greater than the value of the min parameter that we pass. maxlength - if applied to a property, the property must have a length equal to or less than the value of the max parameter that we pass. range - the property must fall in between the min and max values that we supply using the min and max parameter byte, short, integer, long, float, double the property must be convertible to the specified primitive type. date succeeds if the value of the property is a valid date. creditCard succeeds if the value of the property can be a valid credit card number. email succeeds if the value of the property can be a valid email address.

These predefined validators are enough for most validation purposes. In cases where your application's validation requirements cannot be met by these validators, custom

implementations can be created, and their definitions added to the validator-rules.xml file. The creation of custom validator components is an advanced topic and will not be covered in this course.

This configuration file declares to the framework which forms require validation and which validation rules it would like to implement. The framework only provides the structure of the file. Developers would have to configure this file themselves to avail of the framework's functionality. . Configuring the validation.xml file The validation.xml file provides a structure which we can use to specify our validation configuration. The following are the XML elements that it defines: <formset> The root element of this configuration file. <form> Each form in our application that requires validation should have a corresponding <form> element. The name attribute in this case should map to the name of a formbean configured in struts-config.xml. This element can contain one or more <field> elements. <field> Each field element represents a property in the form that requires validation. This element has the following attributes:

property the name of the property within the form that will be validated. depends the comma-separated list of the validators that will be applied to the property. The names of the validators are defined within the validator-rules.xml file.

<arg0> Defines the key to the error message that will be displayed in case the property does not pass validation rules. The key and the actual error message that will be displayed can be found in a resource bundle that the developer should create. <var> Some validators require certain values to be passed to them as parameters so that they can function properly. These parameters can be supplied by using one or more var elements, with each var element corresponding to a parameter given to the validator. This element defines two child elements:

<var-name> - defines the name of the parameter to be supplied. <var-value> - defines the value of the parameter.

An example of such configuration file is provided below.

<formset> <form name=loginForm> <field property=loginName depends=required> <arg0 key=error.loginname.required/> </field> <field property=password depends=required,minlength/> <arg0 key=error.password.required/> <var> <var-name>min</var-name> <var-value>4</var-value> </var> </field> </form> </formset> The sample file provided configures the loginForm used in our earlier examples. loginForm here directly corresponds to an ActionForm that is defined within the struts configuration file under the name loginForm. This was expressed by setting loginForm as the value to the name attribute of the <form> element. After the <form> element, we find that there are two <field> elements defined. The first <field> element configures the loginName property of our form, the second configures the password property. We can determine which is which by looking at the value of the property attribute.

Defining the resource bundle

The <arg0> element above defined a key that needs to be matched to an entry in a resource bundle. This entry is then used to determine the message that will be displayed to the user. The Validator framework makes use of the same resource bundle that the Struts framework uses, which, by default, can be found in the WEB-INF/classes directory under the name ApplicationResources.properties. If such a resource bundle does not currently exist in your application, create a text file with that name. Entries in the resource bundle are simple key=value pairs. For the configuration example above, we can have the following content: error.loginname.required=Please enter your login name error.password.required=You have supplied a blank password or a password with less than 4 characters The last step in enabling the validation framework is to change our ActionForm and DynaActionForm classes to make use of the classes provided. Making use of our earlier DynaActionForm example:

<?xml version=1.0?> <!DOCTYPE struts-config PUBLIC -//Apache Software Foundation//DTD Struts Configuration 1.1//EN http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd > <struts-config> <form-beans> <form-bean name=loginForm type="org.apache.struts.validator.DynaValidatorForm"> <form-property name=loginName type=java.lang.String/> <form-property name=password type=java.lang.String/> </form-bean> </form-beans> <action-mappings> <action name=loginForm path=/login scope=request type=login.LoginAction validation=true> <forward name=success path=/success.jsp/> <forward name=failure path=/failure.jsp/> </action> </action-mappings> </struts-config>

Another framework that works especially well with Struts is the Tiles framework. Using Tiles, we can easily define templates and screen instances which we can use with our application.

What are templates?

Simply put, a template page is a page layout design that can be reused by any of the pages in your application. Making use of templates gives your application a more consistent look and feel. To better appreciate the concept of templates, let us take a look at a couple of pages from a web application:

Figure 1: Sample Page from a Web Application

Figure 2: Sample Page from a Web Application

Looking through the pages, we can see that there are several design elements common for all of them. All of them share the same set of navigational links on the left, the same set of images on top of the page, the same set of images on the links on the bottom of the page. Most of what has changed between the different pages is the content presented in the middle of the page. Think of how we would be able to implement a set of similar pages. If we were to implement each page as we see it, that is, exactly one page to correspond with each of the screens we can see, we would have trouble maintaining it in the future. That would be because there would be a lot of code that would be duplicated if later on someone decided that the images on top didn't really look that good or that the navigational menu on the left isn't intuitive enough, the changes would have to be manually propagated across all of the pages. Being the programmers that we are, we know enough to refactor into separate entities code that would be duplicated across the application. This concept can also be applied to JSP/HTML pages: the navigational links could be implemented as one page, the header images as another, the footer yet another. These pages could then be added into a page implementing the body content. This can be performed without the use of any framework, using the <jsp:include> action we have discussed in the basic JSP lecture.

A better solution would be to make the body content a separate JSP fragment itself, and to create a JSP page that defines the default formatting and layout of the pages, and simply leaves placeholders for the other elements that could be included into the page. A developer implementing a screen would then only have to make use of this "template" page and define a content page that the template would insert into the content placeholder. The benefits of this solution are obvious: any changes that would need to be performed on one aspect of the presentation layer, say the header page, would be applied to all of the pages while modifying only one page. If you wanted to change your site's layout, while still maintaining its content, it could be done by simply changing the template page. We will be learning how to do this templating using the Tiles framework.

Preparing Tiles
Before we can help ourselves to the benefits that the Tiles framework can give, we need to perform several preparatory steps. 1. The first thing that we need to do is to add the following lines to struts-config.xml, just before the closing </struts-config> element: <plug-in className="org.apache.struts.tiles.TilesPlugin" > <set-property property="definitions-config" value="/WEB-INF/tiles-defs.xml" /> </plug-in> This informs Struts that we want to make use of the Tiles framework, and that the configuration file that it would read can be found in the /WEB-INF directory, with the name tiles-defs.xml. 2. Next, we need to copy the struts-tiles.tld and tiles-config.dtd files from the Struts' lib directory into the /WEB-INF directory of our application. The first file contains information on the custom tags that we will need to use for Tiles, and the second defines the structure of the tiles-defs configuration file. 3. Create a blank configuration file that we can fill up later. Make an xml file, name it tiles-defs.xml and place it in the WEB-INF directory. Then place the following content: <!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration//EN" "http://jakarta.apache.org/struts/dtds/tiles-config.dtd"> <tiles-definitions> </tiles-definitions> After we have performed the previous steps, Tiles is ready for use.

Building a Layout template

The first step in building a template is to identify the components that that will be placed in it. For example, most web pages have a header, footer, menu bar, and body as components. If we were to draw a quick sketch, such a layout might look like the one in the diagram below.

Figure 3: Basic layout

To create a template page implementing this layout, follow these steps: 1. Create a new JSP page. 2. Import the Tiles tag library. 3. Create the HTML implementing the layout, leaving as blank the implementation for the actual components. 4. Use the <tiles:insert> tag to act as placeholders for the layout components. The HTML implementing the layout could be as simple as a table, with the components modeled as table cells as shown in the JSP below:

<%@ taglib uri="http://jakarta.apache.org/struts/tags-tiles" prefix="tiles" %> <HTML> <HEAD> <TITLE><tiles:getAsString name="title"/></TITLE> </HEAD> <BODY> <TABLE border="0" width="100%" cellspacing="5"> <TR> <TD colspan="2"><tiles:insert attribute="header"/></TD> </TR> <TR> <TD width="140" valign="top"> <tiles:insert attribute="menu"/> </TD> <TD valign="top" align="left"> <tiles:insert attribute="body"/> </TD> </TR> <TR> <TD colspan="2"> <tiles:insert attribute="footer" /> </TD> </TR> </TABLE> </BODY> </HTML> There are two custom tags in the above sample: <tiles:insert> and <tiles:getAsString>.

<tiles:insert> - has a number of uses in the Tiles framework. In this case, what it does is to insert a JSP fragment referred to by the name supplied in the attribute attribute. Which fragment corresponds to the name will be specified in the screen definition that will make use of this template. <tiles:getAsString> - this tag simply retrieves as a String a value supplied in a screen definition using the name in the name attribute.

Creating Screen Definitions

Once we have a template, we can make use of it to wholly define a screen. Creating screen definitions can be done in two ways within the Tiles framework: the definitions can be defined within JSP pages, or within an XML file recognized by the framework. . Creating a definition using JSP pages Creating a definition within a JSP page makes use of more custom tags supplied by Tiles:

<tiles:definition> - base tag with which to define a screen. The value of the id attribute is the name by which this definition can be referred to by other components. The value of the page attribute is the location of the template file it will use as its base.

<tiles:put> - this tag is used to place a value into a specified attribute inside a template. The name attribute specifies the name of the target location inside the template. The value attribute defines the location of the JSP fragment to be used.

Consider the example below: <%@ taglib uri="http://jakarta.apache.org/struts/tags-tiles" prefix="tiles" %> <tiles:definition id="welcomePage" page="/layout/basicLayout.jsp"> <tiles:put name="title" value="Welcome!"/> <tiles:put name="header" value="/header.jsp"/> <tiles:put name="footer" value="/footer.jsp"/> <tiles:put name="menu" value="/menu.jsp"/> <tiles:put name="body" value="/welcome.jsp"/> </tiles:definition> In this sample, we create a screen definition for a welcome page. It makes use of the layout we defined earlier and places the specified title, header, footer, menu, and body components into locations defined in the template. By default, this definition is visible only within the JSP page containing the declaration. To change this, we can supply a value into the optional scope attribute of the <tiles:definition> tag. The possible values are: page, request, session, and application. . Creating a definition using an XML configuration file The second way of creating a screen definition is by placing a configuration entry in the tiles-defs.xml configuration file that we initially created. The syntax in creating an entry is very similar to the <tiles:definition> used earlier: <!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/tiles-config_1_1.dtd"> <tiles-definitions> <definition name="welcomePage" page="/layout/basicLayout.jsp"> <put name="title" value="Welcome!"/> <put name="header" value="/header.jsp"/> <put name="footer" value="/footer.jsp"/> <put name="menu" value="/menu.jsp"/> <put name="body" value="/welcome.jsp"/> </definition> <!-- ... more definitions ... --> </tiles-definitions> What is the difference between the two methods? Both methods load the definition as a JavaBean into memory. The JSP method though, loads the definition only after the JSP fragment containing it has been accessed and, by default, makes it visible only in the same page. This condition makes reuse of the screen definition throughout the application a bit more problematic, since extra caution must be taken to avoid loading and unloading the definition into and from memory unnecessarily. The XML method, on the other hand, makes the screen definition available to the whole

application immediately after startup. The Tiles framework takes care of persisting them in memory. The only thing that components require to make use of the definition is to supply its name. Another benefit of using the XML method for creating the screen definition is that the definitions themselves can be used as ActionForwards by the Struts framework. This can drastically cut down the number of JSP files required by our application. Using definitions as ActionForwards is a topic that will be discussed in more detail later. . Using the screen definitions Creating the screen definition is not enough for it to be displayed to the user. To put a Definition into use, we can use the <tiles:insert> tag and supply it the name of the definition to display: <%@ taglib uri="http://jakarta.apache.org/struts/tags-tiles" prefix="tiles" %> <tiles:insert beanName="welcomePage" flush="true"/> The above page is all that is needed to display the welcome screen to the user. One of the problems with this approach is that it increases the number of JSPs required to display different screens to the user: aside from a body content component, each screen would require a separate page that would make use of the screen definition. For applications that requires a 100 or so screens, the number of pages that would need to be created is doubled! A better approach would be to make use of the definitions as the targets of ActionForwards. By including the Tiles framework as a plugin for Struts (one of the preparatory steps we performed), Struts is made aware of the screen definitions created using Tiles. The screen names can be used in place of actual locations in <forward> tags. Consider the example below: <action-mappings> <action name=loginForm path=/login scope=request type=login.LoginAction validation=true> <forward name=success path=/welcomePage/> <forward name=failure path=/failure.jsp/> </action> </action-mappings>

Here we have modified our earlier sample of struts-config.xml so that the success logical condition is now mapped to our welcomePage definition. As we can see, this approach is simple, easy to use, and cuts down on the number of JSP files that needs to be created. . Extending definitions One of the strengths of the Tiles framework over other templating methods is that it

allows the extension of screen definitions. Screen extension works in much the same way as class inheritance in Java: the extended screen inherits all the properties and attributes of the parent Definition. This allows us to create a base definition screen that declares default values for attributes that can be extended by Definitions specialized for specific pages. Take the following example: <definition name="basicDefinition" page="/layout/basicLayout.jsp"> <put name="header" value="/header.jsp"/> <put name="footer" value="/footer.jsp"/> <put name="menu" value="/menu.jsp"/> </definition> <definition name="welcomePage" extends="basicDefinition"> <put name="title" value="Welcome!"/> <put name="body" value="/welcome.jsp"/> </definition> <definition name="otherPage" extends="basicDefinition"> <put name="title" value="My title"/> <put name="body" value="/otherContent.jsp"/> </definition> By creating a base screen that can simply be extended, we avoid repetition of attribute value definition. Also, when a change needs to be made with regards to which component is placed in one of the base attributes, this change is immediately and automatically propagated to all of the screens by simply making the change on the base screen definition. EXERCISES The exercise of this chapter will build on the exercise introduced in the earlier chapter. Students should form the same groupings as done in the last exercise. 1) Using the Validator framework, include validation code for all the forms in the previous exercise. -- Download Material Management -Add Download Material form -> Update Download Material form -> Delete Download Material form -> Input is required, must be of integer format. -- User Management -Add User form -> Update User form -> Delete User form -> input is required, must be of integer format. 2) Convert all ActionForms used in the previous exercise into its DynaActionForm equivalent. 3) Implement the screens defined in the previous exercise using the Tiles framework. The layout to be used is the basic layout defined earlier in this chapter. This activity

consists of several tasks: a) Separate the actual content previously created for each page into separate "body" pages. b) Create side bar pages consisting of a list of links applicable for each subsection of the application. For example, if the user is an administrator and is in the Main Content Page, he would be able to see links to the Download Material Management page, User Management page, and User Activity page, as well as a link that he could click to logout of the application. In general, the side bars should be implemented such that they: - Present a link to its parent page as defined in the screen flow definition in the previous exercise. - Present links to the next set of pages that can be accessed according to the same screen flow. - Present a link that will enable the user to log out of the application. c) Create Tiles definitions for each of the screens in the application and use them instead of ActionForwards.