"It would be so nice if something made sense for a change."
Now that we understand how to construct the Model and View components of your application, it is time to focus on the Controller components. The framework includes a servlet that implements the primary function of mapping a request URI to an Action class. Therefore, your primary responsibilities related to the Controller are:
[org.apache.struts.action.Action]
class).
For those of you familiar with MVC architecture, the ActionServlet represents the C - the controller. The job of the controller is to:
In addition to being the front controller for your application, the ActionServlet instance also is responsible for initialization and clean-up of resources. When the controller initializes, it first loads the application config corresponding to the "config" init-param. It then goes through an enumeration of all init-param elements, looking for those elements who's name starts with config/. For each of these elements, the framework loads the configuration file specified by the value of that init-param, and assigns a "prefix" value to that module's ModuleConfig instance consisting of the piece of the init-param name following "config/". For example, the module prefix specified by the init-param config/foo would be "foo". This is important to know, since this is how the controller determines which module will be given control of processing the request. To access the module foo, you would use a URL like:
http://localhost:8080/myApp/foo/someAction.do
For each request made of the controller, the method process(HttpServletRequest, HttpServletResponse) will be called. This method simply determines which module should service the request and then invokes that module's RequestProcessor's process method, passing the same request and response.
The RequestProcessor is where the majority of the core
processing
occurs for each request. Since version 1.3, the default
Request Processor
(ComposableRequestProcessor)
is composed using
Jakarta
Commons Chain,
which is an implementation of the
Chain of Responsibility
pattern (CoR).
It is designed as a drop-in replacement of the Struts
1.2.x behaviour and brings
greater flexibility and easier customization to the
request processing process.
Each step of the processing is represented as a separate
Command in the Chain.
By inserting, substituting, or removing Commands,
you can customize the framework to work
your
way.
Let's take a look at the
default
commands for the ComposableRequestProcessor
Chain in turn. These Command classes are in either the
org.apache.struts.chain.commands
or
org.apache.struts.chain.commands.servlet
packages.
SelectLocale | Select a locale for this request, if one hasn't already been selected, and place it in the request. |
SetOriginalURI | Store the URI of the original request in the request. |
RequestNoCache | If appropriate, set the following response headers: "Pragma", "Cache-Control", and "Expires". |
RemoveCachedMessages |
Removes any ActionMessages object stored
in the session under Globals.MESSAGE_KEY
and Globals.ERROR_KEY if the messages'
isAccessed method returns true. This
allows messages to be stored in the session, displayed
one time, and be released.
|
SetContentType | Set the default content type (with optional character encoding) for all responses if requested. |
SelectAction | Determine the ActionMapping associated with this path. |
AuthorizeAction | If the mapping has a role associated with it, ensure the requesting client has the specified role. If the client does not, raise an error and stop processing of the request. |
CreateActionForm | Instantiate (if necessary) the ActionForm associated with this mapping (if any) and place it into the appropriate scope. |
PopulateActionForm | Populate the ActionForm associated with this request, if any. |
ValidateActionForm | Perform validation (if requested) on the ActionForm associated with this request (if any). |
SelectInput | If validation failed, select the appropriate ForwardConfig for return to the input page. |
ExecuteCommand | Lookup and execute a chain command if the current ActionConfig is so-configured. |
SelectForward | If this mapping represents a forward, forward to the path specified by the mapping. |
SelectInclude | Select the include uri (if any) for the current action mapping. |
PerformInclude | If selected, include the result of invoking the path in this request. |
CreateAction | Instantiate an instance of the class specified by the current ActionMapping (if necessary). |
ExecuteAction | This is the point at which your Action's execute method will be called. |
ExecuteForwardCommand | Lookup and execute a chain command if the current ForwardConfig is so-configured. |
PerformForward | Finally, the process method of the RequestProcessor takes the ActionForward returned by your Action class, and uses it to select the next resource (if any). Most often the ActionForward leads to the presentation page that renders the response. |
In Struts 1.2.x, Tiles processing required configuring
the framework to use the
TilesRequestProcessor
implementation. In the Struts 1.3
Chain
based request processor using Tiles simply
involves configuring it to use an additional Tiles
Command, and
deploying the tiles sub-project jar. The following Command
is in the
org.apache.struts.tiles.commands
package.
TilesPreProcessor | Command class intended to perform responsibilities of the TilesRequestProcessor in Struts 1.2.x |
The Struts 1.2.x RequestProcessor utilized a similar but different set of messages. Here we provide a table summarizing the changes.
This chain attempts to emulate (most of) the standard request processing in the standard org.apache.struts.action.RequestProcessor class, by performing the corresponding tasks in individual Commands that are composable. The following list defines a cross reference between the processXxx methods and the Commands that perform the corresponding functionality: | |
processMultipart | Integrated into servlet and legacy classes |
processPath | SelectAction (which also does processMapping) |
processException | ExceptionCatcher / ExceptionHandler |
processLocale | SelectLocale |
processContent | SetContentType |
processNoCache | RequestNoCache |
processPreprocess | LookupCommand with optional="true". Multiple occurrences of this can easily be added, to support additional processing hooks at any point in the chain without modifying the standard definition. |
processCachedMessages | RemoveCachedMessages |
processMapping | SelectAction (which also does processPath) |
processRoles | AuthorizeAction |
processActionForm | CreateActionForm |
processPopulate | PopulateActionForm |
processValidate | ValidateActionForm / SelectInput |
processForward | SelectForward |
processInclude | SelectInclude / PerformInclude |
processActionCreate | CreateAction |
processActionPerform | ExecuteAction |
An ActionForm represents an HTML form that the user interacts with over one or more pages. You will provide properties to hold the state of the form with getters and setters to access them. ActionForms can be stored in either the session (default) or request scope. If they're in the session it's important to implement the form's reset method to initialize the form before each use. The framework sets the ActionForm's properties from the request parameters and sends the validated form to the appropriate Action's execute method.
When you code your ActionForm beans, keep the following principles in mind:
This bottleneck can be alleviated through the use of DynaActionForm classes. Instead of creating a new ActionForm subclass and new get/set methods for each of your bean's properties, you can list its properties, type, and defaults in the framework's configuration file.
For example, add the following to struts-config.xml for a UserForm bean that stores a user's given and family names:
<form-bean name="UserForm" type="org.apache.struts.action.DynaActionForm"> <form-property name="givenName" type="java.lang.String" initial="John"/> <form-property name="familyName" type="java.lang.String" initial="Smith"/> </form-bean>
The types supported by DynaActionForm include:
If you do not supply an initial attribute, numbers will be initialized to 0 and objects to null.
In JSP pages using the Struts Taglib, attributes of DynaActionForm objects can be referenced just like ordinary ActionForm objects. Wherever a Struts tag refers to a "property", the tags will automatically use the DynaActionForm properties just like those of a conventional JavaBean. You can even expose DynaActionForm properties using bean:define. (Although, you can't use bean:define to instantiate a DynaActionForm, since it needs to be setup with the appropriate dyna-properties).
If you are using the Struts JSTL EL taglib, the references are, by default, different. Only properties of ordinary ActionForm objects can be directly accessed through the JSTL expression language syntax. The DynaActionForm properties must be accessed through a slightly different syntax. The JSTL EL syntax for referencing a property of an ActionForm goes like this:
${formbean.prop}
The syntax for referencing a property of a DynaActionForm would be:
${dynabean.map.prop}
The map property is a property of DynaActionForm which represents the HashMap containing the DynaActionForm properties.
DynaActionForms are meant as an easy solution to a common problem: Your ActionForms use simple properties and standard validations, and you just pass these properties over to another JavaBean (say using BeanUtils.copyProperties(myBusinessBean,form)).
DynaActionForms are not a drop-in replacement for ActionForms. If you need to access DynaActionForm properties in your Action, you will need to use the map-style accessor, like myForm.get("name"). If you actively use the ActionForm object in your Action, then you may want to use conventional ActionForms instead.
DynaActionForms cannot be instantiated using a no-argument constructor. In order to simulate the extra properties, there is a lot of machinery involved in their construction. You must rely on the framework to instantiate a DynaActionForm for you, via the ActionMapping.
If need be, you can extend the DynaActionForm to add custom validate and reset methods you might need. Simply specify your subclass in the struts-config instead. However, you cannot mix conventional properties and DynaProperties. A conventional getter or setter on a DynaActionForm won't be found by the reflection utilities.
To use DynaActionForms with the Struts Validator, specify org.apache.struts.validator.ValidatorActionForm (or your subclass) as the form-bean class.
And, of course, while the DynaActionForm may support various binary types, properties used with the html:text tag should still be String properties.
DynaActionForms relieve developers of maintaining simple ActionForms. For even less maintenance, try the LazyActionForm described in the following section.
Struts Lazy ActionForm which wraps a LazyDynaBean (see Commons BeanUtils JavaDoc).
There isn't really that much to this implementation as most of the lazy behaviour is in LazyDynaBean and wrapping the LazyDynaBean is handled in the parent BeanValidatorForm. The only thing it really does is populate indexed properties which are a List type with a LazyDynaBean in the get(name, index) method.
Lazy DynaBeans provide several types of lazy behaviour:
Having said that it is not necessary to pre-define properties in the struts-config.xml, it is useful to sometimes do so for mapped or indexed properties. For example, if you want to use a different Map implementation from the default HashMap or an array for indexed properties, rather than the default List type:
<form-bean name="myForm" type="org.apache.struts.validator.LazyValidatorForm"> <form-property name="myMap" type="java.util.TreeMap" /> <form-property name="myBeans" type="org.apache.commons.beanutils.LazyDynaBean[]" /> </form-bean>
Another reason for defining indexed properties in the struts-config.xml is that if you are validating indexed properties using the Validator and none are submitted then the indexed property will be null which causes validator to fail. Pre-defining them in the struts-config.xml will result in a zero-length indexed property (array or List) being instantiated, avoiding an issue with validator in that circumstance.
This implementation validates using the ActionForm name . If you require a version that validates according to the path then it can be easily created in the following manner:
public class MyLazyForm extends LazyValidatorForm { public MyLazyForm () { super(); setPathValidation(true); } }
Rather than using this class, another alternative is to either use a LazyDynaBean or custom version of LazyDynaBean directly, the framework now automatically wraps objects which are not ActionForms in a BeanValidatorForm. For example:
<form-bean name="myForm" type="org.apache.commons.beanutils.LazyDynaBean"> <form-property name="myBeans" type="org.apache.commons.beanutils.LazyDynaBean[]" /> </form-bean>
For more coding examples, see the LazyValidatorForm How-To.
The DynaActionForm classes offer the ability to create ActionForm beans at initialization time, based on a list of properties enumerated in the the framework's configuration file. However, many HTML forms are generated dynamically at request time. Since the properties of these forms' ActionForm beans are not all known ahead of time, we need a new approach.
The framework allows you to make one or more of your ActionForm's properties' values a Map instead of a traditional atomic object. You can then store the data from your form's dynamic fields in that Map. Here is an example of a map-backed ActionForm class:
public FooForm extends ActionForm { private final Map values = new HashMap(); public void setValue(String key, Object value) { values.put(key, value); } public Object getValue(String key) { return values.get(key); } }
In its corresponding JSP page, you can access objects stored in the values map using a special notation: mapname(keyname). The parentheses in the bean property name indicate that:
<html:text property="value(foo)"/>
This will call the getValue method on FooForm with a key value of "foo" to find the property value. To create a form with dynamic field names, you could do the following:
<% for (int i = 0; i < 10; i++) { String name = "value(foo-" + i + ")"; %> <html:text property="<%= name %>"/> <br/> <% } %>
Note that there is nothing special about the name "value". Your map-backed property could instead be named "property", "thingy", or any other bean property name you prefer. You can even have multiple map-backed properties on the same bean.
In addition to map-backed properties, you can also create list-backed properties. You do so by creating indexed get/set methods on your bean:
public FooForm extends ActionForm { private final List values = new ArrayList(); public void setValue(int key, Object value) { values.set(key, value); } public Object getValue(int key) { return values.get(key); } }
In your presentation pages, you access individual entries in a list-backed property by using a different special notation: listname[index]. The braces in the bean property name indicate that the bean property named listname is indexed (probably backed by a List), and that the framework should look for get/set methods that take an index parameter in order to find the correct sub-property value.
While map-backed ActionForms provide you with more flexibility, they do not support the same range of syntax available to conventional or DynaActionForms. You might have difficulty referencing indexed or mapped properties using a map-backed ActionForm. The validwhen validator (since Apache Struts 1.2.1) also does not support map-backed ActionForms.
The Action class defines two methods that could be executed depending on your servlet environment:
public ActionForward execute(ActionMapping mapping, ActionForm form, ServletRequest request, ServletResponse response) throws Exception; public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception;
Since the majority of teams using the framework are focused on building web applications, most projects will only use the "HttpServletRequest" version. A non-HTTP execute() method has been provided for applications that are not specifically geared towards the HTTP protocol.
The goal of an Action class is to process a request, via its execute method, and return an ActionForward object that identifies where control should be forwarded (e.g. a JSP, Tile definition, Velocity template, or another Action) to provide the appropriate response. In the MVC/Model 2 design pattern, a typical Action class will often implement logic like the following in its execute method:
The perform method may still be used in Apache Struts 1.1 but is deprecated. The Apache Struts 1.1 method simply calls the new execute method and wraps any Exception thrown as a ServletException. The deprecated perform method was removed in Apache Struts 1.2.
Remember the following design guidelines when coding Action classes:
You can define an ExceptionHandler to execute when an Action's execute method throws an Exception. First, you need to subclass org.apache.struts.action.ExceptionHandler and override the execute method. Your execute method should process the Exception and return an ActionForward object to tell the framework where to forward to next. Then you configure your handler in struts-config.xml like this:
<global-exceptions> <exception key="some.key" type="java.io.IOException" handler="com.yourcorp.ExceptionHandler"/> </global-exceptions>
This configuration element says that com.yourcorp.ExceptionHandler.execute will be called when any IOException is thrown by an Action. The key is a key into your message resources properties file that can be used to retrieve an error message.
If the handler attribute is not specified, the default handler stores the exception in the request attribute under the value of the Globals.EXCEPTION_KEY global key.
The possible attributes for the "exception" element are as follows:
Attribute | Description | Default |
---|---|---|
bundle | Servlet context attribute for the message resources bundle associated with this handler. The default attribute is the value specified by the string constant declared at Globals.MESSAGES_KEY. | org.apache.struts.Globals.MESSAGES_KEY |
className | The configuration bean for this ExceptionHandler object. If specified, className must be a subclass of the default configuration bean | "org.apache.struts.config.ExceptionConfig" |
extends | The name of the exception handler that this will inherit configuration information from. | None |
handler | Fully qualified Java class name for this exception handler. | "org.apache.struts.action.ExceptionHandler" |
key | The key to use with this handler's message resource bundle that will retrieve the error message template for this exception. | None |
path | The module-relative URI to the resource that will complete the request/response if this exception occurs. | None |
scope | The context ("request" or "session") that is used to access the ActionMessage object [org.apache.struts.action.ActionMessage] for this exception. | "request" |
type | Fully qualified Java class name of the exception type to register with this handler. | None |
You can override global exception handlers by defining a handler inside an action definition.
A common use of ExceptionHandlers is to configure one for java.lang.Exception so it's called for any exception and log the exception to some data store.
The PlugIn interface extends Action and so that applications can easily hook into the ActionServlet lifecycle. This interface defines two methods, init() and destroy(), which are called at application startup and shutdown, respectively. A common use of a Plugin Action is to configure or load application-specific data as the web application is starting up.
At runtime, any resource setup by init would be accessed by Actions or business tier classes. The PlugIn interface allows you to setup resources, but does not provide any special way to access them. Most often, the resource would be stored in application context, under a known key, where other components can find it.
PlugIns are configured using <plug-in> elements within the framework's configuration file. See PlugIn Configuration for details.
In order to operate successfully, our controller servlet needs to know several things about how each request URI should be mapped to an appropriate Action class. The required knowledge has been encapsulated in a Java class named ActionMapping, the most important properties are as follows:
The developer's responsibility is to create an XML file named struts-config.xml and place it in the WEB-INF directory of your application. The format of this document is described by the Document Type Definition (DTD) maintained at http://struts.apache.org/dtds/struts-config_1_3.dtd. This chapter covers the configuration elements that you will typically write as part of developing your application. There are several other elements that can be placed in the struts-config file to customize your application. See Configuring Applications for more about the other elements in the framework's configuration file.
The controller uses an internal copy of this document to parse the configuration; an Internet connection is not required for operation.
The outermost XML element must be <struts-config>. Inside of the <struts-config> element, there are three important elements that are used to describe your actions:
Here's a mapping entry based on the MailReader example application. The MailReader application now uses DynaActionForms. But in this example, we'll show a conventional ActionForm instead, to illustrate the usual workflow. Note that the entries for all the other actions are left out:
<struts-config> <form-beans> <form-bean name="logonForm" type="org.apache.struts.webapp.example.LogonForm" /> </form-beans> <global-forwards type="org.apache.struts.action.ActionForward"> <forward name="logon" path="/logon.jsp" redirect="false" /> </global-forwards> <action-mappings> <action path="/logon" type="org.apache.struts.webapp.example.LogonAction" name="logonForm" scope="request" input="/logon.jsp" unknown="false" validate="true" /> </action-mappings> </struts-config>
First the form bean is defined. A basic bean of class "org.apache.struts.webapp.example.LogonForm" is mapped to the logical name "logonForm". This name is used as a request attribute name for the form bean.
The "global-forwards" section is used to create logical name mappings for commonly used presentation pages. Each of these forwards is available through a call to your action mapping instance, i.e. mapping.findForward("logicalName").
As you can see, this mapping matches the path /logon (actually, because the MailReader example application uses extension mapping, the request URI you specify in a JSP page would end in /logon.do). When a request that matches this path is received, an instance of the LogonAction class will be created (the first time only) and used. The controller servlet will look for a bean in request scope under key logonForm, creating and saving a bean of the specified class if needed.
Optional but very useful are the local "forward" elements. In the MailReader example application, many actions include a local "success" and/or "failure" forward as part of an action mapping.
<!-- Edit mail subscription --> <action path="/editSubscription" type="org.apache.struts.webapp.example.EditSubscriptionAction" name="subscriptionForm" scope="request" validate="false"> <forward name="failure" path="/mainMenu.jsp"/> <forward name="success" path="/subscription.jsp"/> </action>
Using just these two extra properties, the Action classes are almost totally independent of the actual names of the presentation pages. The pages can be renamed (for example) during a redesign, with negligible impact on the Action classes themselves. If the names of the "next" pages were hard coded into the Action classes, all of these classes would also need to be modified. Of course, you can define whatever local forward properties makes sense for your own application.
The configuration file includes several other elements that you can use to customize your application. See Configuring Applications for details.
Fronting your pages with ActionMappings is essential when using modules, since doing so is the only way you involve the controller in the request -- and you want to! The controller puts the application configuration in the request, which makes available all of your module-specific configuration data (including which message resources you are using, request-processor, and so forth).
The simplest way to do this is to use the forward property of the ActionMapping:
<action path="/view" forward="/view.jsp"/>
[Since Apache Struts 1.2.0] As an application grows in size, so will the number of action mappings. Wildcards can be used to combine similar mappings into one more generic mapping.
The best way to explain wildcards is to show an example and walk through how it works. This example modifies the previous mapping in the ActionMapping Example section to use wildcards to match all pages that start with /edit:
<!-- Generic edit* mapping --> <action path="/edit*" type="org.apache.struts.webapp.example.Edit{1}Action" name="{1}Form" scope="request" validate="false"> <forward name="failure" path="/mainMenu.jsp"/> <forward name="success" path="/{1}.jsp"/> </action>
The "*" in the path attribute allows the mapping to match the request URIs /editSubscription, editRegistration, or any other URI that starts with /edit, however /editSubscription/add would not be matched. The part of the URI matched by the wildcard will then be substituted into various attributes of the action mapping and its action forwards replacing {1}. For the rest of the request, the framework will see the action mapping and its action forwards containing the new values.
Mappings are matched against the request in the order they appear in the framework's configuration file. If more than one pattern matches the last one wins, so less specific patterns must appear before more specific ones. However, if the request URL can be matched against a path without any wildcards in it, no wildcard matching is performed and order is not important.
Wildcard patterns can contain one or more of the following special tokens:
* | Matches zero or more characters excluding the slash ('/') character. |
** | Matches zero or more characters including the slash ('/') character. |
\character | The backslash character is used as an escape sequence. Thus \* matches the character asterisk ('*'), and \\ matches the character backslash ('\'). |
In the action mapping and action forwards, the wildcard-matched values can be accessed with the token {N} where N is a number from 1 to 9 indicating which wildcard-matched value to substitute. The whole request URI can be accessed with the {0} token.
The action mapping attributes that will accept wildcard-matched strings are:
The action forward attributes that will accept wildcard-matched strings are:
The framework doesn't configure logging itself -- it's all done by commons-logging under the covers. The default algorithm is a search:
Because the framework uses commons-logging and, therefore, includes the necessary JAR files, you've probably had the occasional fleeting thought, "Should I use commons-logging?" The answer (surprise!) depends on the requirements for your particular project. If one of your requirements is the ability to easily change logging implementations with zero impact on your application, then commons-logging is a very good option.
"Great! What do I do to get started using commons-logging in my own code?"
Using commons-logging in your own code is very simple - all you need are two imports and a declaration for a logger. Let's take a look:
package com.foo; // ... import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; ... public class Foo { // ... private static Log log = LogFactory.getLog(Foo.class); // ... public void setBar(Bar bar) { if (log.isTraceEnabled()) { log.trace("Setting bar to " + bar); } this.bar = bar; } // ... }
The general idea is to instantiate a single logger per class and to use a name for the logger which reflects where it's being used. The example is constructed with the class itself. This gives the logger the name of com.foo.Foo. Doing things this way lets you easily see where the output is coming from, so you can quickly pin-point problem areas. In addition, you are able to enable/disable logging in a very fine-grained way.
For examples of using logging in the framework classes, see the Action classes in our MailReader example application.
Next: Configuring Applications