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

Beginning Apache Struts - From Novice To Professional (2006)

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

288

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

Reading XML with Apache’s Digester

Apache’s Digester framework gives you a simple way to read portions of an XML document. The data that is read in is stored in JavaBeans that you supply to Digester.

Usually, you have to define one JavaBean class for each type of tag being read, but this isn’t a requirement. You can have one JavaBean for more than one tag. But in this chapter, we’ll use one JavaBean per tag.

You also have to instruct Digester which portions of the XML file you want to read. These portions are stored in your JavaBeans objects. For example, suppose your XML file, which is called myfile.xml, contains just the tag shown here:

<user id="albert" password="relativity" desc="Albert Einstein" />

Now you want to read this data into the User JavaBean shown in Listing 19-8.

Listing 19-8. The User Bean

public class User{

protected String _id = null; protected String _password = null;

public void setId(String id){ _id = id; } public String getId(){ return _id; }

public void setPassword(String pwd){ _password = pwd; } public String getPassword(){ return _password; }

}

Notice that the desc attribute isn’t stored. Listing 19-9 shows how to do it with Digester.

Listing 19-9. Using Digester to Create a Single User

//Step 1: Create new Digester instance: Digester digester = new Digester();

//Step 2: Associate tag <user> with User bean: digester.addObjectCreate("user", "User");

//Step 3: Tell Digester to read in all

// required properties into User bean: digester.addSetProperties("user");

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

289

//Step 4: Actually parse the file

User u = (User) digester.parse(new File("myfile.xml"));

//Step 5: Our User bean contains the data from the XML file! System.out.println("ID=" + u.getId() + " pwd=" + u.getPassword());

The parse() function on the Digester class (org.apache.commons.digester.Digester) parses the input XML file and returns a populated User instance. There are two functions you should take special note of:

addObjectCreate(path, bean): Takes a path in the XML file and a JavaBean that will get populated based on that path. In our earlier example, the path is just user, which we want to read from and return a populated User object.

addSetProperties(path): This tells Digester that you want to read the attributes of the tag indicated by the given path into the JavaBean you associated with that path using addObjectCreate(). So, in our previous example, if you omitted addSetProperties(), the returned User bean would contain no information.

Handling more than one user declaration is also easy. For example, suppose now we amended our myfile.xml as shown in Listing 19-10.

Listing 19-10. The Amended XML File, with Multiple Users

<users>

<user id="albert" password="relativity" desc="Albert Einstein" /> <user id="marie" password="radioactivity" desc="Marie Curie" /> <user id="paul" password="qed" desc="Paul Dirac" />

</users>

To read in all the Users on this file, we could store them into a Users class, as Listing 19-11 shows.

Listing 19-11. The Users Bean

public class Users extends java.util.ArrayList{

public void addUser(User u){ add(u);

}

public User getUser(int i){ return (User) get(i);

}

}

290

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

As you can see in Listing 19-11, the Users bean is just a collection of User beans. We want Digester to create User beans based on the new XML (see Listing 19-8) and place them into the containing Users bean. To do this, we’ll have to amend Listing 19-9 as shown in Listing 19-12.

Listing 19-12. Using Digester to Create Multiple Users

//Step 1: Create new Digester instance: Digester digester = new Digester();

//Step 2: Associate <users> tag with Users class. digester.addObjectCreate("users", "Users");

//Step 3: Associate <user> tag with User bean.

//Note that the path to the <user> tag is

//indicated. digester.addObjectCreate("users/user", "User");

//Step 4: Tell Digester to read in all

// required properties into User bean: digester.addSetProperties("users/user");

//Step 5: Tell Digester to put all newly created

//User beans into the Users class by calling

//Users.addUser(aNewUserbean) digester.addSetNext("users/user", "addUser");

//Step 6: Actually parse the file

Users users = (Users) digester.parse(new File("myfile.xml"));

//Step 7: Print out each User in the Users bean: for(int i = users.size() - 1; i >= 0; i--){

User u = users.getUser(i); System.out.println("ID=" + u.getId() +

" pwd=" + u.getPassword());

}

Apart from the obvious changes to the paths and the bean returned by parse(), the most significant change is calling Digester’s addSetNext(path, myFunction) function.

When Digester finishes parsing a single <user> tag, it asks “What’s next?” This answer is given by the information you supplied in addSetNext(). Digester will call myFunction() on the object associated with the parent of the path defined by addSetNext(). The argument

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

291

of myFunction() is the latest object created. This is the key to understanding how addSetNext() works, and it can be a little confusing, so let’s take it one step at a time.

Looking again at Listing 19-12, the instruction

digester.addObjectCreate("users", "Users");

maps the path /users (I’ve put in the leading / to indicate it’s a path) to the class Users. So, each time Digester meets a /users tag, it creates a new Users object. Let’s jump forward:

digester.addSetNext("users/user", "addUser");

This tells Digester that after it has parsed the tag with the path /users/user (and therefore created a User bean), it has to call the addUser() function on the object associated with the parent path of /users/user. The parent path of /users/user is obviously /users, and we know the object associated with the /users path is the Users object. So, the function addUser() is called on this Users bean, with the latest User as the argument of addUser().

Note that there’s no need to implement a loop in order to read each User. Each time the Digester meets a path matching one you declared using addObjectCreate(), addSetProperties(), or addSetNext(), it takes the appropriate action indicated by

the function.

This section only skims the surface of the Digester framework. If you’re interested in learning more, consult the many articles on the Internet describing this useful framework in more detail.

We next take a look at the implementation of DynaFormsLoaderFactory, the class that creates a concrete implementation of DynaFormsLoader.

Implementing DynaFormsLoaderFactory

Listing 19-13 shows how DynaFormsLoaderFactory is implemented.

Listing 19-13. DynaFormsLoaderFactory.java

package net.thinksquared.struts.dynaforms;

import java.io.IOException; import java.io.File;

import org.apache.commons.digester.Digester;

import net.thinksquared.struts.dynaforms.definitions.Beans;

public class DynaFormsLoaderFactory{

private DynaFormsLoaderFactory(){}

292 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

public static DynaFormsLoader getLoader(String filename)

throws IOException,DefinitionsException,NoSuitableLoaderException{

Beans config = readConfig(filename);

String clazzname = config.getType();

if( null == clazzname ){

return new DefaultDynaFormsLoader();

}else{

try{

return (DynaFormsLoader) instantiate(clazzname); }catch(Exception e){

throw new NoSuitableLoaderException("Can't initialize loader " + clazzname);

}

}

}

protected static Beans readConfig(String filename) throws IOException,DefinitionsException{

try{

Digester digester = new Digester(); digester.addObjectCreate("form-beans",

"net.thinksquared.struts.dynaforms.definitions.Beans");

digester.addSetProperties("form-beans");

return (Beans)digester.parse(new File(filename));

}catch(IOException ioe){ throw ioe;

}catch(Exception e){

throw new DefinitionsException("Definitions file " + filename +

" has incorrect format");

}

}

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

293

protected static Object instantiate(String clazzname) throws Exception{

return Class.forName(clazzname).newInstance();

}

}

First, notice that the constructor has only private access. This is a design pattern called Singleton and it prevents DynaFormsLoaderFactory from being instantiated. Instantiating DynaFormsLoaderFactory is a waste of resources, since all the action goes on in static functions on this class.

THE SINGLETON DESIGN PATTERN

In its original formulation, the Singleton design pattern was a solution to the problem of ensuring that only one instance of a class was made. But the essence of the Singleton design pattern is that it allows you to control the number of instances of a given class, hence my use of this term in the text.

To do this, all constructors of the class are marked as private, so the class cannot be directly instantiated using the new keyword. Instead, the class provides a static function (usually called getInstance()) that controls instantiation of the class. The following code gives a skeleton of a typical singleton:

public class MySingleton{

private static MySingleton _self = new MySingleton();

private MySingleton(){

/* initialization code here */

}

public static MySingleton getInstance(){ return _self;

}

}

Notice that I’ve had to create the _self instance at the start. One other option is to test for a _self == null in getInstance() and only then create the MySingleton instance. This approach might fail in a multithreaded application, unless getInstance() is marked as synchronized.

The only public function is getLoader(), which returns a concrete implementation of DynaFormsLoader. This function takes as a parameter the filename of the form beans declaration file. With

Beans config = readConfig(filename);

294

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

the input file is parsed using the Digester framework. The Beans class is a JavaBean class, which we will come to later.

A look at the readConfig() function shows that only the root <form-beans> is read, in order to determine the type attribute. If the type attribute is not present, the factory returns an instance of DefaultDynaFormsLoader to load the form beans. You can specify your own implementation of DynaFormsLoader by putting in a value for type:

<form-beans type="com.mycompany.myapp.MyDynaFormsLoader"> ...

This flexibility can come in handy when you want to add new functionality to the DynaForms plug-in, like handling <set-property> tags.

In the next section, we’ll delve into the details of the default implementation of

DynaFormsLoader.

DefaultDynaFormsLoader

DefaultDynaFormsLoader (see Listing 19-14) reads the XML file using the Digester framework, parsing the data into a Beans class (see Listing 19-15), which holds all the form bean declarations from a single file.

Listing 19-16 describes the Bean class that holds information from a single <form-bean> declaration. It also has a function called merge() that creates properties on a “sub form bean,” which have been declared in the parent form bean. Listing 19-17 describes the Property class, which holds the information from a single <form-property> declaration.

Listing 19-14. DefaultDynaFormsLoader.java

package net.thinksquared.struts.dynaforms;

import java.io.IOException; import java.io.File;

import java.util.Iterator;

import org.apache.struts.config.ModuleConfig; import org.apache.struts.config.FormBeanConfig; import org.apache.struts.config.FormPropertyConfig; import org.apache.commons.digester.Digester;

import net.thinksquared.struts.dynaforms.*;

import net.thinksquared.struts.dynaforms.definitions.*;

public class DefaultDynaFormsLoader implements DynaFormsLoader{

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

295

public void load(String filename,ModuleConfig config) throws IOException, DefinitionsException{

Beans beans = readBeans(filename);

for(Iterator beanz = beans.values().iterator(); beanz.hasNext();){ Bean b = resolve(beans, (Bean)beanz.next());

if(b.getCreate()){

config.addFormBeanConfig(createFormBeanConfig(b));

}

}

}

protected Beans readBeans(String filename) throws IOException, DefinitionsException{

try{

Digester digester = new Digester();

digester.addObjectCreate("form-beans", "net.thinksquared.struts.dynaforms.definitions.Beans");

digester.addSetProperties("form-beans");

digester.addObjectCreate("form-beans/form-bean", "net.thinksquared.struts.dynaforms.definitions.Bean");

digester.addSetProperties("form-beans/form-bean");

digester.addObjectCreate("form-beans/form-bean/form-property", "net.thinksquared.struts.dynaforms.definitions.Property");

digester.addSetProperties("form-beans/form-bean/form-property");

digester.addSetNext("form-beans/form-bean/form-property", "addProperty");

digester.addSetNext("form-beans/form-bean","addBean");

return (Beans)digester.parse(new File(filename));

296 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

}catch(IOException ioe){ throw ioe;

}catch(Exception e){

throw new DefinitionsException("Definitions file " + filename +

" has incorrect format.");

}

}

protected Bean resolve(Beans beans, Bean b) throws DefinitionsException{

if(b.getExtends() == null){

//check that all properties have types Object[] properties = b.values().toArray(); for(int i = 0; i < properties.length; i++){

Property p = (Property) properties[i]; if(p.getType() == null){

throw new DefinitionsException("Type of property '" + p.getName() +

"' has not been set on form bean '" + b.getName() +

"'");

}

}

return b;

}

Bean parent = beans.getBean(b.getExtends()); if(parent == null){

throw new DefinitionsException(

"Can't resolve parent bean named " + b.getExtends());

}

b.merge(parent);

//resolve this bean further up the hierarchy return resolve(beans,b);

}

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

297

protected FormBeanConfig createFormBeanConfig(Bean bean){

FormBeanConfig config = bean.getFormBeanConfig();

//add in all the form-properties for this form-bean for(Iterator ps = bean.values().iterator(); ps.hasNext();){

Property p = (Property) ps.next(); config.addFormPropertyConfig(p.getFormPropertyConfig());

}

//Force creation and registration of DynaActionFormClass //instances for all dynamic form beans if(config.getDynamic()){

config.getDynaActionFormClass();

}

return config;

}

}

Listing 19-15. Beans.java

package net.thinksquared.struts.dynaforms.definitions;

import java.util.*;

public class Beans extends HashMap{

protected String _type = null; //Loader classname

public void addBean(Bean b){ put(b.getName(),b);

}

public Bean getBean(String name){ Object obj = get(name);

return (obj == null)? null : (Bean)obj;

}

public void setType(String t){ _type = t;

}