The Struts framework depends on one or more configuration files to be able to load and create the necessary application-specific components at startup. The configuration files allow the behavior of the framework components to be specified declaratively, rather than having the information and behavior hardcoded. This gives developers the flexibility to provide their own extensions, which the framework can discover dynamically. The configuration file is based on the XML format and can be validated against the Struts DTD struts-config_1_1.dtd. Although there are some similarities between the 1.0 and 1.1 versions of the framework with respect to the configuration file, there are at least as many differences. Fortunately, the designers of the framework have made backward compatibility a goal of the Struts 1.1 release; therefore, your 1.0 applications should continue to work properly with the new version. 4.6.1 Configuring Multiple Application ModulesApplication modules were mentioned briefly in Chapter 3, but we haven't yet fully introduced this new feature. With application modules, you can define multiple Struts configuration files, one for each supported module. Each application module can provide its own configuration information, including message resources, and be completely independent from other modules. Application modules allow a single Struts application to be split into separate projects, making parallel development easier to accomplish. Although the functionality for application modules exists in the framework, you are not required to implement more than one (the default application module). We'll discuss application modules further in Chapter 5, Chapter 6, and Chapter 7. For now, we'll concentrate on configuring the default application; we'll see how easy it is to add additional modules later. 4.6.2 The org.apache.struts.config PackageThe org.apache.struts.config package was added to Struts 1.1. The framework uses JavaBeans at runtime to hold the configuration information it reads from the Struts configuration files. Figure 4-4 shows the essential classes from the config package. Figure 4-4. Class diagram for the org.apache.struts.config packageEach class in the config package holds information from a specific section of the configuration file. After the configuration file has been validated and parsed, the Struts framework uses instances of these beans to represent in-memory versions of the information that has been declared in the configuration file. These classes act as runtime containers of the configuration information and are used by the framework components as needed. The org.apache.struts.config.ConfigRuleSet class shown in Figure 4-4 has a slightly different, but related, job it contains the set of rules that are required to parse a Struts configuration file. Its job is to construct instances of the configuration JavaBeans mentioned in the previous paragraph when the application is started. 4.6.3 The ApplicationConfig ClassThe org.apache.struts.config.ApplicationConfig class deserves a special introduction, as it plays a very important role in the framework. As Figure 4-4 indicates, it is central to the entire config package and holds onto the configuration information that describes an entire Struts application. If multiple application modules are being used, there is one ApplicationConfig object for each module. The ApplicationConfig class will surface throughout the remainder of our discussion of the framework. 4.6.4 The Struts Configuration DTDAs the web application's DTD is used to validate the web.xml file, the Struts DTD is used to validate the Struts configuration file.
The following Struts DTD declaration indicates that the struts-config element is the root element for the XML file and that it has eight child elements: <!ELEMENT struts-config (data-sources?, form-beans?, global-exceptions?, global- forwards?, action-mappings?, controller?, message-resources*, plug-in*) > 4.6.4.1 The data-sources elementThe data-sources element allows you to set up a rudimentary data source that you can use from within the Struts framework. A data source acts as a factory[3] for database connections and provides a single point of control. Many data source implementations use a connection-pooling mechanism to improve performance and scalability.
Many vendors provide their own implementations of data source objects. The Java language provides the javax.sql.DataSource interface, which all implementations must implement. Most application servers and some web containers provide built-in data source components. All of the major database vendors also provide data source implementations. The data-sources element can contain zero or more data-source elements: <!ELEMENT data-sources (data-source*)> The data-source element allows for multiple set-property elements to be specified: <!ELEMENT data-source (set-property*)> The set-property element allows you to configure properties that are specific to your data source implementation. Throughout the discussion of the Struts configuration elements in the rest of this chapter, you will notice a child element called set-property in many of the major elements of the configuration file. The set-property element specifies the name and value of an additional JavaBeans configuration property whose setter method will be called on the object that represents the surrounding element. This element is especially useful for passing additional property information to an extended implementation class. The set-property element is optional, and you will use it only if you need to pass additional properties to a configuration class.
The attributes for the data-source element are listed in Table 4-3.
The following code illustrates how to configure a data source within the Struts configuration file: <data-sources> <data-source> <set-property property="autoCommit" value="true"/> <set-property property="description" value="MySql Data Source"/> <set-property property="driverClass" value="com.caucho.jdbc.mysql.Driver"/> <set-property property="maxCount" value="10"/> <set-property property="minCount" value="2"/> <set-property property="user" value="admin"/> <set-property property="password" value="admin"/> <set-property property="url" value="jdbc:mysql-caucho://localhost:3306/storefront"/> </data-source> </data-sources> This code illustrates a data-source element configured to connect to a MySQL database using a JDBC driver from Caucho Technology, the developers of the Resin™ servlet/EJB container. You can specify multiple data sources within the configuration file, assign each one a unique key, and access a particular data source in the framework by its key. This gives you the ability to access multiple databases if necessary.There are several other popular data source implementations you can use. Table 4-4 lists a few of the more popular alternative implementations.
4.6.4.2 The form-beans elementThe form-beans element allows you to configure multiple ActionForm classes that are used by the views. Within the form-beans section, you can configure zero or more form-bean child elements. Each form-bean element also has several child elements. <!ELEMENT form-bean (icon?, display-name?, description?, set-property*, form- property*) > Each form-bean element also has four attributes that you can specify. Table 4-5 lists the attributes.
As mentioned in Chapter 3, a form bean is a JavaBeans class that extends the org.apache.struts.action.ActionForm class. The following code shows how the form-beans element can be configured in the Struts configuration file: <struts-config> <form-beans> <form-bean name="loginForm" type="org.apache.struts.action.DynaActionForm"> <form-property name="username" type="java.lang.String"/> <form-property name="password" type="java.lang.String"/> </form-bean> <form-bean name="shoppingCartForm" type="com.oreilly.struts.order.ShoppingCartForm"/> </form-beans> </struts-config> One of the form-bean elements in this code uses a feature new in Struts 1.1, called dynamic action forms. Dynamic action forms were discussed briefly in Chapter 3 and will be discussed in detail in Chapter 7. You can pass one or more dynamic properties to an instance of the org.apache.struts.action.DynaActionForm class using the form-property element. It is supported only when the type attribute of the surrounding form-bean element is org.apache.struts.action.DynaActionForm, or a descendant class. Each form-property element also has four attributes that you can specify. Table 4-6 lists the attributes allowed in the form-property element.
The following form-bean fragment illustrates the use of the form-property element: <form-bean name="checkoutForm" type="org.apache.struts.action.DynaActionForm"> <form-property name="firstName" type="java.lang.String"/> <form-property name="lastName" type="java.lang.String"/> <form-property name="age" type="java.lang.Integer" initial="18"/> </form-bean> 4.6.4.3 The global-exceptions elementThe global-exceptions section allows you to configure exception handlers declaratively. The global-exceptions element can contain zero or more exception elements: <!ELEMENT global-exceptions (exception*)> Later in this chapter, when action mappings are discussed, you will see that the exception element also can be specified in the action element. If an exception element is configured for the same type of exception both in the global- exceptions element and in the action element, the action level will take precedence. If no exception element mapping is found at the action level, the framework will look for exception mappings defined for the exception's parent class. Eventually, if a handler is not found, a ServletException or IOException will be thrown, depending on the type of the original exception. Chapter 10 deals with both declarative and programmatic exception handling in detail. This section illustrates how to configure declarative exception handling for your applications. The exception element describes a mapping between a Java exception that may occur during processing of a request and an instance of org.apache.struts. action.ExceptionHandler that is responsible for dealing with the thrown exception. The declaration of the exception element illustrates that it also has several child elements: <!ELEMENT exception (icon? display-name? description? set-property*)> Probably more important than the child elements are the attributes that can be specified in the exception element. The attributes are listed in Table 4-7.
The following is an example of a global-exceptions element: <global-exceptions> <exception key="global.error.invalidlogin" path="/security/signin.jsp" scope="request" type="com.oreilly.struts.framework.exceptions.InvalidLoginException"/> </global-exceptions> 4.6.4.4 The global-forwards elementEvery action that is executed finishes by forwarding or redirecting to a view. This view is a JSP page or static HTML page, but might be another type of resource. Instead of referring to the view directly, the Struts framework uses the concept of a forward to associate a logical name with the resource. So, instead of referring to login.jsp directly, a Struts application may refer to this resource as the login forward, for example. The global-forwards section allows you to configure forwards that can be used by all actions within an application. The global-forwards section consists of zero or more forward elements: <!ELEMENT global-forwards (forward*)> The forward element maps a logical name to an application-relative URI. The application can then perform a forward or redirect, using the logical name rather than the literal URI. This helps to decouple the controller and model logic from the view. The forward element can be defined in both the global-forwards and action elements. If a forward with the same name is defined in both places, the action level will take precedence. The declaration of the forward element illustrates that it also has child elements: <!ELEMENT forward(icon?, display-name?, description, set-property*)> As with the exception element, the attributes probably are more interesting than the child elements. The attributes for the forward element are shown in Table 4-8.
Here's an example of a global-forwards element from the Storefront application: <global-forwards> <forward name="Login" path="/security/signin.jsp" redirect="true"/> <forward name="SystemFailure" path="/common/systemerror.jsp"/> <forward name="SessionTimeOut" path="/common/sessiontimeout.jsp" redirect="true"/> <forward name="Welcome" path="/viewsignin"/> </global-forwards> The org.apache.struts.action.ActionForward class is used to hold the information configured in the controller element (discussed later). The ActionForward class now extends org.apache.struts.config.ForwardConfig for backward compatibility. 4.6.4.5 The action-mappings elementThe action-mappings element contains a set of zero or more action elements for a Struts application: <!ELEMENT action-mappings (action*)> The action element describes a mapping from a specific request path to a corresponding Action class. The controller selects a particular mapping by matching the URI path in the request with the path attribute in one of the action elements. The action element contains the following child elements: <!ELEMENT action (icon?, display-name?, description, set-property*, exception*, forward*)> Two child elements should stand out in the list of children for the action element, because you've already seen them earlier in this chapter: exception and forward. We talked about the exception element when we discussed the global-exceptions element. We mentioned then that exception elements could be defined at the global or at the action level. The exception elements defined within the action element take precedence over any of the same type defined at the global level. The syntax and attributes are the same, regardless of where they are defined. We introduced the forward element when we discussed the global-forwards element. As with exception elements, a forward element can be defined both at the global level and at the action level. The action level takes precedence if the same forward is defined in both locations. The action element contains quite a few attributes, shown in Table 4-9.
The following is an example of the "signin" action element from the Storefront application: <action path="/signin" type="com.oreilly.struts.storefront.security.LoginAction" scope="request" name="loginForm" validate="true" input="/security/signin.jsp"> <forward name="Success" path="/index.jsp" redirect="true"/> <forward name="Failure" path="/security/signin.jsp" redirect="true"/> </action>
4.6.4.6 The controller elementThe controller element is new to Struts 1.1. Prior to Version 1.1, the ActionServlet contained the controller functionality, and you had to extend that class to override the functionality. In Version 1.1, however, Struts has moved most of the controller functionality to the RequestProcessor class. The ActionServlet still receives the requests, but it delegates the request handling to an instance of the RequestProcessor. This allows you to declaratively assign the processor class and modify its functionality. If you're familiar with Version 1.0, you'll notice that many of the parameters that were configured in the web.xml file for the controller servlet now are configured using the controller element. Because the controller and its attributes are defined in the struts-config.xml file, you can define a separate controller element for each module. The controller element has a single child element: <!ELEMENT controller (set-property*)> The controller element can contain zero or more set-property elements and many different attributes. The attributes are shown in Table 4-10.
The org.apache.struts.config.ControllerConfig class is used to represent the information configured in the controller element in memory. The following fragment shows an example of how to configure the controller element: <controller contentType="text/html;charset=UTF-8" locale="true" nocache="true" processor/> 4.6.4.7 The message-resources elementThe message-resources element specifies characteristics of the message resource bundles that contain the localized messages for an application. Each Struts configuration file can define one or more message resource bundles; therefore, each module can define its own bundles. The message-resources element contains only a set-property element: <!ELEMENT message-resources (set-property*)> Table 4-11 lists the attributes supported by the message-resources element.
The following example shows how to configure multiple message-resources elements for a single application. Notice that the second element had to specify the key attribute, because only one can be stored with the default key: <message-resources null="false" parameter="StorefrontMessageResources"/> <message-resources key="IMAGE_RESOURCE_KEY" null="false" parameter="StorefrontImageResources"/> 4.6.4.8 The plug-in elementThe concept of a plug-in was added in Struts 1.1. This powerful feature allows your Struts applications to discover resources dynamically at startup. For example, if you need to create a connection to a remote system at startup and you didn't want to hardcode this functionality into the application, you can use a plug-in, and the Struts application will discover it dynamically. To use a plug-in, create a Java class that implements the org.apache.struts.action.PlugIn interface and add a plug-in element to the configuration file. The PlugIn mechanism itself will be discussed further in Chapter 9. The plug-in element specifies a fully qualified class name of a general-purpose application plug-in module that receives notification of application startup and shutdown events. An instance of the specified class is created for each element; the init() method is called when the application is started, and the destroy() method is called when the application is stopped. The class specified here must implement the org.apache.struts.action.PlugIn interface and implement the init() and destroy( ) methods. The plug-in element may contain zero or more set-property elements, so that extra configuration information may be passed to your PlugIn class: <!ELEMENT plug-in (set-property*)> The allowed attribute for the plug-in element is shown in Table 4-12.
The following fragment shows two plug-in elements being used: <plug-in className="com.oreilly.struts.storefront.service.StorefrontServiceFactory"/> <plug-in className="org.apache.struts.validator.ValidatorPlugIn"> <set-property property="pathnames" value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/> </plug-in>
4.6.5 A Complete struts-config.xml FileUp to this point, you haven't seen a complete example of a Struts configuration file. Example 4-5 provides a complete listing. Example 4-5. A complete Struts configuration file<?xml version="1.0" encoding="UTF-8" ?> <!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> <data-sources> <data-source> <set-property property="autoCommit" value="true"/> <set-property property="description" value="Resin Data Source"/> <set-property property="driverClass" value="com.caucho.jdbc.mysql.Driver"/> <set-property property="maxCount" value="10"/> <set-property property="minCount" value="2"/> <set-property property="user" value="admin"/> <set-property property="password" value="admin"/> <set-property property="url" value="jdbc:mysqlcaucho://localhost:3306/storefront"/> </data-source> </data-sources> <form-beans> <form-bean name="loginForm" type="com.oreilly.struts.storefront.security.LoginForm"/> <form-bean name="itemDetailForm" type="org.apache.struts.action.DynaActionForm"> <form-property name="view" type="com.oreilly.struts.catalog.view.ItemView"/> </form-bean> </form-beans> <global-exceptions> <exception key="global.error.invalidlogin" path="/security/signin.jsp" scope="request" type="com.oreilly.struts.framework.exceptions.InvalidLoginException"/> </global-exceptions> <global-forwards> <forward name="Login" path="/security/signin.jsp" redirect="true"/> <forward name="SystemFailure" path="/common/systemerror.jsp"/> <forward name="SessionTimeOut" path="/common/sessiontimeout.jsp" redirect="true"/> </global-forwards> <action-mappings> <action path="/viewsignin" parameter="/security/signin.jsp" type="org.apache.struts.actions.ForwardAction" scope="request" name="loginForm" validate="false" input="/index.jsp"> </action> <action path="/signin" type="com.oreilly.struts.storefront.security.LoginAction" scope="request" name="loginForm" validate="true" input="/security/signin.jsp"> <forward name="Success" path="/index.jsp" redirect="true"/> <forward name="Failure" path="/security/signin.jsp" redirect="true"/> </action> <action path="/signoff" type="com.oreilly.struts.storefront.security.LogoutAction" scope="request" validate="false" input="/security/signin.jsp"> <forward name="Success" path="/index.jsp" redirect="true"/> </action> <action path="/home" parameter="/index.jsp" type="org.apache.struts.actions.ForwardAction" scope="request" validate="false"> </action> <action path="/viewcart" parameter="/order/shoppingcart.jsp" type="org.apache.struts.actions.ForwardAction" scope="request" validate="false"> </action> <action path="/cart" type="com.oreilly.struts.storefront.order.ShoppingCartActions" scope="request" input="/order/shoppingcart.jsp" validate="false" parameter="method"> <forward name="Success" path="/action/viewcart" redirect="true"/> </action> <action path="/viewitemdetail" name="itemDetailForm" input="/index.jsp" type="com.oreilly.struts.storefront.catalog.GetItemDetailAction" scope="request" validate="false"> <forward name="Success" path="/catalog/itemdetail.jsp"/> </action> <action path="/begincheckout" input="/order/shoppingcart.jsp" type="com.oreilly.struts.storefront.order.CheckoutAction" scope="request" validate="false"> <forward name="Success" path="/order/checkout.jsp"/> </action> <action path="/getorderhistory" input="/order/orderhistory.jsp" type="com.oreilly.struts.storefront.order.GetOrderHistoryAction" scope="request" validate="false"> <forward name="Success" path="/order/orderhistory.jsp"/> </action> </action-mappings> <controller contentType="text/html;charset=UTF-8" locale="true" nocache="true" processor/> <message-resources parameter="StorefrontMessageResources" null="false"/> <message-resources key="IMAGE_RESOURCE_KEY" parameter="StorefrontImageResources" null="false"/> <plug-in className="com.oreilly.struts.storefront.service.StorefrontServiceFactory"/> <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> 4.6.6 Using Multiple Application ModulesNow that you've seen how to configure the default application for Struts, the last step is to discuss how you include multiple application modules. With Struts 1.1, you have the ability to set up multiple Struts configuration files. Although the application modules are part of the same web application, they act independently of one another. You also can switch back and forth between application modules if you like. Using multiple application modules allows for better organization of the components within a web application. For example, you can assemble and configure one application module for everything that deals with catalogs and items, while another module can be organized with the configuration information for a shopping cart and ordering. Separating an application into components in this way facilitates parallel development. The first step is to create the additional Struts configuration files. Suppose we created a second configuration file named struts-order-config.xml. We must modify the web.xml file for the application and add an additional init-param element for the new module. This was shown earlier in the chapter, but it's repeated here for convenience. Example 4-6 shows the servlet instance mapping from before with an additional init-param for the second Struts configuration file. Example 4-6. A partial web.xml file that illustrates how to configure multiple modules<servlet> <servlet-name>storefront</servlet-name> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> <init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts-config.xml</param-value> </init-param> <init-param> <param-name>config/order</param-name> <param-value>/WEB-INF/struts-order-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> Notice that the param-name value for the nondefault application module in Example 4-6 begins with config/. All nondefault application modules' param- name elements must begin with config/; the default application's param-name element contains the config value alone. The part that comes after config/ is known as the application module prefix and is used throughout the framework for intercepting requests and returning the correct resources.
Pay special attention to the configuration attributes available in the various Struts XML elements. Some of them, as mentioned in this chapter, have a profound effect on how an application operates in a multiapplication module environment. 4.6.7 Specifying a DOCTYPE ElementTo ensure that your Struts configuration file is valid, it can and should be validated against the Struts DTD. To do this, you must include the DOCTYPE element at the beginning of your Struts configuration XML file: <?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd"> In earlier versions of the framework, there were some issues with applications not being able to start up if they couldn't get to the Jakarta site and access the DTD from there. This is no longer the case, as Struts now provides local copies of the DTDs. Some users prefer to specify a SYSTEM DOCTYPE tag, rather than a PUBLIC one. This allows you to specify an absolute path instead of a relative one. Although this may solve a short-term problem, it creates more long-term ones. You can't always guarantee the directory structure from one target environment to another. Also, different containers act differently when using a SYSTEM DOCTYPE tag. You probably are better off not using it. However, if you decide that you need to do so, it should look something like the following: <?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE struts-config SYSTEM "file:///c:/dtds/struts-config_1_1.dtd"> <struts-config> <!--The rest of the struts configuration file goes next --> As you can see, the location of the DTD is an absolute path. If the path of the target environment is not the same, you'll have to modify the XML file. This is why this approach is not recommended. |