Original URL: https://www.theregister.com/2007/04/23/practical_example_sdo/

Implementing platform independent data with SDO

A practical example of using Service Data Objects

By John Hunt

Posted in Software, 23rd April 2007 07:02 GMT

Hands on In my last column, we looked at the concepts behind SDO or Service Data Objects. SDO is a language independent format for representing data. It is built around the concept of a DataGraph of DataObjects. The DataObjects hold both the actual data being represented and metadata that describes that data. As such, it represents self-describing, platform independent, data. It also represents a separation between the data (in the DataGraph) and the logic that is applied to that data (within an application that uses the SDO).

In this column, we will look at a concrete example of a SDO DataGraph, how a Data Mediation Service (or DMS) might generate a DataGraph and how a DataGraph may be manipulated and stored. We will also look at how custom DataObject classes can be created based on an XSD definition (conceptually these could also be generated form Java interfaces but this functionality is not yet available. We will conclude by looking at the relationship between SDO and SCA (or Service Component Architecture) as well as SDO and JavaBeans.

SDO Implementation

As was mentioned in the last column, SDO is actually a specification (currently managed by the Open SOA organisation but soon to be managed by OASIS and the JCP process). You must obtain an implementation of SDO from an appropriate Application Server vendor (such as IBM with WebSphere or BEA with WebLogic). Alternatively, you can use the Apache Tuscany SDO implementation – which is the version used here.

To get the SDO implementation you should download the Tuscany version appropriate for your platform. From this file, you need to extract the lib jar files that contain the SDO specification as Java interfaces and the Tuscany implementation of those interfaces. In my case, using Windows, I created a lib directory within an Eclipse project containing the following files:

Note that some of these files relate to frameworks used within Tuscany’s implementation of SDO (such as the Eclipse EMF jars).

SDO Example

The simple SDO example presented below is comprised of two classes, the EmployeeDMS and the EmployeeSDOTestClient. The EmployeeDMS illustrates a very simple DMS that is used to create and receive a simple Employee DataGraph. The EmployeeSDOTestClient is a very simple test client that drives the DMS.

The EmployeeDMS

We will first look at the EmployeeDMS that provides a very simple example of how a DMS might work when used with an application that might consume or produce a simple Employee DataGraph. The EmployeeDMS class is presented below:

package com.cramer.sdo.dms;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.tuscany.sdo.util.SDOUtil;

import commonj.sdo.DataGraph;
import commonj.sdo.DataObject;
import commonj.sdo.helper.DataFactory;
import commonj.sdo.helper.XMLDocument;
import commonj.sdo.helper.XMLHelper;
import commonj.sdo.helper.XSDHelper;

public class EmployeeDMS {

        public static final String XSD_FILE = "employee.xsd";
        public static final String NAMESPACE = 
                                "http://www.theregister.com";
        public static final String XML_DIR = "xml/data/";

        public EmployeeDMS() throws Exception {
                System.out.println("Creating EmployeeDMS");
                defineSDOTypes();
        }

        private void defineSDOTypes() throws Exception {
                System.out.println("Defining SDO Types for DMS");
                String xsdFilename = XML_DIR + XSD_FILE;
                File file = new File(xsdFilename);
                if (file.exists()) {
                        System.out.println("Type Def'ns gen'd from: " 
                                          + xsdFilename);
                        InputStream is = new FileInputStream(file);
                        XSDHelper.INSTANCE.define(is, null);
                        is.close();
                } else {
                        System.out.println("File Not Found: " 
                                            + xsdFilename);
                }
        }

        public DataGraph getEmployee() throws Exception {
                System.out.println("Programmatically Creating Data Objects");

                DataGraph dataGraph = SDOUtil.createDataGraph();
        
                DataObject employee = 
              DataFactory.INSTANCE.create(NAMESPACE, "EmployeeType");

                employee.setString("name", "John Hunt");
                employee.setInt("employeeId", 34255);

                DataObject address = 
                          employee.createDataObject("address");
                address.set("country", "GB");
                address.set("street", "12 No Street");
                address.set("city", "Nowhere");
                address.set("county", "Wiltshire");
                address.setString("postcode", "SNnn nJG");

                System.out.println("Created Data Objects");
                
                SDOUtil.setRootObject(dataGraph, employee);

                return dataGraph;
        }
        
        public void saveEmployeeToFile(DataGraph dg, String filename) throws IOException {
                System.out.println("Saving SDO objects to file");
                filename = XML_DIR + filename;
                OutputStream stream = new FileOutputStream(filename);
                DataObject employee = dg.getRootObject();
                XMLHelper.INSTANCE.save(employee, 
                                    NAMESPACE, 
                                    "Employee", 
                                    stream);
                System.out.println("Saved to file " + filename);
        }

        private static final String XML_STRING = 
            "<?xml version=\"1.0\" encoding=\"ASCII\"?>" +
            "<regdeveloper:Employee xmlns:regdeveloper=\"http://www.theregister.com\">" +
                      "<name>John Hunt</name>" +
                      "<address country=\"GB\">" +
                         "<street>12 No Street</street>" +
                         "<city>Nowhere</city>" +
                         "<county>Wiltshire</county>" +
                         "<postcode>SNnn nJG</postcode>" +
                      "</address>" +
                      "<employeeId>34255</employeeId>" +
            "</regdeveloper:Employee>";

        public DataGraph getEmployeeFromString() {
                DataObject employee = 
                 XMLHelper.INSTANCE.load(XML_STRING).getRootObject();
                DataGraph dataGraph = SDOUtil.createDataGraph();
                SDOUtil.setRootObject(dataGraph, employee);
                return dataGraph;
        }

}

The EmployeeDMS is used to create an Employee DataGraph (containing a single Employee DataObject) in a number of ways.

The method defineSDOTypes uses an XSD file to define the types for the objects within the DataGraph. Conceptually the types can be defined in a number of ways including via Java classes. In my example, the XSD file defines the types for use within the DataGraph as this is potentially a useful way of doing this for an SOA system. The XSD file is presented below. As you can see form this file, the XSD is very simple and is intended merely to illustrate the concept:

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns="http://www.theregister.com" targetNamespace="http://www. regdeveloper.com">

    <xsd:element name="Employee" type="EmployeeType"/>
    <xsd:element name="comment" type="xsd:string"/>

    <xsd:complexType name="EmployeeType">
        <xsd:sequence>
            <xsd:element name="name" type="xsd:string"/>
            <xsd:element name="address" type="Address"/>
            <xsd:element name="employeeId" type="xsd:int"/>
        </xsd:sequence>
    </xsd:complexType>

    <xsd:complexType name="Address">
        <xsd:sequence>
            <xsd:element name="street" type="xsd:string"/>
            <xsd:element name="city"   type="xsd:string"/>
            <xsd:element name="county"  type="xsd:string"/>
            <xsd:element name="postcode"    type="xsd:string"/>
        </xsd:sequence>
        <xsd:attribute name="country" type="xsd:NMTOKEN" fixed="GB"/>

    </xsd:complexType>

 </xsd:schema> 

The data model define by the Schema is very simple; essentially an Employee object can have a name, an address and an employeId. A name is a string, an employeId is an integer and an address is another class that contains a street, city, county, postcode and country. Note that the method defineSDOTypes must be run before any DataObjects are created otherwise an exception will be thrown, as the current SDO environment will have no knowledge of the data types for the specified namespace.

The getEmployee method can then be used to programmatically create a very simple DataGraph containing one single DataObject that defines details about the employee “John Hunt”, including his employeeId, and address. Note that one of the utility classes provided in the Tuscany implementation is used to create the DataGraph and to allow the root object of the DataGraph to be set. It is possible to do both of these with the underlying SDO Core API but it is more complex than using the SDOUtilclass.

In contrast to the getEmployee method, the getEmployeeFromString method creates a DataGraph containing a single DataObject using an XML document defined within a string (this could also have been defined in an XML document). This illustrates how an XML payload may be converted into SDO objects with minimal intervention from the programmer.

Finally, the method saveEmployeeToFile take a DataGraph and a filename, and writes the DataGraph to the provided filename in XML format. This is done using the XMLHelper instance method save, that writes the SDO to an output stream in XML format.

The EmployeeSDOTestClient Class

Now that we have the EmployeeDMS, we can create an application that will use the facilities provided by it. This application, in our case, is the EmployeeSDOTestCLient class. This is a simple test harness containing a single main method. This main method exercises each of the public methods in the EmployeeDMS. The test harness class is presented below:

package com.cramer.sdo.client;

import com.cramer.sdo.dms.EmployeeDMS;
import commonj.sdo.DataGraph;
import commonj.sdo.DataObject;

public class EmployeeSDOTestClient {

        public static void main(String[] args) {
                try {
                        System.out.println("---------------------------");
                        EmployeeDMS dms = new EmployeeDMS();

                        System.out.println("---------------------------");
                        DataGraph dg = dms.getEmployee();
                        System.out.println("---------------------------");
                        dms.saveEmployeeToFile(dg, "tmpXMLFile.xml");
                        dg.getRootObject().setString("name", 
                                               "Denise Cooke");
                        dms.saveEmployeeToFile(dg, "tmpXMLFile2.xml");
                        System.out.println("---------------------------");
                        
                        dg = dms.getEmployeeFromString();
                        DataObject employee = dg.getRootObject();
                        System.out.println("Name: " + 
                                employee.getString("name") + 
                                "\n\t" + "Employee ID: " + 
                                employee.getInt("employeeId"));
                        DataObject address = 
                                employee.getDataObject("address");
                        System.out.println("\tAddress: ");
                        System.out.println("\t\t Street: " + 
                                     address.getString("street"));
                        System.out.println("\t\t City: " +  
                                     address.getString("city"));
                        System.out.println("\t\t County: " + 
                                     address.getString("county"));
                        System.out.println("\t\t Postcode: " + 
                                     address.getString("postcode"));
                        
                } catch (Exception e) {
                        System.out.println("Sorry an error occured " + 
                                      e.toString());
                        e.printStackTrace();
                }

        }

}

The first thing that this class does is to instantiate the EmployeeDMS,

EmployeeDMS dms = new EmployeeDMS();

This will cause the DMS to initialise the types defined for our SDO example. Next, the test harness uses the DMS to obtain a DataGraph for the Employee:

DataGraph dg = dms.getEmployee();

This DataGraph is then passed to the saveEmployeeToFile method which saves the contents of the DataGraph to a file called tmpXMLFile.xml.

dms.saveEmployeeToFile(dg, "tmpXMLFile.xml");

The result of this is that a new file is created with the following contents:

<?xml version="1.0" encoding="ASCII"?>
<regdeveloper:Employee xmlns:regdeveloper="http://www.theregister.com">
  <name>John Hunt</name>
  <address country="GB">
    <street>12 No Street</street>
    <city>Nowhere</city>
    <county>Wiltshire</county>
    <postcode>SNnn nJG</postcode>
  </address>
  <employeeId>34255</employeeId>
</regdeveloper:Employee>

This is an XML file that contains the same information as was (programmatically_) created by the DMS.

Next, the test harness changes the name associated with the employee object within the DataGraph to “Denise Cooke” and re-saves the DataGraph to a different file name:

dg.getRootObject().setString("name", 
                             "Denise Cooke");
dms.saveEmployeeToFile(dg, "tmpXMLFile2.xml");

Note that the method setString is used to set the name of the root DataObject as name is a type of string. Also, note that the property name is used with the new value of the property. The new file created is presented below:

<?xml version="1.0" encoding="ASCII"?>
<regdeveloper:Employee xmlns:regdeveloper="http://www.theregister.com">
  <name>Denise Cooke</name>
  <address country="GB">
    <street>12 No Street</street>
    <city>Nowhere</city>
    <county>Wiltshire</county>
    <postcode>SNnn nJG</postcode>
  </address>
  <employeeId>34255</employeeId>
</regdeveloper:Employee>

Note that this file is identical to the first, with the exception that the name has now been changed.

Finally, the test harness now obtains a DataGraph from the getEmployeeFromString method in the DMS and retrieves the root object from the DataGraph:

dg = dms.getEmployeeFromString();
DataObject employee = dg.getRootObject();

It now uses the accessor method to obtain the information held in the DataObject. In this case, it uses the getString method to obtain all the primitive string data, the getInt method to retrieve the single integer held in the DataObject and getDataObject to retrieve the DataObject associated with the employee DataObject.

Running the example

To execute the EmployeeSDOTestClient class we must ensure that the Tuscany jar files are on the class path and that we are at least using Java 5 SE. The output form this test application is presented below:

------------------------------
Creating EmployeeDMS
Defining SDO Types for DMS
Type Def'ns gen'd from: xml/data/employee.xsd
------------------------------
Programmatically Creating Data Objects
Created Data Objects
------------------------------
Saving SDO objects to file
Saved to file xml/data/tmpXMLFile.xml
Saving SDO objects to file
Saved to file xml/data/tmpXMLFile2.xml
------------------------------
Name: John Hunt
        Employee ID: 34255
        Address: 
                 Street: 12 No Street
                 City: Nowhere
                 County: Wiltshire
                 Postcode: SNnn nJG

Generating SDO classes

In the previous section, we have looked at how generic DataObjects can be used to represent data in a transportable, platform independent manner. Another approach is to generate custom SDO objects that will perform the same function but are semantically more meaningful. This can be done using the Tuscany projects Java Generator classes (see the abstract class JavaGenerator and its concrete subclasses Interface2JavaGenerator and XSD2JavaGenerator). For example, to generate Java classes directly from the XSD file presented earlier we can use the following command:

Java org.apache.tuscany.sdo.generate.XSD2JavaGenerator 
-targetDirectory tmp/sdo 
-javaPackage com.regdev xml/data/employee.xsd 

The output for this command is presented below:

 >>   Generating code
 >>   Generating packages
 >>   Generating package RegdevPackageImpl
 >>   Generating Java class com.regdev.impl.RegdevPackageImpl
 >>   Generating /TargetProject/com/regdev/impl/RegdevPackageImpl.java
 >>   Generating Java interface com.regdev.RegdevFactory
 >>   Generating /TargetProject/com/regdev/RegdevFactory.java
 >>   Generating Java class com.regdev.impl.RegdevFactoryImpl
 >>   Generating /TargetProject/com/regdev/impl/RegdevFactoryImpl.java
 >>   Generating Address
 >>   Generating Java interface com.regdev.Address
 >>   Generating /TargetProject/com/regdev/Address.java
 >>   Generating Java class com.regdev.impl.AddressImpl
 >>   Generating /TargetProject/com/regdev/impl/AddressImpl.java
 >>   Generating Employee Type
 >>   Generating Java interface com.regdev.EmployeeType
 >>   Generating /TargetProject/com/regdev/EmployeeType.java
 >>   Generating Java class com.regdev.impl.EmployeeTypeImpl
 >>   Generating /TargetProject/com/regdev/impl/EmployeeTypeImpl.java

This generates 4 classes in the specified com.regdev package within the tmp/sdo directory. These classes are:

com.regdev.impl.AddressImpl
com.regdev.impl.EmployeeTypeImpl
com.regdev.RegdevFactory
com.regdev.impl.RegdevPackageImpl

Uses these classes, we could now refactor the previous example to use the EmployeeType and Address classes explicitly.

SDO and SCA

Service Component Architecture or (SCA) is a set of specifications that describes a model for the construction of services. The Apache Tuscany project also provides an implementation of the SCA standard for the creation of operation SOA services. SDO is ideal as a compliment to the SCA standard as it provides a common way to access many different kinds of data, from many different sources. That is, SDO can provide the common data model used to share data between a client and a service – remember that an SDO model contains metadata that allows the data model to be self describing.

When used with SCA, SDO provides the common data representation for complex data that must be passed into the service as a parameter or returned as the result of that service. Such use of SDO does not require, or indeed preclude, use of the update facilities associated with SDO. However, this feature of SDO is not necessary for effective use within a service. Also, the use of a DMS is not required in the SOA scenario, although as mentioned earlier, using a DMS may be a useful pattern to follow within the client and/or the service.

SDO and JavaBeans

One thing that certainly came to my mind when I first looked at SDO was – isn’t this just JavaBeans with the in-built to be saved as an XML file. Actually, the answer is “no”. SDO goes beyond simple JavaBeans by incorporating a number of important features. First off is of course the metadata inherent to the DataObject that allows the data to be self describing. Another is the ability to navigate the graph of objects either use the API or by XPATH expressions. Added to this is the strong typing of the data model (based on the metadata), the inclusion of a change history, validation of data changes (and constraints upon those changes) and the application of relationship integrity. The end result is a very powerful model for representing data which indeed does go beyond simple JavaBeans.

Summary

Service Data Objects or SDO offers a platform and language independent format for exchanging data between applications. This standardised data format has been around for some time; however, its use within SOA represents a widening of its appeal. In combination with SCA, it offers a powerful, data transport mechanism that increases the simplicity and accessibility of services.

Resources