XMLBeans Bind XML to the Future
Discover a methodology for developing a Web service client from a WSDL to manage inventory in a database
by David Hritz
December 10, 2004
Beehivethe wizard behind much of Workshop's magichas reached the incubation phase at the Apache Software Foundation. However, before Beehive was even a buzz in our ears, BEA submitted another project to Apache: XMLBeans. As of June 2004, XMLBeans has been accepted as a full-fledged Apache Software Foundation Project.
XMLBeans is an XML-Java binding tool. Yes, it is yet another XML-Java binding tool, but it does come with some new perks. XML has proven to be a powerful and useful format for structured data. Current XML-Java binding tools have their benefits and pitfalls, but each could be considered somewhat tedious to use. XMLBeans has overcome this tedium by mapping the XML instance, and underlying schema, to JavaBean-like objects. XML instance data can now be navigated, retrieved, and manipulated through getter and setter accessor methods, an API commonality that any developer can use with ease.
XMLBeans are created through a compilation of XML Schemas. XML Schema is a language used to provide guidelines for XML documents. A schema comprises the rules and regulations that a particular XML document must follow to be considered valid. XML Schemas can become rather complex, depending on the XML instance that they describe. This complexity is nothing to XMLBeans; no matter how complex, your schema can be compiled into XMLBeans.
We'll discus an inventory management system for a fictional automobile dealership written entirely with XMLBeans that demonstrates not only the use of XMLBeans within Web services, but also their use as data objects throughout the process, from the database to the user interface. We'll highlight the important aspects of this application, but you can download the source code for the entire application. This sample application relies on a single database table, which will store our inventory. Most columns are straightforward, except for the last. The STATUS column holds one of two values, new or used, and will be used later to filter our results (see Table 1).
Create a Schema
XMLBeans originate from the compilation of an XML Schema. The inventory management Web service will return a list of vehicles from the table, and we'll need to create an XML Schema to reflect this. Just like JDBC maps SQL types to Java types, XMLBeans map schema types to Java types. By looking at the table's structure, you can imagine that we are dealing with only two types of data. In the Java world these types are String and int (price is an int because most automobile prices do not list cents, but we could use float as well). In the schema world, these types are also string and int. Although in our example we use simple types, XMLBeans can handle any schema type. Listing 1 shows the completed XML Schema. Notice that there are two top-level elements definedvehicleList, which is simply a list of the second element, vehicle. The vehicle element contains node definitions for each column in our inventory table.
Now that an XML Schema has been created, it must be compiled into XMLBeans. If you are using the XMLBeans project, which can be downloaded from Apache, then this compilation can be performed through the Schema Compiler utility, or the special xmlbean Ant task. The scomp utility found in the bin directory of your XMLBeans distribution can be used to compile schemas in a variety of ways. (Refer to the usage documentation for this utility for more information.) The xbean.jar file, located in the lib directory of your XMLBeans distribution, is the container for the xmlbean Ant task. Schema compilation through the Ant task has the obvious benefit of allowing us to include the schema compilation with our other usual build tasks.
If you're using WebLogic Workshop, you can simply create a schema project within your application and import your schema. When using a schema project, compilation occurs automatically each time a schema is imported or modified within the project. Figure 1 shows the result of importing our schema into a schema project. An object has been created with the suffix Document for each top-level element. These objects represent the entire element or type defined in our schema. They are factories that provide an entry point for access, manipulation, and creation of XML that conforms to the given type. The XML type is represented by the nested factory classes: VehicleListDocument.VehicleList and VehicleDocument.Vehicle. These classes give access to the XML instance data itself.
Our inventory management service will provide a few operations. One of these functions will be to return all vehicles from our inventory table. The other two functions allow a client either to update or create vehicles within the inventory. Each task communicates solely using our XMLBeans.
Task Management
We need to create a database control to manage these tasks. This control must provide a method to select the vehicle information from the database, return a list of vehicles, and provide methods that accept vehicle information to update or insert into the database. Normally, we would create a value object and use this object as the return type and argument type of our control's methods. However, since we have modeled our XMLBeans after the inventory table, and we know that we will be using these objects within our service, we can simply retrieve the information directly into our XMLBeans, streamlining our entire service. There is a portion of the database control in this code snippet that shows a getAllVehicles() method simply returning an array of VehicleDocument.Vehicles and the insertVehicle() method that accepts a VehicleDocument.Vehicle object as an argument:
/**
* @jc:sql statement="SELECT *
* FROM INVENTORY"
*/
VehicleDocument.Vehicle[]
getAllVehicles();
/**
* @jc:sql statement="INSERT INTO
* INVENTORY VALUES
* ({vehicle.vin},
* {vehicle.make},
* {vehicle.model},
* {vehicle.year},
* {vehicle.price},
* {vehicle.status})"
*/
void insertVehicle(
VehicleDocument.
Vehicle vehicle);
Because of the getter and setter accessor methods of our XMLBeans, it can be used directly in the SQL expression exactly as if it were a value object. Our Web service will use this database control to retrieve and maintain inventory information. While the control works with the nested classes that represent the XML instance document, Web services cannot transmit inner classes in this manner; they must work solely with the outer factory classes. This situation is a minor issue. The insert and update methods will receive a VehicleDocument as an argument; to retrieve the inner VehicleDocument.Vehicle class, we'll use the accessor method: getVehicle(). The insertVehicle() method of our Web service looks like this:
public void insertVehicle(
VehicleDocument vehicleDoc)
{
VehicleDocument.Vehicle
vehicle = vehicleDoc.
getVehicle();
vehicleControl.
insertVehicle(vehicle);
}
The methodology for updating an item in the inventory is mostly the same, except for the SQL expression. Retrieving the XML instance representation from the factory is a one-step operation. Retrieving the inventory is a little more complicated, but not by much. Our control returns an array of VehicleDocument.Vehicles. The Web service, on the other hand, will return a VehicleListDocument. Luckily, a VehicleListDocument is merely a factory class that represents an array of VehicleDocument.Vehicle objects. All we need to do is create a new VehicleListDocument, use it to create a new VehicleListDocument.VehicleList, and assign to it our retrieved VehicleDocument.Vehicle array:
public VehicleListDocument
findAllVehicles()
{
VehicleDocument.Vehicle[]
vehicles = vehicleControl.
getAllVehicles();
VehicleListDocument
vehicleListDoc =
VehicleListDocument.
Factory.newInstance();
VehicleListDocument.
VehicleList vehicleList =
vehicleListDoc.
addNewVehicleList();
vehicleList.setVehicleArray(
vehicles);
return vehicleListDoc;
}
XMLBeans objects can be used to create new XML instances quickly and easily.
After the Web service has been created, we can generate the Web Services Description Language (WSDL) file and then use it to create a Web service client. When generating the client, Workshop recognizes automatically that our XMLBeans match the methods within the service (see Figure 2). This notification may seem like no great feat because we did develop the Web service to specify returning our XMLBeans, and at this point I'd have to agree. However, we'll soon discuss this same notification, but in a much greater circumstance.
The Web Service Client
Our PageFlow will rely on the service client to manage the vehicle inventory. The PageFlow should provide a user with the ability to view all items in the inventory and modify and create items. Also, to further demonstrate the power of XMLBeans, we will also allow the user to filter the results based on the status (new or used) of the vehicles. This filter, behind the scenes, will use XPath to select the appropriate vehicles. XPath is a syntax that is used to locate specific elements within XML documents based on a certain criterion. We will discuss the major pieces of functionality involved within the PageFlow shortly. (The entire source code is available online.)
First, let's examine displaying the inventory to the user. We know that our inventory service is returning a VehicleListDocument, and we also know that this object is basically a representation of a VehicleDocument.Vehicle array. That array would be the perfect object to use for our display. In the steps for retrieving the VehicleDocument.Vehicle array from our VehicleListDocument, the inventory variable is declared within PageFlow scope so that it will be accessible from within our JavaServer Pages (JSP):
vehicleListDoc = vehicleInventory.
findAllVehicles();
inventory = vehicleListDoc.
getVehicleList().
getVehicleArray();
The VehicleDocument.Vehicle[] array allows us to display the entire inventory with ease. Now we have an array of objects ready to be displayed. I'm sure that we are all used to using JavaBeans and NetUI tags in these instances to display lists of data. Within the NetUI tags, we would specify the property names to display, and internally through reflection, the appropriate accessor methods would be called. Because of the value-object-like properties of XMLBeans, we can utilize the VehicleDocument.Vehicles as we would JavaBeans and display them just the same.
On our JSP, we simply need to drag a NetUI repeater tag from the palette into our edit pane to display the Repeater wizard. In the Data Source box, specify pageFlow.inventory and click Create (see Figure 3). This step is the only one necessary in displaying our inventory (see Listing 2 for the generated repeater code block). Notice that each item in the container is a VehicleDocument.Vehicle. Although this object is a representation of an XML instance document, the subelements of the vehicle are easily accessed by name.
For the update and insert portion of the inventory management system, create a form bean and data-entry pages as you would normally. Upon submission of a modified or new vehicle, we will need to use the submitted form bean to create a VehicleDocument, the expected argument to our inventory management service. Let's explore this task in relation to inserting a new vehicle.
In our example, not only will we be using our service to insert the new vehicle into the database, but we'll also update the inventory array that we are using to display our data, so that the new vehicle is displayed without having to retrieve a new VehicleListDocument from our service. In real-world situations, you may want to re-retrieve after each update, ensuring that you have a more up-to-date inventory list.
Update Inventory
Our first step is to use our originally retrieved VehicleListDocument to obtain the VehicleListDocument.VehicleList and add a new VehicleDocument.Vehicle to it:
VehicleDocument.Vehicle vehicle =
vehicleListDoc.getVehicleList().
addNewVehicle();
Next we will use the form bean accessor methods in combination with the XMLBeans accessor methods to set the properties of the VehicleDocument.Vehicle:
vehicle.setMake(form.getMake());
vehicle.setModel(form.getModel());
vehicle.setYear(form.getYear());
vehicle.setPrice(form.getPrice());
vehicle.setVin(form.getVin());
vehicle.setStatus(
form.getStatus());
Now that our XMLBeans contain the values from the form, we'll create a new instance of a VehicleDocument, set our newly created VehicleDocument.Vehicle, and call the appropriate method of our service, passing to it the VehicleDocument:
VehicleDocument vehicleDoc =
VehicleDocument.Factory.
newInstance();
vehicleDoc.setVehicle(vehicle);
vehicleInventory.insertVehicle(
vehicleDoc);
Finally, update the inventory array that is used for display, since we have just added a new vehicle to it:
inventory = vehicleListDoc.
getVehicleList().
getVehicleArray();
Updating items in our inventory is almost the same as inserting them. The only difference is we retrieve the VehicleDocument.Vehicle object from our inventory array, rather than adding to it, since we are modifying an existing vehicle. XPath can be used to filter the results displayed in our inventory management system. The last piece of functionality to explore within our inventory management system is to filter results based on a vehicle's status, which is either new or used.
The first step will be to construct an XPath query. We'll concentrate on a query for new vehicles that is made up of two parts: a namespace declaration and a path-based query. In our case, referring back to our original schema, we can see that our namespace is http://example/schema. Our query begins:
private static final String
NEW_QUERY =
"declare namespace xq=
'http://example/schema'"
Next, we'll need to construct the query portion. We want a query that will retrieve all vehicle elements whose status is new. The XPath equivalent is:
$this/xq:vehicleList/xq:
vehicle[status='New']
We add this query to our namespace declaration to form:
private static final String
NEW_QUERY =
"declare namespace xq=
'http://example/schema'" +
"$this/xq:vehicleList/xq:
vehicle[status='New']";
In our action method, we'll use this query to filter our inventory, which can be accomplished by using:
XmlObject[] vehicles = (
XmlObject[])vehicleListDoc.
selectPath(NEW_QUERY);
if (vehicles.length > 0) {
inventory = (
VehicleDocument.
Vehicle[])vehicles;
}
Starting with the VehicleListDocument originally received from our service, we use our query to select the path defined by that query. The selectPath() method returns an array of XMLObjects, the base class of all XMLBeans. Since we know that we have queried for VehicleDocument.Vehicles, we can safely cast to our specific XMLBeans type and update our inventory array. Upon redisplay, we will be viewing only vehicles that satisfied our query.
Leveraging Choice
So far, we have seen that integration of XMLBeans with our Web services, PageFlows, and even database controls is relatively easy. In this case, we had the opportunity to develop our own service, dictating that it would return the XMLBeans of our choice. However, many times this is a courtesy that is not available, and we find ourselves integrating external services into our applications. An ever-growing number of Web service publishersincluding companies like Yahoo!, Google, eBay, and Amazonallow us to integrate with their well-known services. It is our task to leverage these services within our applications, having access only to the associated WSDL that describes the service.
Although it may sound like a different situation, in actuality it is quite the same. We can compile this WSDL file into XMLBeans, just as we did our VehicleList.xsd file. Once our XMLBeans are available, we can generate the Web service client from the WSDL, and Workshop will once again realize that we have types associated with the methods of the Web service and use our XMLBeans. In this case, XMLBeans give us a great advantage over other methodologies, especially since all of our needed data objects have been created for us with a few clicks of a mouse. Hopefully, the example presented here has opened your eyes to some of the possibilities available with XMLBeans, and you'll be encouraged to experiment with them in your own applications.
About the Author
David Hritz is strategic technology director of smart innovative solutions and a coauthor of Creating Web Portals with BEA WebLogic (APress, March 2003). Contact David at dhritz@smart-isolutions.com.
|