Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Apache Struts - From Novice To Professional (2006)

.pdf
Скачиваний:
56
Добавлен:
17.08.2013
Размер:
11.68 Mб
Скачать

278

C H A P T E R 1 9 D E V E L O P I N G P L U G - I N S

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Figure 19-1. A simple hierarchy of entities

Both the Ship and Car entities contain properties from Vehicle. Now, suppose you wanted to create dynamic form beans corresponding to each of these entities. How would you do it? Listing 19-1 shows how.

Listing 19-1. Dynamic Form Bean Declaration for Entity Hierarchy

<form-bean name="Vehicle" type="org.apache.struts.action.DynaActionForm">

<form-property name="class" type="java.lang.String" initial="Unknown Class"/>

<form-property name="height" type="java.lang.Integer" initial="0"/>

<form-property name="width" type="java.lang.Integer" initial="0"/>

</form-bean>

<form-bean name="Ship" type="org.apache.struts.action.DynaActionForm">

<form-property name="class" type="java.lang.String" initial="Ship"/>

<form-property name="height" type="java.lang.Integer" initial="1000"/>

<form-property name="width" type="java.lang.Integer" initial="0"/>

<form-property name="name" type="java.lang.String"/> </form-bean>

C H A P T E R 1 9 D E V E L O P I N G P L U G - I N S

279

<form-bean name="Car" type="org.apache.struts.action.DynaActionForm">

<form-property name="class" type="java.lang.String" initial="Car"/>

<form-property name="height" type="java.lang.Integer" initial="1000"/>

<form-property name="width" type="java.lang.Integer" initial="0"/>

<form-property name="make" type="java.lang.String"/> <form-property name="year" type="java.lang.Integer"/>

</form-bean>

There’s a lot of duplication in the declarations. This becomes much worse if there are more “subentities” of Vehicle. Our goal is to allow Listing 19-1 to be simplified as shown in Listing 19-2.

Listing 19-2. Using the extends Attribute

<form-bean name=".vehicle" type="org.apache.struts.action.DynaActionForm">

<form-property name="class" type="java.lang.String" initial="Unknown Class"/>

<form-property name="height" type="java.lang.Integer" initial="0"/> <form-property name="width" type="java.lang.Integer" initial="0"/>

</form-bean>

<form-bean name=".vehicle.ship" extends=".vehicle"> <form-property name="class" initial="Ship"/> <form-property name="height" initial="1000"/> <form-property name="name" type="java.lang.String"/>

</form-bean>

<form-bean name=".vehicle.car" extends=".vehicle"> <form-property name="class" initial="Car"/> <form-property name="make" type="java.lang.String"/> <form-property name="year" type="java.lang.Integer"/>

</form-bean>

Much better! As you can see, this really cuts down the code duplication. The extends attribute functions like its counterpart in Tiles, where you could “extend” one Tiles definition from another. Notice that I’ve used a dot naming system for the form names, similar to the one used for Tiles.

280

C H A P T E R 1 9 D E V E L O P I N G P L U G - I N S

We’ll want to have a few other things in the DynaForms plug-in:

A “sub form bean” can override both the initial value or the type of a

<form-property>.

DynaForms must place no restrictions on the class type of the form bean. You should be able to declare any form bean with DynaForms.

There should be a mechanism to prevent “ancestor” form beans, which are never used in your struts-config.xml file, from being instantiated. This saves system resources. To accomplish this, I’ll add another attribute called create into <form-bean>. Setting create=false would suppress creation of that form bean. For example,

if we wanted to suppress creation of .vehicle in Listing 19-2, we’d use

<form-bean name=".vehicle" create="false" ....

In summary, apart from the extends and create attributes, the new form bean declarations look just like the original Struts ones.

In addition, I’ll make one big simplification: the new form bean declarations won’t support the <set-property> tag. Adding support for <set-property> is not too difficult. You are encouraged to attempt this on your own at the end of the chapter.

Lastly, we can’t place our new form beans in struts-config.xml itself, since this will require subclassing ActionServlet. Instead, we’ll have to place them in another XML file (or in multiple XML files).

Now that the goals of the DynaForms plug-in are clear, the next step is deciding how to achieve them.

Implementation Road Map

Essentially, the implementation of the DynaForms plug-in would have to

Parse the input XML files (there might be more than one). So the plug-in declaration will require at least a pathnames property, like the Validator framework, which is a comma-separated list of filenames.

Resolve the inherited properties of a form bean.

Somehow get Struts to recognize these as if they had been declared in struts-config.xml.

For the first, I’ll show you how to use the Apache Digester, which is an easy way to read XML files. Digester is also used internally by Struts. The second step is relatively straightforward, but the last requires some knowledge of how Struts reads and declares form beans. We’ll tackle this last step first.

C H A P T E R 1 9 D E V E L O P I N G P L U G - I N S

281

How Struts Processes Form Beans

When Struts first loads, a single instance of ActionServlet is created, which handles all subsequent initialization of Struts, including reading of struts-config.xml. This class is internal to Struts, but the servlet container knows about it since it is declared in web.xml. Listing 19-3 shows the declaration in web.xml. You can also see that the name of the Struts configuration file, struts-config.xml, is passed as an initialization parameter into ActionServlet.

Note In case it isn't clear by now, Listings 19-3, 19-4 and 19-5 are all excerpts from code generously made available by the Apache Software Foundation. The Apache License is available at http://www. apache.org/licenses/LICENSE-2.0.

Listing 19-3. Declaring the ActionServlet and struts-config.xml file in web.xml

<servlet> <servlet-name>action</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> </servlet>

Once the ActionServlet instance is created, its init() function is called to initialize Struts with the information contained in struts-config.xml. The relevant code that actually reads struts-config.xml appears in Listing 19-4.

Listing 19-4. Initializing Struts in init()

public void init() throws ServletException {

...

// Initialize modules as needed

ModuleConfig moduleConfig = initModuleConfig("", config); initModuleMessageResources(moduleConfig); initModuleDataSources(moduleConfig); initModulePlugIns(moduleConfig);

moduleConfig.freeze();

...

}

282

C H A P T E R 1 9 D E V E L O P I N G P L U G - I N S

In Listing 19-4, you should note a few things:

config refers to the filename of the Struts configuration file (/WEB-INF/ struts-config.xml by default).

ModuleConfig is a class that holds all configuration information contained in struts-config.xml. It has several variables, each of which holds information from a different part of struts-config.xml. For example, form bean declarations are held in a variable of ModuleConfig of type FormBeanConfig. Figure 19-2 shows how form bean data is stored in ModuleConfig through the use of FormBeanConfig and

FormPropertyConfig.

Form beans are read in at the function initModuleConfig().

Plug-ins are processed in sequence by initModulePlugIns(). As you will see shortly, all plug-ins will get a chance to interrogate and manipulate the ModuleConfig instance.

The ModuleConfig instance is “frozen” after the struts-config.xml file is read. No changes may be made to a ModuleConfig instance after it has been frozen.

Figure 19-2. ModuleConfig, FormBeanConfig, and FormPropertyConfig

C H A P T E R 1 9 D E V E L O P I N G P L U G - I N S

283

A ModuleConfig instance is passed to our plug-in as soon as it is created. Our DynaForms plug-in will have to create new FormBeanConfig instances based on the new XML form bean declaration files and add them to ModuleConfig. Once this is accomplished, we’re done.

Well, almost! A quick check on initModuleConfig(), which creates a ModuleConfig instance and initializes it with form bean information, shows that form beans whose type are subclasses of DynaActionForm require special handling (see Listing 19-5).

Listing 19-5. Special Treatment for DynaActionForm Subclasses

protected ModuleConfig initModuleConfig(String prefix, String paths) throws ServletException {

/* create ModuleConfig instance, "config" */

/* read form-bean data using Apache Digester */

//Force creation and registration of DynaActionFormClass instances

//for all dynamic form beans we will be using

FormBeanConfig fbs[] = config.findFormBeanConfigs(); for (int i = 0; i < fbs.length; i++) {

if (fbs[i].getDynamic()) { fbs[i].getDynaActionFormClass();

}

}

return config;

}

In Listing 19-5, config refers to the newly created ModuleConfig instance. As the listing shows, once this instance has been filled with form bean information (held by FormBeanConfig instances), each FormBeanConfig instance is interrogated (using getDynamic()) to see if it is a subclass of DynaActionForm. If so, the function getDynaActionFormClass() is called on that FormBeanConfig. All getDynaActionFormClass() does is initialize that DynaActionForm subclass with the <form-property>s stored in FormBeanConfig.

We will have to duplicate this behavior in our DynaForms plug-in.

284

C H A P T E R 1 9 D E V E L O P I N G P L U G - I N S

EXERCISE

Open ActionServlet in Eclipse (or some other IDE) and follow the function calls I’ve described in this section. Also look at ModuleConfig and FormBeanConfig.

Your goal should be to satisfy yourself of the veracity of the statements I’ve made in this section.

Now that you have a global view of how form beans are read and stored, I’ll show you how to create a plug-in.

Anatomy of a Plug-in

A plug-in must implement Struts’ PlugIn interface, which is shown in Listing 19-6.

Listing 19-6. The PlugIn Interface

/********************************************************************** Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

See the License for the specific language governing permissions and limitations under the License.

**********************************************************************/

package org.apache.struts.action;

import javax.servlet.ServletException;

import org.apache.struts.config.ModuleConfig;

public interface PlugIn {

//called after the PlugIn has been initialized using //setters for each property

C H A P T E R 1 9 D E V E L O P I N G P L U G - I N S

285

void init(ActionServlet servlet, ModuleConfig config) throws ServletException;

//called just before system shutdown void destroy();

}

This interface has just two functions:

init() is called when the PlugIn instance is created. Note that this function receives an instance of ModuleConfig.

destroy() is called when Struts shuts down, to allow the plug-in to clean up any system resources (release file handles, close database connections, etc.).

In addition to these two, your implementation of the PlugIn interface will have additional setters for each parameter that you want to pass into your plug-in class. These parameters are set in struts-config.xml in the plug-in’s declaration. For example, the declaration

<plug-in className="com.mycompany.myapp.MyPlugIn" > <set-property property="message" value="hello world"/>

</plug-in>

implies that MyPlugIn has a setMessage() function.

In the following section, I’ll introduce you to DynaFormsPlugIn.

Implementing DynaFormsPlugIn

Listing 19-7 is an implementation of the PlugIn interface for the DynaForms plug-in.

Listing 19-7. DynaFormsPlugIn.java

package net.thinksquared.struts.dynaforms;

import java.io.IOException; import java.util.StringTokenizer;

import javax.servlet.ServletContext; import javax.servlet.ServletException;

286

C H A P T E R 1 9 D E V E L O P I N G P L U G - I N S

import org.apache.struts.action.PlugIn;

import org.apache.struts.action.ActionServlet; import org.apache.struts.config.ModuleConfig;

public class DynaFormsPlugIn implements PlugIn{

protected String _pathnames = "";

public void setPathnames(String pathnames){ _pathnames = pathnames;

}

public void init(ActionServlet servlet, ModuleConfig config) throws ServletException{

ServletContext context = servlet.getServletContext();

StringTokenizer tokenizer =

new StringTokenizer(_pathnames,",");

while(tokenizer.hasMoreTokens()){

String fname = context.getRealPath(tokenizer.nextToken().trim());

try{

DynaFormsLoader loader =

DynaFormsLoaderFactory.getLoader(fname);

loader.load(fname,config);

}catch(NoSuitableLoaderException le){ throw new ServletException(le);

}catch(IOException ioe){

throw new ServletException(ioe); }catch(DefinitionsException de){

throw new ServletException(de);

}

}

C H A P T E R 1 9 D E V E L O P I N G P L U G - I N S

287

}

public void destroy(){/* do nothing */}

}

As you can see, DynaFormsPlugIn contains three functions: a setter for the pathnames property, which is a comma-delimited list of filenames (see the section “Implementation Road Map” earlier). These filenames refer to files that contain the form bean declarations. The destroy() function has a “no-op” implementation (meaning it does nothing).

Most of the action is in the init() function. This function does the following:

Gets a reference to ServletContext. This class is needed to resolve file paths. In our case we have XML files containing form bean declarations, which we have to read. The plug-in-declaration will only specify them relative to the webapp’s root directory, for example, /WEB-INF/form-beans-def.xml. Obviously, this path isn’t enough.

To read the file, we need the full path to the file. This is where ServletContext comes in. It has a getRealPath() function that resolves the full path of a given relative file path.

Gets a DynaFormsLoader instance from the DynaFormsLoaderFactory. DynaFormsLoader is actually an interface that we need to implement. This is good design: if you needed to load form beans with your own DynaFormsLoader, you can. The DynaFormsLoaderFactory determines which loader to use from the <form-beans>’s type attribute. You’ll see how this is done shortly.

The load() function on DynaFormsLoader does the actual work of reading the form bean declarations and adding the necessary FormBeanConfig instances to ModuleConfig.

To summarize, the init() function parses the pathnames variable, using a comma delimiter. Each file’s full path is determined using the ServletContext’s getRealPath() function. This filename is passed to DynaFormsLoaderFactory, which gives us an instance of DynaActionForm. We call load() on this instance to parse and load the form bean declarations into ModuleConfig. The load() function will also resolve the extends properties of each form bean, and run the special processing of DynaActionForms, described in the earlier section “How Struts Processes Form Beans.”

Before we can proceed, you have to know how to read XML files with Apache’s Digester framework. This easy-to-use framework is utilized extensively within Struts. The JAR files for the Digester framework are in the Struts distribution (commons-digester.jar). This is another good reason to use Digester rather than another XML parsing tool (like the excellent JDOM).