by Jason Menard (jason@javaranch.com)
This is the first in a three part series discussing elements of the Jakarta Commons project. The first part discusses the Commons BeanUtils package, the second part will discuss the Commons Digester package, and the final part will demonstrate using these packages together to build a framework for dynamically generating data transfer objects.
The Jakarta Commons project is a warehouse of reusable Java components. Two very useful components in Commons are BeanUtils and Digester. Digester, which I will discuss in the second part of this series, is a set of classes that facilitate the easy mapping of XML to a Java object. BeanUtils, provides easy wrappers around the Java introspection and reflection APIs, facilitating the simple manipulation of JavaBeans and the creation of dynamically generated JavaBeans.
What is a JavaBean? I'll refer you to the Sun JavaBeans API Specification for all the down-and-dirty details, but for our purposes there are a few important things to highlight:
Here is an example of a simple JavaBean representing some business class called User, presumably the user of some system we are working on:
import java.util.Date; public class User { private Date loginDate; private String name; private String password; public User() { } public Date getLoginDate() { return loginDate; } public String getName() { return name; } public String getPassword() { return password; } public void setLoginDate(Date loginDate) { this.loginDate = loginDate; } public void setName(String name) { this.name = name; } public void setPassword(String password) { this.password = password; } public void delete() { // code to delete user from database } public void update() { // code to update user in database } public static User getUser(String name) { // code returning a new User object // populated from the database } } |
If you take a glimpse at the User class you will see it has the common attributes that we associate with JavaBeans. It has getXxx() and setXxx() methods for its properties, and it has the default constructor. Additionally we threw in a couple of business methods that might exist to save the objects state in the database.
The Commons BeanUtils component provides a class called PropertyUtils that supplies methods for manipulating JavaBeans such as our User class. Using PropertyUtils to call the accessor and mutator methods would look something like this:
User user = User.getUser("jason"); String password = (String) PropertyUtils.getSimpleProperty(user, "password"); Date loginDate = new Date(); PropertyUtils.setSimpleProperty(user, "loginDate", loginDate); |
But what's the point? Why not just call the appropriate accessors or mutators? That's a fair question if we know exactly what type of object we are dealing with in advance. That may not always be the case however. Often times in applications we use Data Transfer Objects (DTO) to represent the state of an object in a compact format suitable for serializing and transmitting over a network. Often times a DTO will look a lot like a business object in terms of the properties it exposes, yet it will not possess any business methods. A DTO for the User class might be something like the following:
import java.util.Date; public class UserDTO { private Date loginDate; private String name; private String password; public UserDTO() { } public Date getLoginDate() { return loginDate; } public String getName() { return name; } public String getPassword() { return password; } public void setLoginDate(Date loginDate) { this.loginDate = loginDate; } public void setName(String name) { this.name = name; } public void setPassword(String password) { this.password = password; } } |
When we are using DTOs, often we are copying data back and forth between the DTO and the business object. As this is the case, we might write a method or two to facilitate this copying. Normally we might have to write multiple methods depending on the direction we are copying our data - from DTO to business object, or from business object to DTO. On top of that, we would have to write these transfer methods for every DTO and business object in our system. Using PropertyUtils, we can get away with writing just one method.
public void copyProperties(Object dest, Object orig) { PropertUtils.setSimpleProperty(dest, "loginDate", PropertyUtils.getSimpleProperty(orig, "loginDate")); PropertUtils.setSimpleProperty(dest, "name", PropertyUtils.getSimpleProperty(orig, "name")); PropertUtils.setSimpleProperty(dest, "password", PropertyUtils.getSimpleProperty(orig, "password")); } |
Now using the above method, we could call either copyProperties(user, userDTO) or copyProperties(userDTO, user) as appropriate to achieve the desired results. Recognizing how amazingly useful a method such as the above is, there just so happens to be a method in PropertyUtils already in place for doing just this. Coincidentally, the method is :
PropertyUtils.copyProperties(Object dest, Object orig) |
To copy the properties from a User object "user", to a UserDTO object "userDTO", we can use the following single line of code:
PropertyUtils.copyProperties(userDTO, user); |
That sure beats writing a bunch of code calling several get and set methods, doesn't it?
One thing you may have noticed is that up to this point we have only referred to simple properties, via methods such as PropertyUtils.getSimpleProperty(). A simple property is a property with a single value. Java primitives and Strings are just a couple of examples of simple properties. That may cover you in most situations, but PropertyUtils also offers methods for handling other types of properties. Specifically, there are additional methods for dealing with indexed properties, mapped properties, and nested properties.
Indexed properties maintain an ordered collection of objects that are accessed individually by an integer index value. Arrays are the obvious example, but the BeanUtils package also handles indexed properties that are based on an underlying java.util.List.
If a bean "myBean" has a property:
String[] userNames; |
Then we might access this property in the following manner:
String userName = (String) PropertyUtils.getIndexedProperty(myBean, "userNames", 1); |
Which is analogous to:
String userName = userNames[1]; |
Mapped properties are based on an underlying java.util.Map. A String key retrieves the value of a mapped property.
If a bean "myBean" has a property:
Map users; |
We could access this property using PropertyUtils like so:
User user = (User) PropertyUtils.getMappedProperty(myBean, "users", "jason"); |
This is somewhat analogous to:
User user = (User) users.get("jason"); |
If a property of a bean is itself a JavaBean, that properties of that JavaBean are known as nested properties. A nested property may also be simple, indexed, mapped, or even further nested. Nested properties are accessed using a dotted notation that should be familiar to us.
If we have a bean "myBean" with the following property:
User user; |
We could access the simple property "name" of the property "user" like this:
String userName = (String) PropertyUtils.getNestedProperty(myBean, "user.name"); |
Accessing a nested property of a mapped property:
String userName = (String) PropertyUtils.getNestedProperty(myBean, "users(jason).userName"); |
Accessing a nested property of an indexed property:
String userName = (String) PropertyUtils.getNestedProperty(myBean, "user[1].userName"); |
PropertyUtils also provides generic getProperty() and setProperty() methods that may be used to manipulate any property, whether it is simple, indexed or mapped, and regardless of how deeply nested. For example:
PropertyUtils.getProperty(myBean, "mappedProp(key).indexedProp[1].simpleProp"); |
While the methods described above for dealing with existing beans are certainly very handy, the meat of this package comes in the form of dynamically generated beans, or "DynaBeans". If you've ever used dynamic action forms in Struts, then you have used DynaBeans.
The BeanUtils package has two interfaces for handling dynamic beans: the DynaBean interface, and the DynaClass interface. Concrete implementations of these interfaces are provided in the form of BasicDynaBean and BasicDynaClass classes.
Like standard JavaBeans, DynaBeans have a set of properties that may be accessed or changed. Going back to our earlier example of the User bean, let's say we have a DynaBean with the same set of properties called "user". This is how we would go about getting and setting our properties:
String name = (String) user.get("name"); |
It can't get much easier than that. In addition to the above methods for manipulating simple properties, an object implementing the DynaBean interface must also provide the following methods for manipulating indexed and mapped properties:
public Object get(java.lang.String name, int index) |
In order to generate our own DynaBean classes, we must first provide an implementation of DynaClass. A DynaClass provides the same basic functionality as java.lang.Class for classes that implement the DynaBean interface. It is in a concrete implementation of DynaClass where we must specify the properties of associated DynaBeans, and it is this DynaClass implementation that generates new instances of a DynaBean with the specified properties.
The set of properties a DynaBean has may be described by an array of DynaProperty objects. A DynaProperty has name and type attributes. Instantiating a DynaProperty that is equivalent to the User name property from our example would be done like this:
DynaProperty nameProperty = new DynaProperty("name", String.class); |
A DynaProperty array describing all the attributes from our User bean:
DynaProperty[] properties = { new DynaProperty("loginDate", Date.class), new DynaProperty("name", String.class), new DynaProperty("password", String.class) }; |
Getting back to the DynaClass interface, if we take a look at BasicDynaClass we see the following constructor:
BasicDynaClass(java.lang.String name, java.lang.Class dynaBeanClass, DynaProperty[] properties) |
The name parameter is the name of this DynaBean class, and the properties parameter describes the properties our DynaBean will have. The dynaBeanClass parameter is the class of the implementation of DynaBean we are using. If we use null for the dynaBeanClass parameter, this DynaClass will produce new instances of DynaBeans of type BasicDynaClass. From this information, and with our previously constructed array of DynaProperty objects, we can now construct our DynaClass.
DynaClass userDynaClass = new BasicDynaClass("user", null, properties); |
Let's now put it all together to retrieve and use a new instance of our user DynaBean.
DynaProperty[] properties = { new DynaProperty("loginDate", Date.class), new DynaProperty("name", String.class), new DynaProperty("password", String.class) }; DynaClass userDynaClass = new BasicDynaClass("user", null, properties); DynaBean user = userDynaClass.newInstance(); user.set("name", "jason"); user.set("password", "secret"); user.set("loginDate", new Date()); |
The BeanUtils class has several static methods for manipulating DynaBeans in much the same way that the PropertyUtils class manipulates standard JavaBeans. Methods provided to get and copy properties are the same as in PropertyUtils, however the only mutator provided is the generic setProperty() method. With this in mind, we can copy values from our user DynaBean to or from an instance of the User object we discussed in the first section.
User user = new User(); user.setName("jason"); DynaBean dynaUser = userDynaClass.newInstance(); BeanUtils.copyProperties(dynaUser, user); System.out.println(dynaUser.get("name")); // displays 'jason' |
One additional method of note in BeanUtils allows the copy of a set of mapped properties into a DynaBean, using the populate() method. If the map has a key that corresponds to the name of a DynaBean property, it will attempt to copy it. Let's take a look at the following example:
Map map = new HashMap(); map.put("name", "jason"); map.put("password", "secret"); map.put("loginDate", new Date()); DynaBean user = userDynaClass.newInstance(); BeanUtils.populate(user, map); System.out.println(user.get("name")); // displays 'jason' |
Type conversion is handled behind the scenes using methods defined in a class called ConvertUtils. You may write your own converter by implementing the Converter interface and then calling the ConvertUtils.register() method in order to register your new Converter.
Let's do something useful with what we've learned. Here's a method that will take a HttpServletRequest object and return a populated DynaBean dynamically generated from the names and values of the request parameters.
public DynaBean createFromRequest(String dynaBeanName, HttpServletRequest req) throws Exception { HashMap map = new HashMap(); ArrayList list = new ArrayList(); Enumeration enum = req.getParameterNames(); while (enum.hasMoreElements()) { String name = (String)enum.nextElement(); String[] values = req.getParameterValues(name); Class type = null; if (values.length == 1) { type = String.class; } else { type = String[].class; } DynaProperty prop = new DynaProperty(name, type); list.add(prop); map.put(name, values); } DynaProperty[] properties = (DynaProperty[])list.toArray(new DynaProperty[0]); DynaClass dynaClass = new BasicDynaClass(dynaBeanName, null, properties); DynaBean dynaBean = dynaClass.newInstance(); BeanUtils.populate(dynaBean, map); return dynaBean; } |
Hopefully I've demonstrated just how handy this package can be. Be sure to check out the API documentation for more information, particularly the documentation for the org.apache.commons.beanutils package. This is just one of many useful components of the Jakarta Commons project, and next month we will take a look at another.
Please feel free to email me with any questions or comments concerning this article.