Java/XML Type Mapping

Although the Novell exteNd WSSDK has very comprehensive support for Java type to XML type mapping, applications sometimes need more advanced type mappings. This is especially true in heterogenous environments where Java programs are exchanging non-simple data types with non-Java programs. In such applications, a mapping between the Java type and an external XML representation must be defined and the runtime must be instructed to use this mapping.

Since version 2.0, the need for user-defined type mappings has reduced greatly since the xsd2java compiler automatically generates type mappings, serializers and de-serializers to deal with complex types. The type mapping functionality is therefore not used in most application and has status of an expert facility. It should also be noted that the old mapping package has been replaced by a new encoding package with the standards-based type mapping framework.

The Novell exteNd WSSDK allows programmers to define XML to Java mappings using a registry called a type mapping registry. The type mapper holds information about Java types and their XML mappings. There is a marshaler object, which can map between Java types and XML types. The marshaler has a serializer that converts Java to XML and a deserializer that converts XML to Java. Novell exteNd WSSDK uses this same architecture internally for types like Map, Vector, etc.

This section describes an address book example, which illustrates the type mapping registry functionality in the Novell exteNd WSSDK. The address book Web service has methods for storing and retrieving addresses using a complex data type called Address. The Address class is a struct (JavaBean), which has fields such as street number, street name, city, state and zip code. The example defines an AddressMashaler, which is capable of serializing and deserializing the data structure.

Address Class

The Address class is shown below:

package addressbook;
                                                                           
public class Address
{
    private int     streetNum;
    private String  streetName;
    private String  city;
    private String  state;
    private int     zipCode;
                                                                           
    public Address() {}
                                                                           
    public Address(String city, String state, String streetName, int streetNum,         int zipCode)
    {
    |   setCity(city);
    |   setState(state);
    |   setStreetName(streetName);
    |   setStreetNum(streetNum);
    |   setZipCode(zipCode);
    }
                                                                           
    public int getStreetNum() { return streetNum; }
                                                                           
    public void setStreetNum(int i) { streetNum = i; }
                                                                           
    public String getStreetName() { return streetName; }
                                                                           
    public void setStreetName(String s) { streetName = s; }
                                                                           
    public String getCity() { return city; }
                                                                           
    public void setCity(String s) { city = s; }
                                                                           
    public String getState() { return state; }
                                                                           
    public void setState(String s) { state = s; }
                                                                           
    public int getZipCode() { return zipCode; }
                                                                           
    public void setZipCode(int i) { zipCode = i; }
                                                                           
    public String toString()
    {
    |   return streetNum +" "+ streetName +" "+ city +" "+ state +" "+ zipCode;
    }
}

The XML type for this could for instance be:


<?xml version="1.0" encoding="UTF-8"?>
<schema targetNamespace="http://www.acme.org/Address"
 xmlns="http://www.w3.org/2001/XMLSchema">
 <complexType name="Address">
  <sequence>
      <element name="streetNum" type="string"/>
      <element name="streetName" type="string"/>
      <element name="city" type="string"/>
      <element name="state" type="string"/>
      <element name="zipCode" type="string"/>
  </sequence>
 </complexType>
</schema>

Figure 1: The XML definition for the Address data type.

AddressBook Marshaler

The marshaler is the key object that defines how Java types are mapped to and from XML types. Programming directly with XML is fairly crude and error-prone and the Novell exteNd WSSDK therefore supports streams for marshaling and unmarshaling data. Streams are a familiar and convenient concept for most Java programmers and they avoid the tedious programming with DOM or SAX API's. XML is simply marshaled and unmarshaled using API's similar to java.io.DataInputStream and java.io.DataOutputStream.

The AddressMarshaler class is shown below. As can be seen, the data is read and written in a manner very close to Java serialization. The Marshaler interface supports a getAttributes method in case the XML type definition requires any attributes. Using this API, application programmers will not be exposed to XML programming. Instead, the state of objects can be externalized and internalized using very few lines of code.

package addressbook;
                                                                           
import java.io.IOException;
                                                                           
import com.sssw.jbroker.web.encoding.Attribute;
import com.sssw.jbroker.web.encoding.Marshaler;
                                                                           
import com.sssw.jbroker.web.portable.InputStream;
import com.sssw.jbroker.web.portable.OutputStream;
                                                                           
public class AddressMarshaler implements Marshaler
{
    public void serialize(OutputStream os, Object obj) throws IOException
    {
    |   Address address = (Address) obj;
    |                                                                      
    |   os.writeObject(address.getCity(), "city");
    |   os.writeObject(address.getState(), "state");
    |   os.writeObject(address.getStreetName(), "streetName");
    |   os.writeInt(address.getStreetNum(), "streetNum");
    |   os.writeInt(address.getZipCode(), "zipCode");
    }
                                                                           
    public Attribute[] getAttributes(Object obj) { return null; }
                                                                           
    public String getMechanismType() { return null; }
                                                                           
    public Object deserialize(InputStream is, Class cl) throws IOException
    {
    |   Address address = new Address();
    |                                                                      
    |   address.setCity((String) is.readObject(String.class, "city"));
    |   address.setState((String) is.readObject(String.class, "state"));
    |   address.setStreetName((String) is.readObject(String.class, "streetName"));
    |   address.setStreetNum(is.readInt("streetNum"));
    |   address.setZipCode(is.readInt("zipCode"));
    |                                                                      
    |   return address;
    }
}

Note that since the Address class is a JavaBean, it would also have been possible to use the built-in BeanMarshaler. We show how to implement a custom marshaler here to illustrate the concept.

AddressBook Web Service

The AddressBook service is shown below:

package addressbook;
                                                                           
import java.rmi.Remote;
import java.rmi.RemoteException;
                                                                           
public interface AddressBook extends Remote
{
    void putAddress(String name, Address address) throws RemoteException;
                                                                           
    Address getAddress(String name) throws NoAddressFound, RemoteException;
                                                                           
    String[] getAllNames() throws RemoteException;
                                                                           
    Address[] getAllAddresses() throws RemoteException;
}

Client

The client program puts some addresses into the address book and queries them afterwards. The client must further make the Address type known to the runtime using the importTypeMappings method of the TypeMappingRegistry. The type mapper can conveniently load mappings from a property file, which has a list of 6-tuples:

<name> = <class> <serializer> <deserializer> <namespace> <local name> <location>

For example:

#
# Address Book Type Library Descriptor
#
                                                                           
address=addressbook.Address addressbook.AddressMarshaler addressbook.AddressMarshaler urn:AddressBook Address Address.xsd
exception=addressbook.NoAddressFound addressbook.NoAddressFoundMarshaler addressbook.NoAddressFoundMarshaler urn:AddressBook NoAddressFound none

The location is an absolute path to the schema definition of the type, e.g. http://www.acme.org/Address. The client program is shown below:

package addressbook;
                                                                           
import java.lang.reflect.Array;
                                                                           
import javax.xml.rpc.Stub;
import javax.naming.InitialContext;
                                                                           
import com.sssw.jbroker.web.ServiceObject;
import com.sssw.jbroker.web.encoding.DefaultTypeMappingRegistry;
                                                                           
public class Client
{
    public static void main(String[] args) throws Exception
    {
    |   // lookup the AddressBook Service
    |   InitialContext ctx = new InitialContext();
    |   AddressBookService service = (AddressBookService)
    |       ctx.lookup("xmlrpc:soap:addressbook.AddressBookService");
    |   AddressBook abook = service.getAddressBookPort();
    |   // set the end point address
    |   ((Stub)abook)._setProperty("javax.xml.rpc.service.endpoint.address",
    |       args.length > 0 ? args[0] :"http://localhost:9090/addressbook");
    |                                                                      
    |   // create and set the type mapper
    |   DefaultTypeMappingRegistry registry =
    |       new DefaultTypeMappingRegistry();
    |   registry.importTypeMappings("address.mappings");
    |   ((ServiceObject)abook)._setTypeMappingRegistry(registry);
    |                                                                      
    |   // add some addresses to the address book
    |   abook.putAddress(frodo,   addr1);
    |   abook.putAddress(bilbo,   addr1);
    |   abook.putAddress(pippin,  addr2);
    |   abook.putAddress(merry,   addr2);
    |   abook.putAddress(samwise, addr3);
    |                                                                      
    |   // query individual addresses
    |   System.out.println(abook.getAddress(frodo));
    |   System.out.println(abook.getAddress(samwise));
    |                                                                      
    |   // get a list of all the names
    |   printArray(abook.getAllNames());
    |                                                                      
    |   // get a list of all the addresses
    |   printArray(abook.getAllAddresses());
    |                                                                      
    |   // look up an undefined name
    |   try {
    |   |   abook.getAddress(gollum);
    |   } catch (NoAddressFound ex) {
    |   |   System.out.println(ex.getName() + " is not a Hobbit");
    |   }
    }
                                                                           
    private static void printArray(Object array)
    {
    |   int len = Array.getLength(array);
    |                                                                      
    |   System.out.print("{");
    |                                                                      
    |   for (int i=0; i < len; i++) {
    |   |   System.out.print((i == 0) ? "" : ", ");
    |   |   Object element = Array.get(array, i);
    |   |   System.out.print(element);
    |   }
    |                                                                      
    |   System.out.print("}\n");
    }
                                                                           
    static String bilbo   = "Bilbo";
    static String frodo   = "Frodo";
    static String pippin  = "Pippin";
    static String merry   = "Merry";
    static String samwise = "Samwise";
    static String gollum  = "Gollum";
                                                                           
    static Address addr1 = new Address("YYYYYYYY", "ZZZZZ", "XXXX", 111, 0);
    static Address addr2 = new Address("BBBBBBBB", "CCCCC", "AAAA", 222, 1);
    static Address addr3 = new Address("LLLLLLLL", "MMMMM", "KKKK", 333, 1);
}

NoAddressFound

The address book example also has an exception, which is marshaled using the standard BeanMarshaler:

package addressbook;
                                                                           
public class NoAddressFound extends Exception
{
    private String _name;
                                                                           
    public NoAddressFound() { super("address not found"); }
                                                                           
    public NoAddressFound(String name) { _name = name; }
                                                                           
    public String getName() { return _name; }
                                                                           
    public void setName(String name) { _name = name; }
}

AddressBook Web Service Implementation

The server implementation uses the tie approach to delegate to the implementation object. Note that the server is also required to populate the type mapper. This is normally done in the servlet's init method.

package addressbook;
                                                                           
import javax.servlet.ServletException;
                                                                           
import com.sssw.jbroker.web.encoding.DefaultTypeMappingRegistry;
                                                                           
public class AddressBookImpl extends AddressBook_ServiceTieSkeleton
{
    public AddressBookImpl()
    {
    |   // initialize the delegate
    |   setTarget(new AddressBookDelegate());
    }
                                                                           
    public void init() throws ServletException
    {
    |   super.init();
    |                                                                      
    |   // load and set the type library
    |                                                                      
    |   try {
    |   |   DefaultTypeMappingRegistry registry =
    |   |       new DefaultTypeMappingRegistry();
    |   |   registry.importTypeMappings("address.mappings");
    |   |   _setTypeMappingRegistry(registry);
    |   } catch (Throwable t) {
    |   |   t.printStackTrace();
    |   |   throw new ServletException(t.getMessage());
    |   }
    }
}

TIE Implementation

The delegate implementation is listed below:

package addressbook;
                                                                           
import java.util.HashMap;
                                                                           
import java.rmi.RemoteException;
                                                                           
public class AddressBookDelegate implements AddressBook
{
    private final HashMap _map = new HashMap();
                                                                           
    public synchronized void putAddress(String name, Address address)
        throws RemoteException
    {
    |   _map.put(name, address);
    }
                                                                           
    public synchronized Address getAddress(String name)
        throws NoAddressFound, RemoteException
    {
    |   Address addr = (Address) _map.get(name);
    |   if (addr == null) throw new NoAddressFound(name);
    |   return addr;
    }
                                                                           
    public synchronized String[] getAllNames() throws RemoteException
    {
    |   Object[] objs = _map.keySet().toArray();
    |   String[] names = new String[objs.length];
    |   for (int i=0; i < objs.length; i++)
    |       names[i] = (String) objs[i];
    |   return names;
    }
                                                                           
    public synchronized Address[] getAllAddresses() throws RemoteException
    {
    |   Object[] objs = _map.values().toArray();
    |   Address[] addrs = new Address[objs.length];
    |   for (int i=0; i < objs.length; i++)
    |       addrs[i] = (Address) objs[i];
    |   return addrs;
    }
}

Using rmi2wsdl to Generate Schemas

The rmi2wsdl is used to generate a WSDL given a remote interface. While generating the WSDL it can generate schemas for the types used in the remote interface methods. The mappings can be specified using the -typemapping flag, e.g.

rmi2wsdl -typemapping address.mappings addressbook.AddressBook

The generated WSDL is below:

<?xml version="1.0" encoding="UTF-8"?>
<definitions name="AddressBookService"
 targetNamespace="http://www.addressbook"
 xmlns="http://schemas.xmlsoap.org/wsdl/"
 xmlns:ns0="http://www.lang.java"
 xmlns:ns1="urn:AddressBook"
 xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
 xmlns:tns="http://www.addressbook"
 xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <types>
  <schema elementFormDefault="qualified" targetNamespace="http://www.lang.java">
   <complexType name="StringArray">
    <sequence>
     <element maxOccurs="unbounded" minOccurs="0"
      name="string" type="xsd:string"/>
    </sequence>
   </complexType>
  </schema>
  <schema targetNamespace="http://www.addressbook"
   xmlns="http://www.w3.org/2001/XMLSchema" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <complexType name="Address">
    <sequence>
     <element name="city" type="string"/>
     <element name="state" type="string"/>
     <element name="streetName" type="string"/>
     <element name="streetNum" type="int"/>
     <element name="zipCode" type="int"/>
    </sequence>
   </complexType>
  </schema>
  <schema elementFormDefault="qualified" targetNamespace="http://www.addressbook">
   <complexType name="AddressArray">
    <sequence>
     <element maxOccurs="unbounded" minOccurs="0"
      name="address" type="ns1:Address"/>
    </sequence>
   </complexType>
  </schema>
 </types>
 <message name="getAllNamesInput"/>
 <message name="getAllNamesOutput">
  <part name="result" type="ns0:StringArray"/>
 </message>
 <message name="getAllAddressesInput"/>
 <message name="getAllAddressesOutput">
  <part name="result" type="tns:AddressArray"/>
 </message>
 <message name="putAddressInput">
  <part name="arg0" type="xsd:string"/>
  <part name="arg1" type="ns1:Address"/>
 </message>
 <message name="putAddressOutput"/>
 <message name="getAddressInput">
  <part name="arg0" type="xsd:string"/>
 </message>
 <message name="getAddressOutput">
  <part name="result" type="ns1:Address"/>
 </message>
 <message name="NoAddressFound">
  <part element="xsd:string" name="name"/>
 </message>
 <portType name="AddressBook">
  <operation name="getAllNames">
   <input message="tns:getAllNamesInput"/>
   <output message="tns:getAllNamesOutput"/>
  </operation>
  <operation name="getAllAddresses">
   <input message="tns:getAllAddressesInput"/>
   <output message="tns:getAllAddressesOutput"/>
  </operation>
  <operation name="putAddress" parameterOrder="arg0 arg1">
   <input message="tns:putAddressInput"/>
   <output message="tns:putAddressOutput"/>
  </operation>
  <operation name="getAddress" parameterOrder="arg0">
   <input message="tns:getAddressInput"/>
   <output message="tns:getAddressOutput"/>
   <fault message="tns:NoAddressFound" name="NoAddressFound"/>
  </operation>
 </portType>
</definitions>

Please refer to the README for detailed instructions on compiling, deploying and running the example.



Copyright © 2003, 2004 Novell, Inc. All rights reserved. Copyright © 2001, 2002, 2003 SilverStream Software, LLC. All rights reserved.