JavaEnterprise JavaBuilding Web Services with User-Defined Data Types

Building Web Services with User-Defined Data Types

Introduction

In a real-world scenario, Web services do not always use primitive data types. The real-world objects may be made of complex, user-defined data types. This article explains in detail the procedure for creating and exposing a Web service with user-defined data types deployed in a Weblogic SOAP container on a BEA Weblogic Server 8.x application server.

Aim of the Article

Creating Web services out of complex, user-defined data types remains one of the challenges of the next generation of Web services. In this article, you have an user-defined data type called personSample. You will learn about the creation and deployment of a Web service with personSample being both the input as well as the return type, along with the complete code.

Web Service with User-Defined Data Types

Weblogic Server transparently handles the conversion of the built-in data types between their XML and Java representation. However, if the Web service operation uses non-built-in data types, you must provide necessary information to the Weblogic Server so that it can perform the conversion. There are two ways of doing it:

  1. Using Weblogic Servers servicegen and autotype Ant tasks. Ant tasks will automatically create a Java class, serialization class, and data type mappings and assemble them into a Web service.
  2. Manual handling of user-defined data types. The problem with the autotype Ant task is that if the data type is too complex, autotype will not correctly generate the components. Or, if you want more control over how the data is converted between its XML and Java representations rather than relying on the default conversion procedure used by WebLogic Server. Various components that are required for manual handling are listed below.
    • A Serialization class that converts between the XML and Java representation of the data.
    • A Java class to contain the Java representation of the data type.
    • An XML Schema representation of the data type.
    • Data type mapping information in the web-services.xml deployment descriptor file.

This article concentrates on manually handling Web services with user-defined data types.

Handling User Defined/Non–Built-In Data Types

The following procedure describes how to create non-built-in data types and use the servicegen Ant task to create a deployable Web service.

Creating an XML schema representation of the data type

Web services use SOAP as the message format to transmit data between the service and the client application that invokes the service. Because SOAP is an XML-based protocol, you must use XML Schema notation to describe the structure of non–built-in data types used by Web Service operations.

The following example describes the XML Schema representation for a non–built-in datatype called personSample.

<xsd:schema xmlns_xsd="http://www.w3.org/2001/XMLSchema"
   xmlns_stns="java:Sample.personSample"
   attributeFormDefault="qualified"
   elementFormDefault="qualified"
   targetNamespace="java:Sample.personSample">
  <xsd:complexType name="personSample">
    <xsd:sequence>
        <xsd:element name="name"
                     type="xsd:string"
                     nillable="true"
                     minOccurs="1"
                     maxOccurs="1">
        </xsd:element>
        <xsd:element name="age"
                     type="xsd:int"
                     minOccurs="1"
                     maxOccurs="1">
        </xsd:element>
    </xsd:sequence>
  </xsd:complexType>
</xsd:schema>

The following XML shows an instance of the personSample data type:

<personSample>
  <name>Gerry</name> 
  <age>25</age>
</personSample>

Creating the Java class that represents the data type

The Java class representation of the user-defined data type is to defined. It will contain the getter and setter methods for various attributes with constructors. The following example shows the Java class representation of the personSample data type.

package Sample;
public final class personSample
{
   String name;
   int age;

   public personSample()
   {}

   public personSample(String name, int age)
   {
      this.name = name;
      this.age = age;
   }

   public void setName(String name)
   {
      this.name = name;
   }

   public void setAge(int age)
   {
      this.age = age;
   }

   public String getName()
   {
      return name;
   }

   public int getAge()
   {
      return age;
   }

}

Writing the Serialization Class

The serialization class performs the actual conversion of the data between its XML and Java representations. You can write only one class that contains methods to serialize and deserialize the data. In this class, the WebLogic XML Streaming API are used to process the XML data. These APIs enable a procedural, stream-based handling of XML documents.

The following example shows a personSampleCodec class that uses the XML Streaming API to serialize and deserialize the personSample data type.

package Sample;
// Importing the classes that are required for serialization and
// deserialization
import weblogic.webservice.encoding.AbstractCodec;
import weblogic.xml.schema.binding.DeserializationContext;
import weblogic.xml.schema.binding.DeserializationException;
import weblogic.xml.schema.binding.Deserializer;
import weblogic.xml.schema.binding.SerializationContext;
import weblogic.xml.schema.binding.SerializationException;
import weblogic.xml.schema.binding.Serializer;
// importing the WebLogic XML Streaming API classes
import weblogic.xml.stream.Attribute;
import weblogic.xml.stream.CharacterData;
import weblogic.xml.stream.ElementFactory;
import weblogic.xml.stream.EndElement;
import weblogic.xml.stream.StartElement;
import weblogic.xml.stream.XMLEvent;
import weblogic.xml.stream.XMLInputStream;
import weblogic.xml.stream.XMLName;
import weblogic.xml.stream.XMLOutputStream;
import weblogic.xml.stream.XMLStreamException;
// This class extends the weblogic.webservice.encoding.AbstractCodec
// class.
public final class personSampleCodec extends
   weblogic.webservice.encoding.AbstractCodec
{
public void serialize(Object obj,XMLName name,XMLOutputStream writer,
                      SerializationContext context)throws
                     SerializationException
{
  personSample personSample = (personSample) obj;
  try
  {
  //outer start element
  writer.add(ElementFactory.createStartElement(name));
  //personSample_ name element
  writer.add(ElementFactory.createStartElement("name"));
 writer.add(ElementFactory.createCharacterData(personSample.getName()));
  writer.add(ElementFactory.createEndElement("name"));
  //personSample_ age element
  writer.add(ElementFactory.createStartElement("age"));
  String age_string = Integer.toString(personSample.getAge());
  writer.add(ElementFactory.createCharacterData(age_string));
  writer.add(ElementFactory.createEndElement("age"));
  //outer end element
  writer.add(ElementFactory.createEndElement(name));
  }
  catch(XMLStreamException xse)
  {
    throw new SerializationException("stream error", xse);
  }
 }

public Object deserialize(XMLName name,XMLInputStream reader,
                          DeserializationContext context)
                         throws DeserializationException
{
  // extract the desired information out of reader, consuming the
  // entire element representing the type, construct your
  // object, and return it.
  personSample personSample_ = new personSample();
  try
  {
    if (reader.skip(name, XMLEvent.START_ELEMENT))
    {
      StartElement top = (StartElement)reader.next();
      //next start element should be the name
      if (reader.skip(XMLEvent.START_ELEMENT))
      {
    StartElement personSample_name = (StartElement)reader.next();
        //assume that the next element is our name character data
        CharacterData cdata = (CharacterData) reader.next();
        personSample_.setName(cdata.getContent());
       }
       else
       {
          throw new DeserializationException
            ("personSample_ name not found");
       }
       //next start element should be the personSample_ age
       if (reader.skip(XMLEvent.START_ELEMENT))
       {
        StartElement personSample_id = (StartElement)reader.next();
        //assume that the next element is our age character data
        CharacterData cdata = (CharacterData) reader.next();
        personSample_.setAge(Integer.parseInt(cdata.getContent()));
       }
       else
       {
          throw new DeserializationException
            ("personSample_ age not found");
       }
       //we must consume our entire element to leave the stream in
       //a good state for any other deserializer
      if (reader.skip(name, XMLEvent.END_ELEMENT))
      {
        XMLEvent end = reader.next();
      }
      else
      {
        throw new DeserializationException
          ("expected end element not found");
      }
    }
    else
    {
      throw new DeserializationException
        ("expected start element not found");
    }
  }
 catch (XMLStreamException xse)
 {
 throw new DeserializationException("stream error", xse);
 }
 return personSample_;
 }
}
Method Description

The Serialization classes needs to extend weblogic.webservice.encoding.AbstractCodec.

The weblogic.webservice.encoding.AbstractCodec is an abstract class that contains the default implementation for serializers, deserializers, exceptions, and their contexts. You need to provide the custom implementation for the serialize() and deserialize() methods.

void serialize(Object obj,XMLName name,
                 XMLOutputStream writer,
                 SerializationContext context)
    throws SerializationException;

The serialize() method contains implementation for converting the data from Java to XML. The first parameter for this method is an Object parameter that represents the user-defined object. In this example, it represents the personSample object. Using the XML Streaming API, the Java objects are written to the XMLOutputStream parameter. The XMLName parameter is used as the name of the resulting element. The SerializationContext object is used by the Weblogic internally.

Object deserialize(XMLName name,
                   XMLInputStream reader,
                   DeserializationContext context)
    throws DeserializationException;

The deserialize() method is used to convert the XML to Java objects. The XMLInputStream will contain the XML. You need to parse the XML using the Weblogic Streaming APIs and contruct the Java Object. While using XML Streaming APIs, the stream needs to be read until the EndElement is reached. Deserialization of subsequent elements might fail otherwise. DeserializationContext is used by Weblogic internally.

Creating the data type mapping file

The data type mapping file is a subset of the web-services.xml deployment descriptor file. It contains some of the information about non–built-in data types, such as the name of the Java class that describes the Java representation of the data, the name of the serialization class that converts the data between XML and Java, and so on. The servicegen Ant task uses this data type mapping file when creating the web-services.xml deployment descriptor for the WebLogic Web Service that uses the non–built-in data type.

Following are the steps to create the data type mapping file:

  1. Create a text file named personSampleMapping.
  2. Within in the text file, add a <type-mapping> root element:
    <type-mapping>
    ...
    </type-mapping>
    
  3. For each non–built-in data type for which you have created a serialization class, add a <type-mapping-entry> child element of the <type-mapping> element. Include the following attributes:
    • xmlns:name—Declares a namespace.
    • class-name—Specifies the fully qualified name of the Java class.
    • type—Specifies the name of XML Schema type for which this data type mapping entry applies.
    • serializer—The fully qualified name of the serialization class that converts the data from its Java to its XML representation.
    • deserializer—The fully qualified name of the serialization class that converts the data from its XML to its Java representation.

    The following example is shows a data type mapping file with one <type-mapping> entry for the personSample XML Schema data type.

    <type-mapping>
    <type-mapping-entry
       xmlns_p2="java:Sample.personSample"
       class-name="Sample.personSample"
       type="p2:personSample"
       serializer="Sample.personSampleCodec"
       deserializer="Sample.personSampleCodec" >
    </type-mapping-entry>
    </type-mapping>
    

Creating the service with a non–built-in type

The following code shows a service that uses a personSample (user-defined) data type as its parameter and also as a return type.

package Sample;
public class MySample
{
  public personSample getset(personSample n)
  {
    System.out.println("Name : "+n.getName());
    System.out.println("Age  : "+n.getAge());
    return n;
  }
}

Compilation

Complile the personSampleCodec and MySample Java files into classes. Ensure that the CLASSPATH variable can locate the classes. In your example, the classes will be under the “Sample” directory.

Assembling the Web service using servicegen

Assembling into Web service can be done by running a Weblogics Ant task called servicegen. The necessary information needs to be provided in a build.xml file. The build.xml for your example is shown below.

<project name="MySampleWebservice" default="ear">
<target name="ear">
<servicegen destEar="MySample.ear" contextURI="MySample" >
<service javaClassComponents="Sample.MySample"
         targetNamespace="http://www.bea.com/webservices/basic/
                          statelesSession"
         serviceName="MySample" serviceURI="/MySample"
         typeMappingFile="c:/personSampleTypeMapping.txt"
         generateTypes="True"
         expandMethods="True"
         style="rpc" >
</service> 
</servicegen>
</target>
</project>

The only difference in build.xml from using a built-in data type is that here you need to provide an extra attribute called typeMappingFile, whose value will be the name of the file where the datatype mapping is written. In your example, it is placed at the “c:/personSampleTypeMapping.txt” location.

The next step is to run Ant to assemble the classes. Before that, you need to set the environment by running the setWLSEnv.bat file of Weblogic Server. This will set the necessary environment for running the servicegen task. Once the environment is set, the task can be done by issuing the command “ant” from the prompt. This servicegen task will create an EAR file; in your example, it is MySample.ear. It will contain the Web-services.war that internally contains the web-services.xml file. The .EAR file will contain the following structure.

Manifest.mf
Web-services.war
    Necessary class file
    Web.xml
    Web-services.xml
    ... ... ...
Application.xml

Updating the web-services.xml file

The web-services.xml file generated by servicegen will not have the XML Schema information for the non–built-in data type for which you have created your own custom serialization class. For this reason, you must manually add the XML Schema information to the deployment descriptor under the <types></types> tag that is a child element of <web-service>.

In our example, after modification, web-services.xml will look like

<?xml version="1.0" encoding="UTF-8"?>
<web-services>
<web-service useSOAP12="false"
             targetNamespace="http://www.bea.com/webservices/basic/
                                     statelesSession"
             name="MySample" style="rpc" uri="/MySample"
             ignoreAuthHeader="false">
  <types>
    <xsd:schema xmlns_xsd="http://www.w3.org/2001/XMLSchema"
                xmlns_stns="java:Sample.personSample"
                attributeFormDefault="qualified"
                elementFormDefault="qualified"
                targetNamespace="java:Sample.personSample">
      <xsd:complexType name="personSample">
      <xsd:sequence>
        <xsd:element name="name" type="xsd:string"
                     nillable="true" minOccurs="1"
                     maxOccurs="1"></xsd:element>
        <xsd:element name="age" type="xsd:int" minOccurs="1"
                     maxOccurs="1"></xsd:element>
      </xsd:sequence>
      </xsd:complexType>
    </xsd:schema>
  </types>
  <type-mapping>
    <type-mapping-entry deserializer="Sample.personSampleCodec"
                        xmlns_p2="java:Sample.personSample"
                        type="p2:personSample"
                        serializer="Sample.personSampleCodec"
                        class-name="Sample.personSample">
    </type-mapping-entry>
    </type-mapping>
      <components>
        <java-class name="jcComp0" class-name="Sample.MySample">
        </java-class>
      </components>
      <operations>
        <operation name="getset" method="getset(Sample.personSample)"
                   component="jcComp0">
          <params>
            <param style="in" xmlns_p2="java:Sample.personSample"
                    type="p2:personSample" location="body"
                    name="personSample"
                    class-name="Sample.personSample">
            </param>
              <return-param xmlns_p2="java:Sample.personSample"
                            type="p2:personSample"
                            location="body" name="result"
                            class-name="Sample.personSample">
              </return-param>
            </params>
        </operation>
    </operations>
  </web-service>
</web-services>

Now, the MySample.ear is updated and it is ready for deployment. In the weblogic administration console, deploy this MySample.ear under Deployments>Application. The URL for accessing this Web service will be http://hostname:portname/servicename. For example: http://localhost:7001/mySample/mySample.

Conclusion

Enabling a progarmmer to create Web services from complex data types opens up a wide area of possible applications in e-commerce. There will be more Web services that will address the exact needs of the target customer in a more efficient manner.

About the Author

Nandhini Arumugam holds a Masters degree in Computer Applications from PSG College of Technology, Coimbatore. She has been working as a software designer in the Telecom and Mobile Solutions Lab, Hewlett-Packard, Bangalore for more than one year. The domain of her experience includes Web services and J2EE-related technologies. She can be reached at nandhini.arumugam@hp.com.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories