Chapter 3. Actions and ActionSupport

In the previous chapter, we had a crash course on how to configure our Struts 2 application and got a (very) small taste of Struts 2 actions—the place where our application does its work. Even if the bulk of our application's functionality resides in service objects, actions are where the service objects are instantiated and used.

In this chapter, we'll examine Struts 2 actions further. While the mantra of "actions are POJOs" echoes in our heads, extending the ActionSupport class provides us with quite a bit of functionality, including internationalization (I18N), validation, and so on.

Much of this functionality is provided by a combination of interface implementations and interceptors. We'll cover ActionSupport, a utility class implementing most of the commonly used interfaces, as well as a few other handy interfaces. We'll see how we can get a lot out of the framework, without covering how some of the functionality is actually implemented.

We'll implement a prototype of our first user story, entering a recipe. We'll also take a quick detour into some ways of exploring code, including the usefulness of Unix-like command line utilities, and how the ability to create ad hoc tools can really be handy.

ActionSupport and its interfaces

ActionSupport is an XWork class that provides default implementations of validation and I18N interfaces. Since many actions require both, it makes a convenient "default" action base class. It's not required for our actions to subclass ActionSupport, but it's handy.

ActionSupport implements the following six interfaces:

  • Action

  • Validateable

  • ValidationAware

  • TextProvider

  • LocaleProvider

  • Serializable

We'll take a brief look at each of these except Serializable (it's boring, and is already familiar to Java developers).

The Action interface

The Action interface defines a single method:

public String execute();

One benefit of implementing Action is that reflection isn't required to check for the execute() method. We can just cast to Action and call execute().

Action's convenience strings

ActionSupport, via the Action interface, exposes several typical result name strings. For example, we've already seen actions returning "success" and "error". ActionSupport defines these as SUCCESS and ERROR respectively. It also defines INPUT, LOGIN, and NONE. Each of these has the value we'd expect.

NONE is a bit different from the rest. It is used when no view is shown by the framework, as when we write directly to the response.

The TextProvider interface

I18N support is provided by the TextProvider implementation along with the LocaleProvider (discussed later in this chapter). The TextProvider interface provides access to standard Java ResourceBundles and the messages defined in them. Ultimately, TextProvider provides two methods:

  • getText()which provides an access to a single message

  • getTexts() which returns an entire ResourceBundle

Both the listed methods have several method signatures. We'll focus on the many variants of getText().

The getText() method comes in several flavors. The simplest is the String getText(String key)method, which retrieves the named message from a resource file. Messages are retrieved in the hierarchical fashion. Messages are retrieved in the following order:

  1. ${actionClassName}.properties.

  2. ${baseClass(es)}.properties.

  3. ${interface(es)}.properties.

  4. ModelDriven's model, if action implements ModelDriven (discussed later). Steps 1-3 are performed for the model class. However, we'll ignore this feature for now.

  5. package.properties (and any parent package's package.properties).

  6. I18N message key hierarchy.

  7. Global resource properties.

To demonstrate the process of locating messages, we'll create the following package and file hierarchy:

/com/packt/s2wad/ch03/i18n/
/com/packt/s2wad/ch03/i18n/package.properties
/com/packt/s2wad/ch03/i18n/TextExampleAction.java
/com/packt/s2wad/ch03/i18n/TextExampleAction.properties
/com/packt/s2wad/ch03/i18n/TextInterface.java
/com/packt/s2wad/ch03/i18n/TextInterface.properties
/com/packt/s2wad/ch03/i18n/sub/
/com/packt/s2wad/ch03/i18n/sub/package.properties
/com/packt/s2wad/ch03/i18n/sub/TextExampleSubAction.java
/com/packt/s2wad/ch03/i18n/sub/TextExampleSubAction.properties

We see a Java package containing package.properties, an action class (TextExamplesAction), and an interface (TextInterface). Each has a corresponding property file. TextExamplesAction extends ActionSupport and implements TextInterface. We have a subpackage with its own package.properties file. The subpackage also contains an action class (TextExamplesSubAction) extending TextExamplesAction with its own property file.

Our first sanity check will be simple. We'll test our TextExamplesAction class by looking up a message from the TextExamplesAction.properties file. From the list we saw earlier, Struts 2 will look for messages in a property file named after the class.

We'll use the Convention plug-in again, avoiding XML. The action will log the results of a getText() call. TextExampleAction.properties contains a single entry:

from.class.props=Hello from TextExamples.properties!

As we're using the Convention plug-in, we need to create a JSP file for our Convention-based action, else Struts 2 will give us an error. For now, we create a dummy JSP file in /WEB-INF/jsps/i18n/text-example.jsp. The expurgated version (it's the one without the gannet) of our action looks like this:

package com.packt.s2wad.ch03.actions.i18n;
public class TextExamplesAction extends ActionSupport
implements TextInterface {
private static Log log =
LogFactory.getLog(TextExamplesAction.class);
public String execute() throws Exception {
log.debug(getText("from.class.props"));
return SUCCESS;
}
}

When we access our action at /i18n/text-examples, we should see something resembling the following (assuming we're using the log4j.properties contained in the chapter's project):

DEBUG com.packt.s2wad.ch03.actions.i18n.TextExamplesAction.execute: 15 - Hello from TextExamples.properties!

If no message is found for a given key, getText() will return null.

To avoid going to the console to check our output, let's figure out a way to display our messages on our JSP page. Our first detour serves two purposes:

  • It provides a very brief introduction to how Struts 2 exposes values to our JSPs

  • It gives us another acronym to add to our growing collection

Detour—action properties, JSPs, and more tags

Traditionally, accessing application data from our JSP page would involve putting a value into either request or session scope. Some frameworks handle it more automatically, for example, Struts 1 would put an ActionForm into either request or session scope.

Struts 2 works in a similar fashion. However, Struts 2 simply puts the entire action into scope. (It's actually a bit more complex. The action is actually pushed onto a stack that's in scope, but we'll cover that later.) We then access action properties using Struts 2 custom tags and OGNL (Object Graph Navigation Language), another expression language similar to JSP EL. Another option is to use JSP EL (thanks to Struts 2's custom request wrapper).

Note

Using the Struts 2 custom tags and OGNL gives us another advantage. We can access action methods, including those that take parameters, directly from our JSP. This is in contrast to JSP EL, which allows access only to properties (although access to functions may appear in JEE 6).

Right now, we just want to display localized messages on our JSP page. As mentioned earlier, we can call action methods from our JSP using Struts 2 tags and OGNL. One of the custom tags is <s:property>, which is the most basic way to access action properties (and methods). We specify the action property (or method) with the value attribute.

Our stub JSP, which is no longer a stub, will contain the following (the remaining examples in this file will just show the relevant portions):

<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<body>
<dl>
<dt>From TextExamplesAction.properties via property tag,
using getText(...):</dt>
<dd>
<s:property value="getText('from.class.props')"/>
</dd>
</dl>
</body>
</html>

This JSP will produce output similar to our console output, but in the browser:

Detour&mdash;action properties, JSPs, and more tags

This is a little unwieldy. Being lazy programmers, we want to minimize typing as much as possible. As we have access to action properties as well as methods, we could simply put the results of the getText() call into an action property.

Action properties are exposed via public getter methods:

public String getText1() {
return getText("from.class.props");
}

Now, we can access the text in our JSP via the text1 action property:

<s:property value="text1"/>

Note

Struts 2.1 doesn't actually require a public getter method if we declare the property itself public. OGNL will access public properties without a getter. Whether or not this is a good practice is a debatable issue. The biggest drawback of not following the JavaBean specification is that not all tools or libraries will be as forgiving as OGNL.

Finally, as mentioned, we can access the property via JSP EL. This is achieved using the simplified property access notation introduced in JSP 2.0, reducing the JSP code to a terse:

${text1}

Note

Struts 2 wraps requests in a StrutsRequestWrapper. This class is responsible for accessing scoped attributes. StrutsRequestWrapper will check for properties on the Struts 2 value stack first. If no property is found in the stack, it will defer to the default request wrapper, which searches the normal JEE scopes.

Even with the JSP EL shortcut, this is still a fair amount of work, and it doesn't seem reasonable that we have to write Java code just to look up a message. Struts 2 provides an<s:text> tag, meaning more work in our JSP, but less in our Java.

<s:text name="from.class.props"/>

To protect against missing messages, we can use<s:text> tag's body to provide a default message value:

<s:text name="an.unlikely.message.name">
Default message if none found
</s:text>

This is similar to the functionality provided by the getText(String, String) method.

Which method we use is largely a matter of preference. The<s:text> tag is arguably the cleanest, as it's short and it's all in one file.

Continuing with message lookup

Next on the list of places to look for messages are the superclass property files. Our TextExampleSubAction class extends TextExampleAction. Hence, messages in TextExampleAction.properties are available to TextExampleSubAction. Messages defined in both files will resolve to the more specific, giving us the ability to override messages for subclasses, just like we can override class functionality.

To demonstrate, we'll add a message under the key overridden.message to both TextExampleAction.properties and TextExampleSubAction.properties. However, the values will be different (you can guess which file contains the following).

overridden.message=I am from TextExampleSub.properties

We'll add a property and value for the same overridden key to TextExampleAction.properties, only in the superclass property file:

overridden.message=I am from TextExample.properties
superclass.message=Who's your superclass?

We'll add some<s:text> tags to a new JSP for the subclass, and to our superclass JSP:

<s:text name="overridden.message"/>
<s:text name="superclass.message"/>

When we visit /i18n/sub/text-examples-sub, we'll see the overridden message from the subclass property file, and also the message defined in the superclass's property file.

Messages may also be placed in interface property files, following class and package hierarchy. Similarly, messages placed in a Java package's package.properties file are available to any class contained in that package. What might be slightly less obvious is that Struts 2 will continue searching up the package hierarchy for package.properties files until either the message is found or Struts 2 runs out of packages to search.

If we have application-wide message resources, and our actions are not arranged under a single package, we can create and define a global resources bundle. We add an initialization parameter to our web.xml file, which defines two more message resource files:

<init-param>
<param-name>struts.custom.i18n.resources</param-name>
<param-value>
ch03-global-messages,
com/packt/s2wad/ch03/misc/misc-messages
</param-value>
</init-param>

These resource files would also be used to resolve message keys. Multiple files, separated by commas, can be defined, and these files may reside anywhere in the class hierarchy.

Parameterized messages

Messages may also be parameterized via the getText(String, List) and getText(String, String[]) methods. As with getText(String), there are also signatures that take a default message (see the API documentation for details).

Parameterized messages take positional parameters. For example, we'll put the following message that takes two parameters into a package.properties file in the same package as the TextExamplesAction class:

params.msg1=You passed {0} and {1}.

The {0} and {1} placeholders are filled with the values passed in the parameter list. Calling from Java looks exactly like we'd expect.

public String getParams1() {
return getText("params.msg1", new ArrayList() {{
add("Foooo!");
add("Baaar!");
}});
}

Note

Funky Java Alert: The code following the ArrayList instantiation is normal Java code, although it doesn't look like it. Code like this drives people crazy, and there are situations in which it's wholly inappropriate. However, it is a useful trick to know, and it's not really much of a trick. It's left as an exercise for the reader (including determining when, and why, it might not be appropriate!).

We can also parameterize the text tag in several ways using the<s:param> tag. The first way is to just put the parameter values in the tag body:

<s:text name="params.msg1">
<s:param>Fooooo!</s:param>
<s:param>Baaaar!</s:param>
</s:text>

We can also use the param tag's value attribute and pass in a string. It's very important to note the extra quotes around the value:

<s:text name="params.msg1">
<s:param value="'Foooo!'"/> <!-- See the extra quotes? -->
<s:param value="'Baaar!'"/> <!-- Don't forget them! -->
</s:text>

The value attribute is an object, not an immediate value. In other words, the contents of the value attribute are evaluated. If we left the quotes off, we'd see null in our output and a warning in our log file (if devMode is turned on).

This gives us a handy way to include action properties in our messages. If we add action properties named foo and bar to our TextExamplesAction class, initialized to "Foooo!" and "Baaar!" respectively, we could write the following:

<s:text name="params.msg1">
<s:param value="foo"/>
<s:param value="bar"/>
</s:text>

Given the previous code fragments and assumptions, visiting /i18n/text-examples will produce the same output as the text tags using the immediate values shown earlier.

Our final example of parameterized messages is pretty cool (as cool as parameterized I18N message can be) In addition to providing message parameters using a list (or array) as shown above, we can access action properties directly in our messages.

Instead of using the positional {n} syntax, we can access action properties using the ${...} construct, the same syntax as the param tags. For example:

params.from.action=From action: ${foo}, ${bar}... wow!

The text tag to access this message is as we'd expect:

<s:text name="params.from.action"/>

Our action's foo and bar properties will be inserted into the message with no additional work on our part, saving us some JSP code. The expressions allowed in the property file are the same as those allowed in the JSP. This means that we could call action methods, including those that take arguments. This ability may not be useful all the time, but it can often save some irritating Java and/or JSP code on occasion.

The LocaleProvider interface

LocaleProvider allows TextProvider to get locale information and look up resources based on Java property file names. LocaleProvider provides one method, getLocale(). The default locale value comes from the browser via the I18N interceptor. By overriding this method we could, for example, return a user's preferred locale from a database.

The Validateable and ValidationAware interfaces

Discussing the complete functionality of Validateable and ValidationAware at this point is problematic. Much of the functionality depends on several Struts 2 custom tags. We'll defer those details to Chapter 6. Here we'll learn only the minimum necessary.

Validateable contains a single method, void validate(). ActionSupport contains a default implementation that allows for both XML-based and/or annotation-based configurations.

We can implement our own validate method in our actions and manually perform validation . We can include default framework validation by calling super.validate(). Combining XML- or annotation-driven validation with manual validation (including "business-logic level" validation), along with the ability to define our own custom validators (covered later), covers all of our validation needs.

ValidationAware provides a group of methods used to collect error messages related to specific form fields or the action in general. It is also used to collect general informational messages and determine if any errors or messages are present.

These two interfaces work together with the default Struts 2 interceptor stack (specifically the "validation" and "workflow" interceptors) to validate forms and direct application flow based on validation results (specifically the presence of error messages). In a nutshell, if the validation succeeds, the appropriate action method is executed normally. If the validation fails, we're returned to the "input" result.

Implementing our first user story

We actually know enough at this point to prototype one of our user stories. We'll implement this story by hand as a useful exercise, and to help build appreciation for the framework (and reduce your appreciation for your author&mdash;however, I can take it!)

One of our user stories, as you can recall, was the following:

  • Users can enter recipes to share

The followers of more "traditional" requirement methodologies will be nothing less than horrified by this minimal description of a functional requirement. A traditional version of this story could run across a dozen pages, capturing (or attempting to capture) every nuance of the concept of "recipe entry" including the user interface design, complex validation rules, every possible form field, the underlying data model, and so on.

Once this (portion of the) specification is printed, and the toner has cooled, it will invariably be wrong. Something will have been forgotten. An edge case will have been overlooked. Half a dozen questions about the user interface will surface, all without having written any code or having used even the most minimal of recipe entry systems.

By minimizing functionality, we can very quickly create a low-fidelity prototype, allowing us to vet the basics of our system. While it's not a finished product, it's a way of determining what the finished product will be. Using our prototype can help determine user interface design decisions, spot overly-complex application flow, highlight missing form fields, and so on.

And really, that single sentence does capture the essence of what we need to do, if not all the specifics.

Refining our story

Since we're still learning Struts 2 and prototyping our application, we can afford to make some low-fidelity (lo-fi) assumptions. We'll assume that a "recipe" has an "id", a "name", a "description", a list of "ingredients", and some "directions".

For now, each of these will be a string (except for an Integer id). Low-fidelity prototypes, especially early in the development cycle, don't need to reflect the final product with regards to looks, functionality, data models, and so on. Our Recipe class is simply a POJO with appropriately named properties. The only note of interest is that the Recipe class should have a toString() method, giving us a human-readable version of the object.

public String toString() {
return String.format("Recipe (%s) [id=%d, name=%s]",
super.toString(), id, name);
}

Note

Including the default toString() in our toString() methods lets us tell at a glance if two string representations are the same object. This isn't always appropriate, particularly if we'd rather use toString() output on a webpage. String.format() was a long overdue addition!

The last story refinement is that we require the name and the ingredients, but not the description and the directions. The form will have text inputs for the name and description, and text areas for the ingredients and description (remember&mdash;this is a lo-fi prototype).

Creating the recipe form

We'll now flesh out our "new recipe" JSP stub with some input tags. We'll be implementing this form by hand. If you're already familiar with the Struts 2 form tags, bear with me. Starting off on our own makes us much more appreciative of the framework.

Our recipe action will have a recipe property, which is the Recipe POJO described in the previous section. We'll put our form headers and input elements into a table. Here's a representative element, the recipe name (or at least what we have so far):

<tr>
<td>Name</td>
<td><input name="recipe.name" type="text" size="20"/></td>
</tr>

The most interesting thing (so far) is the name attribute. Struts 2 will do the right thing (in general) when encountering request parameters. It will attempt to convert them to the correct type and set the corresponding action property.

In this case, we have a nested object. The recipe property is of the type Recipe. The name property of the recipe is just a string. When we see a form element name, such as the recipe.name, what happens (more or less) is roughly equivalent to the following:

getCurrentAction().getRecipe().setName(value from form);

On top of that, Struts 2 will take care of instantiating a Recipe object if one doesn't exist. We can verify this functionality by creating a stub action to process form submissions. We'll call it NewRecipe (extending ActionSupport). However, for now, we'll just print the recipe to our standard output (hopefully), containing values from the form. Our stub action looks like this (getters and setters elided):

package com.packt.s2wad.ch03.actions.recipes;
public class NewRecipe extends ActionSupport {
private Recipe recipe;
public String execute() throws Exception {
System.out.println(recipe);
return SUCCESS;
}
}

This is just a sanity check to show that the recipe is, indeed, being instantiated and populated. However, at this time there's no real functionality, no results, and so on.

Note

This is another principle of agile development. Do one thing at a time, verify that it works, and then continue. Here, we're doing something similar to TDD (Test Driven Development). However, we're the testers. We'll cover TDD later.

The final piece of our current puzzle is the form tag in the JSP itself. We'll use a plain HTML form tag. As we're staying in our recipe package, and we're using Convention, we can use a relative action path. The form tag will look like this:

<form action="new-recipe" method="post">

Visiting the page at /recipes/new-recipe-input (following the naming conventions of the Convention plug-in) should show us the form. We fill the form using "Java Soup" for the recipe name (along with other details). Now, when we submit the form and look at the console, we should see something like this:

Recipe (com.packt.s2wad.ch03.models.Recipe@921fc7) [id=null, name=Java Soup, etc...])

As unassuming as this is, it's actually pretty cool. Struts 2 instantiated a Recipe object and filled it with values from our form without any intervention on our part. We don't need to grab values from the request parameters, or use ActionForm. It was just a model and an action.

Adding some validation

Now that we know we have a recipe object that can be validated, we'll do that. As our action extends ActionSupport (which implements Validateable), we can override the validate() method, which unsurprisingly performs validation. Inside the validate() method, we use ActionSupport's implementation of ValidationAware, which includes methods for adding form field errors, keyed by the input element's name.

Our action's validate() method implements our refined user story&mdash;name and ingredients are required, but not the description and the directions. We'll use Apache Commons Lang's StringUtils.isBlank() method. There's no point in creating yet another implementation of a null or whitespace check. (Some folks disagree, and their concerns that it's another dependency and adds a large API for just one method are understandable.)

Each blank field gets an error message, keyed to the name of the input element, and added to a collection of field errors using the ValidationAware's addFieldError(String fieldName, String message) method.

Our action's validation method is simple:

public void validate() {
if (StringUtils.isBlank(recipe.getName())) {
addFieldError("recipe.name",
getText("recipe.name.required"));
}
if (StringUtils.isBlank(recipe.getIngredients())) {
addFieldError("recipe.ingredients",
getText("recipe.ingredients.required"));
}
}

The validation process depends on interceptors. For now, it's enough to know that if there are any field errors, we'll be returned to our "input" result. We'll add results to our action, an "input" result for when the validation fails, and a "success" result for when it succeeds. Our "input" result will be the same JSP reached through the /recipes/new-recipe-input request, whereas our "success" result is a simple thank you page. We'll put the thank you page in /WEB-INF/content/recipes/ new-recipe-success.jsp.

If we navigate back to our form and try to submit an empty form, we'll find ourselves back on the input form. However, our URL will change from /recipes/new-recipe-input to /recipes/new-recipe, which is what we'd expect. Adding the required fields and resubmitting shows us our thank-you page. The validation portion of the story is working, but we don't see our validation errors displayed.

Displaying our error messages

ActionSupport provides an implementation of ValidationAware's getFieldErrors(), which returns a map of field errors keyed by field names. Each map entry is an array. We'll take a sneak peek at another Struts 2 tag,<s:if> and use it to determine whether an error message for a specific field exists or not.

We'll add a test to each field to see if an error message exists for that field. If the message exists, we'll display it in a span. For the name property, we end up with this:

<s:if test="getFieldErrors().get('recipe.name') != null">
<div class="error">
<s:property
value="getFieldErrors().get('recipe.name')[0]"/>
</div>
</s:if>
<input name="recipe.name" type="text" size="20"
value="<s:property value="recipe.name"/>" />

For now, we can assume there's only one error message for each field, as that's all our validate() method does. For multiple errors, we'd have to iterate over all messages. Also, we're using the<s:property> tag to reload the field's value. Hence, our form contents are restored when the validation fails.

A simple user story turned into a lot of work! You'll either be delighted, or irritated, to learn that we didn't have to do a large portion of what we just did, thanks to Struts 2's form tags. In fact, most of our JSPs will disappear. We'll cover these tags later.

More action interfaces

We've seen the interfaces supported by the ActionSupport class. Combining interfaces (implemented by an action) with interceptors (either the framework's or your own) is how Struts 2 implements the bulk of its functionality.

We can implement additional interfaces to get additional capabilities. We'll cover quite a few useful interfaces here, from both XWork and Struts 2, after a brief detour to explore... ways to explore.

Detour&mdash;creating the list of interfaces to explore

As a side note, reinforcing the idea of exploration and ad hoc tool creation, let's think for a moment about how to find information about the environment we're working in. I knew I wanted a list of interfaces from both XWork and Struts 2. However, if we look at the Javadocs for Struts 2 (which, by the way includes the XWork Javadocs), there's no quick way to get a list of all the interfaces.

Standard Javadocs indicate interfaces with italics in the classes frame (lower left by default). That's not a particularly efficient system discovery method, although it works. We could define our own stylesheet that highlights the interfaces, but that's a fair amount of work. We'll see a few ways (out of many available) to extract this kind of information.

Leveraging the IDE

If we're developing in an IDE, there's most likely some form of source or library browser available. For example, Eclipse has a Java Code Browser perspective. Selecting a package shows us a list of the defined types, an icon indicating the interface, class, and so on. We can also filter our view to show only interfaces.

We're currently interested in the interfaces provided by XWork. Browsing the Action interface shows us something similar to the following:

Leveraging the IDE

This is helpful. However, if we're trying to get an overview of the system, identify relationships, create printed documentation, and so on, it's not as helpful as it could be.

Using the command line

One quick-and-dirty way to get information like this from a source tree (if we have access to the source code, we can also examine the contents of a JAR file) is by using the grep command, available on all Unix-like systems (and Windows via Cygwin).

Note

If we're running Windows and not using Cygwin, we're selling ourselves short. Cygwin provides much of a Unix-like environment, including the Bash shell and all of the incredibly useful file and text processing tools such as find, grep, sort, sed, and so on. The importance of these tools cannot be over-emphasized, but I'll try. They're really, really important! Really! Honestly, they should be in the arsenal of every developer.

By chaining a few of these simple utilities together, we can create a list of interfaces defined in the source code. With some minor formatting, the output is a list of interfaces we can then explore via Javadocs or the source. The following is one way to get a list of all files in a source tree that declare an interface. Continuing our look at XWork, running the following command line will produce the terse, but useful output here:

$ find . -name "*.java" | xargs grep -l "public interface"
./com/opensymphony/xwork2/Action.java
./com/opensymphony/xwork2/ActionEventListener.java
./com/opensymphony/xwork2/ActionInvocation.java
./com/opensymphony/xwork2/ActionProxy.java
./com/opensymphony/xwork2/ActionProxyFactory.java
./com/opensymphony/xwork2/config/Configuration.java
./com/opensymphony/xwork2/config/ConfigurationProvider.java

Not the prettiest output, but it quickly provides a high-level view of the interfaces defined in the XWork source code without any actual effort. The argument to the grep command is a regular expression. Never underestimate the power of regular expressions. A mastery of regular expressions is an awesome timesaver in so many circumstances, from finding a file to doing incredibly complicated search-and-replace operations.

Repeating the same command in the Struts 2 source tree would generate a list of Struts 2 interfaces. Never underestimate the usefulness of the find/grep combination as a "first line of attack" tool set, particularly when we know regular expressions well.

Examining class files

The last method involves looking at Java class files, in this case, extracted from the XWork and Struts 2 core library files. From there, the ASM bytecode manipulation library is used to extract information about each interface that is defined in the class files.

Note

Bytecode manipulation sounds scary, but it's easier (and more useful!) than you might think. There's a good chance that you're already using some libraries (such as Hibernate or Spring) that use it for some of their functionalities.

The following image was created with a Java byte code exploration tool with a Graphviz back end. The ability to query our code and libraries can be a valuable aid for understanding by suggesting areas of a framework or library to explore, and more.

Examining class files

We see a few interfaces we've already touched upon (Action, ValidationAware, Validateable, TextProvider, LocaleProvider), and several we haven't (but will).

Using the tools we already have, or the tools that we create on an as needed basis, we can facilitate development and learning efforts.

Additional action interfaces

We'll take a brief look at a few more useful interfaces provided by XWork and Struts. We can use our own exploring methods (or just scan the Javadocs) to discover more. Just like it's difficult to over-emphasize how important command line mastery is, it's tough to oversell the value of exploration, writing example and test code, and so on.

Preparable interface

The Preparable interface works in concert with the prepare interceptor. It defines a single method, prepare(), which is executed before any action method. For example, this can be used to set up a data structure used in a JSP page, perform database access, and so on. Preparable is most useful when an action has multiple methods, each needing identical initialization. Rather than calling a preparation method each time, we implement Preparable, and the framework calls it for us.

Actions with only a single method might not benefit from Preparable. The benefit of Preparable is that it can remove initialization and setup code from the mainline code. The disadvantage is that it requires the developer to know that the preparable methods are being called by the framework during the request handling process.

Actions containing multiple methods can implement method-specific Preparable methods. For example, if we have an action method doSomething(), we'd create prepareDoSomething(). The prepare interceptor would call this method after the prepare() method, if it exists.

Accessing scoped attributes (and request parameters)

As we've seen, the request parameters will be set as action properties by the framework. Some web applications will also need access to application-scoped, session-scoped, or request-scoped attributes. A trio of interfaces cover this requirement, another adds accessing request parameters in case we need to access them directly.

These four interfaces, each containing a single method, work in concert with the servletConfig interceptor. They allow actions to access scoped data, without tying our actions to the servlet specification. This has a compelling consequence. We can use these actions out of our application server environment, including in standalone applications, or unit and functional tests. (We'll explore testing topics later in the book.)

void setApplication(Map application); // ApplicationAware
void setSession(Map session); // SessionAware
void setRequest(Map request); // RequestAware
void setParameters(Map parameters); // ParameterAware

Note that we can access the same objects via ActionContext methods. For example, we can access the session attributes with:

Map session = ActionContext.getContext().getSession();

This is, however, less testable. Therefore, implementing SessionAware is preferable.

Accessing servlet objects

While it's not recommended, there are (hopefully rare) times when we absolutely need access to Servlet specification objects, rather than just their attribute or parameter maps. Implementing these interfaces will provide these objects, but tie our action to the Servlet specification.

// ServletContextAware
void setServletContext(ServletContext context);
// ServletRequestAware
void setServletRequest(HttpServletRequest request);
// ServletResonseAware
void setServletResponse(HttpServletResponse response);

Similar to the attribute and parameter maps, we can also access these objects using a utility class. For example, we could access the HTTP request with:

HttpServletRequest req = ServletActionContext.getRequest();

As indicated before, this is not the preferred method, as it's more flexible to implement the interface.

Along the same line as the other *Aware interfaces is the CookiesAware interface, which works with the cookie interceptor to provide our action with a map of Cookies. This also ties our action to the Servlet specification, although perhaps with less consequence.

void setCookiesMap(Map cookies); // CookiesAware

Request parameter/action property filtering

The NoParameters interface (interestingly defined by both XWork and Struts 2, although the Struts 2 version does not appear to add any additional functionality) effectively turns off the normal request parameter framework processing. In other words, implementing NoParameters means that our action won't have its properties set from request parameters. This is only a marker interface and defines no methods.

The ParameterNameAware interface allows an action to declare whether or not it will accept a parameter of a given name via the acceptableParameterName() method. The method should return true if the action will accept a parameter. Otherwise it should return false.

boolean acceptableParameterName(String parameterName);

This interface also works in conjunction with the params method. There are various ways to use this method. We could create a white list of parameters to accept, or a blacklist to ignore. We'll discover another way to get the same functionality using a parameter filter interceptor, thereby avoiding Java code. However, it's nice to know that the functionality is there.

Summary

This chapter delves into ActionSupport. It explains how ActionSupport, along with a few interfaces and a few Struts 2 tags, allows us to implement a fair amount of functionality. The chapter covers a bit about the I18N and validation support provided by ActionSupport.

The chapter also takes a quick took at a few other interfaces that provide access to scoped attributes (and if needed, direct access to request parameters). We can also access Servlet specification objects, such as the request and response, if we really need to.

All of these interfaces work in concert with interceptors, which we'll discuss in detail later. Even without in-depth knowledge of interceptors, we are already using them. And we could see that they're something worth paying attention to&mdash;much of Struts 2's functionality depends on them.

Next on our plate is more about Struts 2 results, including a look at the framework's default result types. We'll also look further at result configuration, type conversion, and how to create our own custom result types.

We'll also add a bit more functionality to our recipe application, sneak a peak at some additional Struts 2 tags (that will make you wonder why I made you type all that JSP in this chapter!), and get our first look at how to fake some data&mdash;smoke and mirrors, but handy!

References

A reader can refer to the following:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.189.186