Tutorial: Using data types with JAX-WS

The following tutorial demonstrates how to access web services using the Java API for XML Web Services (JAX-WS).

If you have done the earlier JAX-WS tutorial, then some of these steps will already have been performed.

Before you begin, you will need the JAX-WS tools available from Sun. If you do not have this package installed on your system, you must download the JAX-WS tools and install them. To download the JAX-WS tools, visit [external link] http://java.sun.com/webservices/. Click the link for JAX-WS. This should take you to the [external link] Java API for XML Web Services page. Click the Download Now link. After you have downloaded the software package, install it on your system.

This example was developed with JAX-WS 2.1.3 for Windows.

SQL Anywhere SOAP web services that are accessed from JAX-WS should be declared to be of format CONCRETE.

To create SOAP and DISH services

  1. At a command prompt, execute the following statement to start a personal web server. The -xs http(port=80) option tells the database server to accept HTTP requests on port 80. If you already have a web server running on port 80, use another port number such as 8080 for this tutorial.

    dbeng11 -xs http(port=80) samples-dir\demo.db
  2. Start Interactive SQL and connect to the SQL Anywhere sample database as the DBA. Execute the following statements:

    1. Define a stored procedure that lists some columns of the Employees table.
      CREATE PROCEDURE ListEmployees()
      RESULT (
       EmployeeID            INTEGER,
       Surname               CHAR(20),
       GivenName             CHAR(20),
       StartDate             DATE,
       TerminationDate       DATE )
      BEGIN
        SELECT EmployeeID, Surname, GivenName, 
          StartDate, TerminationDate 
        FROM Employees;
      END;
    2. Define a SOAP service that calls this stored procedure.
      CREATE SERVICE "WS/EmployeeList2"
      TYPE 'SOAP'
      FORMAT 'CONCRETE' EXPLICIT ON
      DATATYPE OUT
      AUTHORIZATION OFF
      SECURE OFF
      USER DBA
      AS CALL ListEmployees();

      The EXPLICIT clause can only be used with a SOAP or DISH service of type CONCRETE. In this example, EXPLICIT ON indicates that the corresponding DISH service should generate XML Schema that describes the EmployeeList2Dataset object. This option only affects the WSDL document that is generated. In an earlier JAX-WS tutorial, we looked at an example that used EXPLICIT OFF. See Tutorial: Accessing web services from JAX-WS.

      DATATYPE OUT indicates that explicit data type information is generated in the XML result set response. If DATATYPE OFF had been specified, then all data would be typed as string. This option does not affect the WSDL document that is generated.

      Because authorization has been turned off, anyone can use this service without supplying a user name and password. The commands run under user DBA. This arrangement is simple, but not secure.

    3. Create a DISH service to act as a proxy for the SOAP service and to generate the WSDL document.
      CREATE SERVICE "WSDish"
      TYPE 'DISH'
      FORMAT 'CONCRETE'
      GROUP "WS"
      AUTHORIZATION OFF
      SECURE OFF
      USER DBA;
      

      The SOAP and DISH service must both be format CONCRETE. Since the EmployeeList2 service is in the WS group, a GROUP clause is included.

  3. Take a look at the WSDL that the DISH service automatically creates. To do so, open a web browser and browse to the following URL: [external link] http://localhost:80/demo/WSDish. The DISH service automatically generates a WSDL document that appears in the browser window.

    In particular, observe the EmployeeList2Dataset object that is exposed because the format of this service is CONCRETE and EXPLICIT is ON. In a later step, the wsimport application uses this information to generate a SOAP 1.1 client interface for this service.

    <s:complexType name="EmployeeList2Dataset">
    <s:sequence>
    <s:element name="rowset">
      <s:complexType>
      <s:sequence>
      <s:element name="row" minOccurs="0" maxOccurs="unbounded">
        <s:complexType>
        <s:sequence>
        <s:element minOccurs="0" maxOccurs="1" name="EmployeeID" 
            nillable="true" type="s:int" /> 
        <s:element minOccurs="0" maxOccurs="1" name="Surname" 
            nillable="true" type="s:string" /> 
        <s:element minOccurs="0" maxOccurs="1" name="GivenName" 
            nillable="true" type="s:string" /> 
        <s:element minOccurs="0" maxOccurs="1" name="StartDate" 
            nillable="true" type="s:date" /> 
        <s:element minOccurs="0" maxOccurs="1" name="TerminationDate" 
            nillable="true" type="s:date" /> 
        </s:sequence>
        </s:complexType>
      </s:element>
      </s:sequence>
      </s:complexType>
    </s:element>
    </s:sequence>
    </s:complexType>

To generate a JAX-WS interface for these web services

  1. In this example, the Java API for XML Web Services (JAX-WS) and the Sun Java 1.6.0 JDK are installed on drive C:. Set your PATH environment variable so that it includes the JAX-WS binaries, as well as the JDK binaries. The binaries are located in the following directories:

    c:\Sun\jaxws-ri\bin
    c:\jdk1.6.0\bin
  2. Set your CLASSPATH environment variable.

    SET classpath=.;C:\Sun\jaxws-ri\lib\jaxb-api.jar
    ;C:\Sun\jaxws-ri\lib\jaxws-rt.jar
  3. The next step is to generate the interface needed to call the web service.

    At a command prompt, execute the following command.

    wsimport -keep -Xendorsed "http://localhost:80/demo/WSDish"

    The wsimport tool retrieves the WSDL document from the given URL, generates the Java files that define the interface for it, and then compiles the Java files.

    The keep option tells wsimport not to delete the .java files. Without this option, these files are deleted after the corresponding .class files have been generated. Saving these files makes it easer to examine the makeup of the interface.

    The Xendorsed option allows you to use the JAX-WS 2.1 API with JDK6.

    Once this command completes, you should have a new subdirectory structure named localhost\demo\ws in your current directory that contains the following Java files, along with the compiled .class versions of each Java file.

    EmployeeList2.java
    EmployeeList2Dataset.java
    EmployeeList2Response.java
    FaultMessage.java
    ObjectFactory.java
    package-info.java
    WSDish.java
    WSDishSoapPort.java

To use the generated JAX-WS interface

  1. Save the following Java source code into SASoapDemo2.java. Make sure that this file is located in the same directory containing the localhost subdirectory that was generated by the wsimport tool.

    // SASoapDemo2.java illustrates a web service client that
    // calls the WSDish service and prints out the data.
    
    import java.util.*;
    import javax.xml.ws.*;
    import org.w3c.dom.Element;
    import org.w3c.dom.Node;
    import javax.xml.datatype.*;
    import localhost.demo.ws.*;
    
    public class SASoapDemo2
    {
      public static void main( String[] args )
      {
        try {
          WSDish service = new WSDish();
    
          Holder<EmployeeList2Dataset> response = 
              new Holder<EmployeeList2Dataset>();
          Holder<Integer> sqlcode = new Holder<Integer>();
                
          WSDishSoapPort port = service.getWSDishSoap();
    
          // This is the SOAP service call to EmployeeList2
          port.employeeList2( response, sqlcode );
    
          EmployeeList2Dataset result = response.value;
          EmployeeList2Dataset.Rowset rowset = result.getRowset();
    
          List<EmployeeList2Dataset.Rowset.Row> rows = rowset.getRow();
    
          String fieldType;
          String fieldName;
          String fieldValue;
          Integer fieldInt;
          XMLGregorianCalendar fieldDate;
          
          for ( int i = 0; i < rows.size(); i++ ) {
            EmployeeList2Dataset.Rowset.Row row = rows.get( i );
    
            fieldType = row.getEmployeeID().getDeclaredType().getSimpleName();
            fieldName = row.getEmployeeID().getName().getLocalPart();
            fieldInt = row.getEmployeeID().getValue();
            System.out.println( "(" + fieldType + ")" + fieldName + 
                                "=" + fieldInt );
            
            fieldType = row.getSurname().getDeclaredType().getSimpleName();
            fieldName = row.getSurname().getName().getLocalPart();
            fieldValue = row.getSurname().getValue();
            System.out.println( "(" + fieldType + ")" + fieldName + 
                                "=" + fieldValue );
            
            fieldType = row.getGivenName().getDeclaredType().getSimpleName();
            fieldName = row.getGivenName().getName().getLocalPart();
            fieldValue = row.getGivenName().getValue();
            System.out.println( "(" + fieldType + ")" + fieldName + 
                                 "=" + fieldValue );
            
            fieldType = row.getStartDate().getDeclaredType().getSimpleName();
            fieldName = row.getStartDate().getName().getLocalPart();
            fieldDate = row.getStartDate().getValue();
            System.out.println( "(" + fieldType + ")" + fieldName + 
                                "=" + fieldDate );
            
            if ( row.getTerminationDate() == null ) {
              fieldType = "unknown";
              fieldName = "TerminationDate";
              fieldDate = null;
            } else {
              fieldType = 
                row.getTerminationDate().getDeclaredType().getSimpleName();
              fieldName = row.getTerminationDate().getName().getLocalPart();
              fieldDate = row.getTerminationDate().getValue();
            }
            System.out.println( "(" + fieldType + ")" + fieldName + 
                                "=" + fieldDate );
            System.out.println();
          }
        }
        catch (Exception x) {
          x.printStackTrace();
        }
      }
    }

    If you chose to start the web server using a different port number such as 8080, then you will have to alter the import localhost source line to something like the following:

    import localhost._8080.demo.ws.*;
  2. Compile SASoapDemo2.java.

    javac SASoapDemo2.java
  3. Run the compiled class file.

    java SASoapDemo2

    When the application sends its request to the web server, it receives an XML result set response that consists of an EmployeeList2Result with a rowset containing several row entries. Also included in the response is the sqlcode result from executing the query. An example of the response is shown next.

    <tns:EmployeeList2Response>
     <tns:EmployeeList2Result xsi:type='tns:EmployeeList2Dataset'>
      <tns:rowset>
        <tns:row>...</tns:row>
    .
    .
    .
        <tns:row>...</tns:row>
       </tns:rowset>
     </tns:EmployeeList2Result>
     <tns:sqlcode>0</tns:sqlcode>
    </tns:EmployeeList2Response>

    Each row in the rowset is sent in a format similar to the following.

    <tns:row>
       <tns:EmployeeID xsi:type="xsd:int">1751</tns:EmployeeID>
       <tns:Surname xsi:type="xsd:string">Ahmed</tns:Surname>
       <tns:GivenName xsi:type="xsd:string">Alex</tns:GivenName>
       <tns:StartDate xsi:type="xsd:date">1994-07-12-04:00</tns:StartDate>
       <tns:TerminationDate xsi:type="xsd:date">2008-04-18-04:00
           </tns:TerminationDate>
    </tns:row>

    Note that the column name and data type are included.

    Using a proxy

    You can observe the response shown above through the use of proxy software that logs the XML message traffic. The proxy inserts itself between your client application and the web server.

    The EmployeeList2 result set is displayed as (type)column name=value pairs by the SASoapDemo2 application. Several lines of output similar to the following should be generated.

    (Integer)EmployeeID=102
    (String)Surname=Whitney
    (String)GivenName=Fran
    (XMLGregorianCalendar)StartDate=1984-08-28-04:00
    (unknown)TerminationDate=null
    
    (Integer)EmployeeID=105
    (String)Surname=Cobb
    (String)GivenName=Matthew
    (XMLGregorianCalendar)StartDate=1985-01-01-05:00
    (unknown)TerminationDate=null
    .
    .
    .
    (Integer)EmployeeID=1740
    (String)Surname=Nielsen
    (String)GivenName=Robert
    (XMLGregorianCalendar)StartDate=1994-06-24-04:00
    (unknown)TerminationDate=null
    
    (Integer)EmployeeID=1751
    (String)Surname=Ahmed
    (String)GivenName=Alex
    (XMLGregorianCalendar)StartDate=1994-07-12-04:00
    (XMLGregorianCalendar)TerminationDate=2008-04-18-04:00

    Note that the TerminationDate column is only sent when its value is not NULL. The Java application is designed to detect when the TerminationDate column is not present. For this example, the last row in the Employees table was altered such that a non-NULL termination date was set.

    Also note that date values include an offset from UTC time. In the above sample data, the server is located in the North American Eastern time zone. This is 5 hours earlier than UTC time (-05:00) for dates when daylight savings is not in effect and 4 hours earlier than UTC time (-04:00) for dates when daylight savings is in effect.

    For further understanding of the Java methods used in the SASoapDemo2 application, it is worthwhile investigating the javax.xml.bind.JAXBElement class documentation on the java.sun.com web site ([external link] http://java.sun.com/javaee/5/docs/api/).