站内搜索: 请输入搜索关键词
当前页面: 图书首页 > XML and Java: Developing Web Applications, Second Edition

XML and Java: Developing Web Applications, Second Edition

[ directory ] Previous Section Next Section

13.2 Web Services Description

In Chapter 12, we showed examples of SOAP messaging with the travel reservation scenario. According to the Web services architecture (see Figure 13.1), the travel agency should be able to publish its service on a service broker. But how can we describe the necessary information on the service? In this section, we explain WSDL. We first discuss the syntax and semantics of WSDL. Then we examine WSDL tools that can help in application development. Finally, we develop a prototype of a WSDL compiler so that you can understand further details of WSDL in terms of programming.

13.2.1 Overview of WSDL

Let us begin by looking at an example of SOAP messaging. Listings 13.1 and 13.2 show request and response messages in our travel reservation example developed in Chapter 12. In addition to SOAP messages, we include HTTP headers for our discussion. The question here is, what should requestors know to perform such message exchange? At first glance, we can come up with the following items based on Listings 13.1 and 13.2.

  • The format of request and response messages is explicitly represented.

  • The messages are combined to organize request/response messaging.

  • Each message is wrapped by a SOAP Envelope.

  • HTTP is used for transport, and its access point URL is http://demohost:8080/xmlbook2/chap12/servlet/SOAPTravelServlet.

The previous information is described with WSDL.

Listing 13.1 Example of SOAP messaging over HTTP (request message)
POST /xmlbook2/chap12/servlet/SOAPTravelServlet HTTP/1.1
Content-Type: text/xml; charset=UTF-8
SOAPAction: ""
User-Agent: Java1.3.0
Host: demohost:2020
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-length: 373

<?xml version="1.0"?>
<SOAP-ENV:Envelope
    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Body>
        <ns1:reserve xmlns:ns1="urn:reserve-tour-msg">
            <user>nakamury</user>
            <tourId>hawaii55</tourId>
            <departure>
                <year>2002</year>
                <month>9</month>
                <day>15</day>
            </departure>
        </ns1:reserve>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Listing 13.2 Example of SOAP messaging over HTTP (response message)
HTTP/1.0 200 OK
Content-Type: text/xml; charset=UTF-8
Servlet-Engine: Tomcat Web Server/3.2.1
(JSP 1.1; Servlet 2.2; Java 1.3.0;
 Windows 2000 5.0 x86; java.vendor=Sun Microsystems Inc.)

<?xml version="1.0"?>
<SOAP-ENV:Envelope
    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Body>
        <ns1:accepted xmlns:ns1="urn:reserve-tour-msg">
            <user>nakamury</user>
            <tourId>hawaii55</tourId>
            <departure>
                <year>2002</year>
                <month>9</month>
                <day>15</day>
            </departure>
            <reservation-id>hawaii551000144683336</reservation-id>
        </ns1:accepted>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Listing 13.3 shows a WSDL document for a document-centric messaging (DCM) version of our travel reservation example. In other words, this document describes how to access the TravelServiceSOAP class (refer to Listing 12.12 in Chapter 12) via SOAP. The root element of WSDL is definitions, and it has five types of child elements: types, message, portType, binding, and service. Let us briefly map our examples to the elements.

Listing 13.3 WSDL for document-centric messaging over SOAP
<?xml version="1.0"?>
<definitions name="TravelServiceMessaging"
    targetNamespace="http://example.com/travel-msg.wsdl"
          xmlns:tns="http://example.com/travel-msg.wsdl"
          xmlns:xsd1="http://example.com/travel-msg.xsd"
          xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
          xmlns="http://schemas.xmlsoap.org/wsdl/"
          xmlns:xsd="http://www.w3.org/2000/10/XMLSchema">

    <types>
       <schema targetNamespace="http://example.com/travel-msg.xsd"
              xmlns="http://www.w3.org/2000/10/XMLSchema">
           <element name="reserve" type="reserveType"/>
           <complexType name="reserveType">
               <element name="user" type="xsd:string"/>
               <element name="tourId" type="xsd:string"/>
               <element name="departure" type="dateType"/>
           </complexType>
           <complexType name="dateType">
               <element name="year" type="xsd:int"/>
               <element name="month" type="xsd:int"/>
               <element name="date" type="xsd:int"/>
           </complexType>
           <elementname="accepted"type="acceptedType"/>
           <complexType name="acceptedType">
           <element name="user" type="xsd:string"/>
           <element name="tourId" type="xsd:string"/>
           <element name="date" type="dateType"/>
             <element name="reservation_id" type="xsd:string"/>
       </complexType>
       </schema>
    </types>
    <message name="ReserveRequest">
        <part name="body" element="xsd1:reserve"/>
    </message>

    <message name="ReserveResponse">
        <part name="body" element="xsd1:Reservation"/>
    </message>

    <portType name="ReservePortType">
        <operation name="ReserveOperation">
           <input message="tns:ReserveRequest"/>
           <output message="tns:ReserveResponse"/>
        </operation>
    </portType>

    <binding name="ReserveSoapBinding" type="tns:ReservePortType">
        <soap:binding style="document"
            transport="http://schemas.xmlsoap.org/soap/http"/>
        <operation name="ReserveOperation">
           <soap:operation soapAction="http://example.com/reserve"/>
           <input>
               <soap:body use="literal"/>
           </input>
           <output>
               <soap:body use="literal"/>
           </output>
        </operation>
    </binding>

    <service name="ReserveService">
        <documentation>Travel Reservation Service</documentation>
        <port name="ReservePort" binding="tns:ReserveSoapBinding">
           <soap:address
               location=
  "http://demohost:8080/xmlbook2/chap12/servlet/SOAPTravelServlet"/>
  <!-- Indentation is broken for printing -->
        </port>
    </service>

</definitions>
message and types

The message section specifies an abstract typed definition of messages. In our example, we have two types of messages: a travel reservation request and a reservation acceptance response. These two message types are defined in Listing 13.3. The message sections do not specify the actual type definition for the messages. Such information is defined in the types section. Here, the types are defined with XML Schema, and the names of the types are referred to in the message section. For example, a reservation request is defined as ReserveType and is given reserve as an element name, and the message section named ReserveRequest specifies reserve in the element attribute in the part element.

portType

We want to define that two message types for a travel reservation are combined in a request/response manner. The operation element in this section provides a means for combining with input and output elements. The portType section can be defined as a collection of operations. We would have a travel search operation in this portType in addition to the reservation operation.

binding

This section specifies how to bind portType to a particular protocol. The example here takes SOAP over HTTP as the protocol, as shown in the transport attribute in the soap:binding element.[1] The operation element in this section specifies how each operation in portType is bound to the protocol in more detail. The soap:body element and the value literal of the attribute use indicates that the message defined in the message section is included in the SOAP body as it is.

[1] In WSDL, the core portion and the extensions are clearly separated. In the example, constructs qualified by wsdl belong to the core part, and the ones qualified by soap belong to the SOAP extension. Although HTTP and MIME bindings are defined in the WSDL specification in addition to SOAP binding, you can define your own bindings.

service

The port element in this section describes how the portType bound to a particular protocol can be accessed. The only missing information for that purpose is the access point, and it is specified by the location attribute of the soap:address element. A service itself is considered as a collection of ports.

Figure 13.2 illustrates the structure of WSDL documents. In the figure, solid lines indicate parent-child relationships between elements. Such relationships are clearly represented in XML, but WSDL defines additional relationships, shown in dashed lines in the figure. As an example, let us understand the relationship between binding and portType (indicated by a dashed line labeled "type") more precisely, referring to Listing 13.3. You can see ReservePortType in the attribute name of the portType element. On the other hand, the type attribute of the binding element is tns:ReservePortType. This indicates that the binding is a decoration of the ReservePortType port type. In the same manner, WSDL defines other relationships between elements, as shown in the figure.

Figure 13.2. Relationships between WSDL elements

graphics/13fig02.gif

Now we shift our focus to RPC. Listing 13.4 shows a WSDL document for RPC for the TravelServiceRPC class (refer to Listing 12.1) developed in Chapter 12. The key difference from Listing 13.3 is that the part elements in message sections correspond to parameters of the method reserve in the TravelServiceRPC class. For example, three parts of the first message element are parameters of reserve, while the part in Listing 13.3 is just an XML document. This indicates that the part element is intended to be a flexible mechanism for describing the logical abstract content of a message.

The difference in the parts affects the binding section. Let us look at the soap:binding element where the style attribute is rpc. This indicates that the message is used for RPC. Furthermore, in the soap:binding element, the message is defined to be encoded (use="encoded"), and the encoding style is specified in the encodingStyle attribute (SOAP encoding is specified here). Also, as we discussed in Chapter 12 (see Listings 12.7 and 12.8), we have to specify a namespace URI to indicate a resource that can process messages. The namespace attribute here indicates urn:reserve-tour-rpc as in the RPC examples in Chapter 12.

Listing 13.4 WSDL for SOAP-RPC
<?xml version="1.0" ?>

<definitions name="TravelServiceRPC"
             targetNamespace="http://example.com/travel-rpc.wsdl"
             xmlns:tns="http://example.com/travel-rpc.wsdl"
             xmlns:typens="http://xmlbook2/chap13/wsdl/types"
             xmlns:xsd="http://www.w3.org/1999/XMLSchema"
             xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
             xmlns:java="http://schemas.xmlsoap.org/wsdl/java/"
             xmlns="http://schemas.xmlsoap.org/wsdl/">

  <!-- type defs -->
  <types>
    <xsd:schema
      targetNamespace="http://xmlbook2/chap13/wsdl/types"
                xmlns:xsd="http://www.w3.org/1999/XMLSchema">
      <xsd:complexType name="ReservationType">
        <xsd:element name="user" type="xsd:string"/>
        <xsd:element name="tourId" type="xsd:string"/>
        <xsd:element name="date" type="xsd:timeInstant"/>
        <xsd:element name="reservationId" type="xsd:string"/>
      </xsd:complexType>
    </xsd:schema>
  </types>

  <!-- message declns -->
  <message name="ReserveRequest">
    <part name="user" type="xsd:string"/>
    <part name="tourId" type="xsd:string"/>
    <part name="date" type="xsd:timeInstant"/>
  </message>

  <message name="ReserveResponse">
    <part name="reservation" type="typens:ReservationType"/>
  </message>

  <!-- port type declns -->
  <portType name="TravelServiceRPCPortType">
    <operation name="ReserveOperation">
      <input message="tns:ReserveRequest"/>
      <output message="tns:ReserveResponse"/>
    </operation>
  </portType>

  <!-- binding declns -->
  <binding name="SOAPBinding" type="tns:TravelServiceRPCPortType">
    <soap:binding style="rpc"
                  transport="http://schemas.xmlsoap.org/soap/http"/>
    <operation name="ReserveOperation">
      <soap:operation soapAction=""/>
      <input>
        <soap:body use="encoded"
                   namespace="urn:reserve-tour-rpc"
                   encodingStyle=
                       "http://schemas.xmlsoap.org/soap/encoding/"/>
      </input>
      <output>
        <soap:body use="encoded"
                   namespace="urn:reserve-tour-rpc"
                   encodingStyle=
                       "http://schemas.xmlsoap.org/soap/encoding/"/>
      </output>
    </operation>
  </binding>

  <!-- service decln -->
  <service name="TravelServiceRPCService">
    <port name="SOAPPort" binding="tns:SOAPBinding">
      <soap:address
          location="http://demohost:8080/soap/servlet/rpcrouter"/>
    </port>
  </service>

</definitions>

We have overviewed WSDL using our travel reservation examples: documentcentric messaging and RPC. We have not covered all the features of WSDL or discussed them word by word because this book focuses on the programming aspects of XML. As for the details of WSDL, refer to the specification. The rest of this section further discusses how to use WSDL.

13.2.2 WSDL as an Interface Definition Language

The main purpose of WSDL is to provide a basis on which service providers can communicate with service requestors about information on the service. However, if you only want to do that, you may not have to use such a formal description as WSDL, but you may want to use a natural language description. Although WSDL is human-readable, it is also machine-readable so that programs can process it to automate some tasks. For example, a requestor program may process a WSDL document and automatically invoke the service without human intervention. In this sense, we can consider WSDL documents as specifications for enabling requestor and provider programs to interact with each other.

If WSDL is machine-readable, we can immediately come up with an idea for supporting program development. In the distributed object area, such as with CORBA, there is a concept of an Interface Definition Language (IDL). In the same manner, we can consider WSDL as an IDL in Web services. A number of WSDL tools exist, and most of them are based on IDL technologies.

With an IDL perspective in mind, we can illustrate the relationship between WSDL and programs, as shown in Figure 13.3. If we consider WSDL as an IDL, we can compile a WSDL document to generate client stub code and a server skeleton. As in an IDL, the client stub serves as a proxy of the server application, so a client application can invoke the server application by invoking the client stub locally. As a result, the client application does not have to care about complicated pre- and postprocessing for remote invocation. On the other hand, service providers can develop a server application by filling in some codes in the server skeleton, ensuring that the completed application conforms to the WSDL document.

Figure 13.3. Relationships between WSDL and programs

graphics/13fig03.gif

Furthermore, apart from IDL concepts, when you already have a server application, you may want to automatically or semiautomatically generate a WSDL document. Especially when the application is written in Java, you can use the Java reflection API. Therefore, there are a number of WSDL generation tools for Java (see Section 13.2.3).

The perspective in Figure 13.3 indicates that we can define a mapping between a WSDL document and programs. Let us go into the details of the mapping with examples. Listing 13.5 shows an example of a server skeleton, which is an abstraction of our TravelServiceRPC class (see Listing 12.1), and would be generated from the WSDL in Listing 13.4. The interface name is based on the name attribute of the portType type element. Note that we introduce a convention so that PortType is viewed as a suffix added to the interface name. For example, we remove PortType from TravelServiceRPCPortType to get the interface name.

Listing 13.5 Server skeleton example
package chap13.generated;

public interface TravelServiceRPC {
    public chap12.travel.Reservation
        reserve(java.lang.String user,
                java.lang.String tourId,
                java.util.Date date);
}

The method name reserve comes from the name attribute of the operation element in portType. We remove Operation from ReserveOperation and make the first character a lowercase (R->r). As for parameters, we traverse operation, input, message, and part, as shown in Figure 13.2. As for the first part, the name attribute is user, and the type attribute is xsd:string. Therefore, we can generate a definition of the parameter, such as java.lang.String user.

As for the return value, we traverse operation, output, message, and part. Because the type attribute of the part is typens:ReservationType, we could place chap12.travel.Reservation as a return type for the reserve method. Note that we have to provide the mapping between ReservationType and chap12.travel.Reservation somewhere else, although the definition of Reservation can be automatically generated based on the JavaBeans convention of setter/getter. The package name chap13.generated should also be provided.

So far, we have discussed the relationship between a WSDL document and a server skeleton. When we have conventions such as removing PortType from the interface name and providing a small amount of information such as a package name, the mapping can be performed automatically. We can reuse that idea here when considering the client stub, described next.

Listing 13.6 shows the client stub code that would be generated from the WSDL document. The target platform for this code is Apache SOAP, and the code for another platform such as Apache Axis could be quite different from Listing 13.6. Let us compare Listing 13.6 with ApacheSOAPRequestor in Listing 12.23 in Chapter 12. As you can see, we extract a typical pattern of RPC invocation for requestor programs and fill in information extracted from a WSDL document in the pattern. For example, the class name TravelServiceRPCPrxoy is based on the name attribute of the portType element. More specifically, we remove PortType from TravelServiceRPCPortType and add Proxy to create the class name. The construction of the method interface is exactly the same as for the server skeleton. The definition of the reserve method consists of the instantiation of the Call class, the parameter setup for the object, and the invocation of the object. The pattern here is typical, and you can find the necessary parameters from the WSDL document.

Listing 13.6 Client stub example
package chap13.generated;

import java.net.URL;
import java.util.*;
import org.apache.soap.util.xml.*;
import org.apache.soap.*;
import org.apache.soap.rpc.*;
import org.apache.soap.encoding.*;
import org.apache.soap.encoding.soapenc.*;

import chap12.travel.Reservation;

public class TravelServiceRPCProxy {
    private URL url;
    private SOAPMappingRegistry smr = new SOAPMappingRegistry();
    public TravelServiceRPCProxy(URL url){
        this.url = url;
        BeanSerializer beanSer = new BeanSerializer();
        smr.mapTypes(Constants.NS_URI_SOAP_ENC,
                     new QName("urn:xml-soap-travel-demo",
                               "reservation"),
                               chap12.travel.Reservation.class,
                               beanSer, beanSer);
    }
    public chap12.travel.Reservation
        reserve(java.lang.String user,
                    java.lang.String tourId,
                    java.util.Date date)
        throws Exception
    {
        Call call = new Call ();
        call.setSOAPMappingRegistry(smr);
        call.setTargetObjectURI(
            "urn:reserve-tour-rpc");
        call.setMethodName ("reserve");
        call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
        Vector params = new Vector ();
        params.addElement(
            new Parameter("user", java.lang.String.class,
                          user, null));
        params.addElement(
            new Parameter("tourId", java.lang.String.class,
                          tourId, null));
        params.addElement(
            new Parameter("date", java.util.Date.class,
                          date, null));
        call.setParams (params);
        Response resp = call.invoke (this.url, "");
        if (resp.generatedFault ()) {
            Fault fault = resp.getFault ();
            throw new Exception(fault.toString());
        } else {
            Parameter result = resp.getReturnValue ();
            return (chap12.travel.Reservation)result.getValue ();
        }
    }
}

In this section, we described the relationship between WSDL documents and programs in greater detail. You have already seen the basic principle for WSDL compilation and code generation shown in Figure 13.3. Next we cover the existing WSDL tools that are applications of the principle we explained here.

13.2.3 WSDL Tools

Tools for the three operations shown in Figure 13.3 are provided in Web Services Toolkit (WSTK) 2.4, included on the CD-ROM.[2] Some good articles on how to use the WSDL tools are already available (such as Graham Grass's article on IBM developerWorks).[3] Here, we only briefly overview the tools to see how they map WSDL and Java programs.

[2] As we write this book, WSTK 3.01 is the latest version, and you cannot get WSTK 2.4 at its Web page (http://www.alphaworks.ibm.com/tech/webservicestoolkit). Therefore, we extracted the WSDL tools from WSTK 2.4 and included them on the CD-ROM.

[3] See the Web page at http://www-106.ibm.com/developerworks/webservices/library/ws-peer4/. The article is based on WSTK 2.1, but the basic steps are the same.

Before executing the tools, you need to install and set up WSTK 2.4. You should find wstk4.zip under the tools directory on the CD-ROM. Unzip the zip file under "R:\", and then you can see the R:\wstk-2.4 directory. In the chap12 directory, we have the modified TravelServiceRPC and Reservation classes.

Because the WSDL tools in WSTK 2.4 cannot handle the Java Date class properly, we changed Date to String. The necessary jar files are placed under the lib directory. You need to add these jar files (wstk.jar, xerces.jar, wsdl4j.jar, and soap.jar) and tools.jar to the class path, provided with the Java Development Tools.

WSDL Generation from Server Code

You can generate a WSDL document from your requestor application. Let's try out the WSDL generation tool with our TravelServiceRPC. Execute the following command:

R:\wstk-2.4\wsdlegen>java com.ibm.wstk.swrapper.ui.WizardMain

Then the GUI shown in Figure 13.4 appears. In the GUI, specify the class name chap12.travel.provider.TravelServiceRPC and its classpath.[4] When you specify the class name, default values for Service Name, Service URN, and so on are automatically generated based on a convention. Then press the Next button. In the next GUI, shown in Figure 13.5, select the reserve method to publish the method as a service, and press the Next button. Finally, you are required to select a complex type to wrap in the WSDL document, as shown in Figure 13.6. Select Reservation and press the Next button. After you press Finish in a confirmation window, two WSDL documents are generated: one for the interface, the other for the implementation. We will discuss the distinction later.

[4] To execute this, we have modified the TravelServiceRPC and Reservation classes, changing Date to String. They are different from the ones provided in Chapter 11.

Figure 13.4. WSDL Generation Tool GUI, screen 1

graphics/13fig04.gif

Figure 13.5. WSDL Generation Tool GUI, screen 2

graphics/13fig05.gif

Figure 13.6. WSDL Generation Tool GUI, screen 3

graphics/13fig06.gif

Generating a Server Skeleton from WSDL

Next we generate a server skeleton from the generated WSDL document. Issue the following command:

R:\wstk-2.4\servicegen>java com.ibm.wstk.tools.gen.ui.Main 杢arget server
       ..\wsdlgen\TravelServiceRPC_Service-interface.wsdl

In addition to the server skeleton, a Reservation class is also generated. These classes are found in the com\travelservicerpcservice\www\Travel ServiceRPC_interface directory and its subdirectory (the types directory).

Generating a Client Stub from WSDL

Furthermore, you can generate a client stub from the WSDL document. Issue the following command:

R:\wstk-2.4\proxygen>java com.ibm.wstk.tools.gen.ui.Main -target client
       ..\wsdlgen\TravelServiceRPC_Service-interface.wsdl

Although the generated code looks complicated, the core idea is the same as in our client stub example in Listing 13.6.

In this section, we explained how to use the WSDL tools provided by WSTK 2.4. If you compare the generated WSDL document, server skeleton, and client stub, you may notice that there is a convention to relate them. In other words, once you define such a convention, you can easily develop WSDL tools. In the next section, we develop a prototype of a WSDL tool.

13.2.4 Programming with WSDL4J

The WSDL tools provided by WSTK 2.4 are enough for many cases. However, you may want to operate on a WSDL document directly or develop your own WSDL tools. For that, you need to work with a lower-level API for WSDL. Such an API is being developed through the JSR process (JSR 110), and there is an open source project called WSDL for Java (WSDL4J). The goal here is to understand WSDL in terms of programming, using WSDL4J. Although you may feel that working with WSDL is too much detail, we encourage you to walk through the WSDL4J API because WSDL is becoming a central entity in Web services (see Sections 13.2.5 and 13.5, for example).

The basic idea of WSDL4J is simply to prepare a Java class for each WSDL element and to prepare operations on the class. For example, a Definition class is prepared for the definitions element, a Binding class is prepared for the binding element, and so on. Table 13.1 summarizes the main classes.

To show how to use WSDL4J, we develop a prototype of the WSDL compiler. Because the prototype is too big to include in the book, we only show some excerpts from it. You can find the complete source code in chap13.wsdl.WSDLCompiler.java. Our WSDLCompiler generates a server skeleton and a client stub from a WSDL document. You can execute it as follows:

R:\samples>java chap13.wsdl.WSDLCompiler
              chap13\wsdl\travel-rpc.wsdl
-----  Generating Server Skeleton ------
package chap13.generated;

 public interface TravelServiceRPC {
     public chap12.travel.Reservation reserve(java.lang.String user,
java.lang.St

Table 13.1. WSDL Elements and WSDL4J Classes

WSDL ELEMENT

WSDL4J CLASS

HOW TO ACCESS THE OBJECT

definitions

Definition

Created by WSDLReader.readWSDL

types

N/A

You can get a DOM element only by using Definition.getTypesElement at this moment.

message

Message

Definition.getMessage/getMessages

part

Part

Message.getPart/getParts/getOrderdParts

portType

PortType

Definition.getPortType/getPortTypes

operation

Operation

PortType.getOperation/getOperations

input

Input

Operation.getInput

output

Output

Operation.getOutput

binding

Binding

Definition.getBinding/getBindings

soap:binding

SOAPBinding

Use Binding.getExtensibilityElements, and then check the type of each of the returned elements.

operation

BindingOperation

Binding.getBindingOperation/ getBindingOperations

soap:operation

SOAPOperation

Use BindingOperation.getExtensibility Elements(), and then check the type of each of the returned elements.

input

BindingInput

BindingOperation.getInput

output

BindingOutput

BindingOperation.getOutput

soap:body

SOAPBody

Use BindingInput/BIndingOutput. getExtensibilityElements, and then check the type of each of the returned elements.

 ring tourId, java.util.Date date) throws Exception;
 }
 -----  Generating Client Stub ------
 package chap13.generated;
 ................

When you specify WSDL for the RPC travel service (see Listing 13.4), the server skeleton as in Listing 13.5 and the client stub as in Listing 13.6 are generated. Note that the current code just prints the server and client code on the screen, but you can easily modify it to save the generated code in files.

Let us walk through WSDLCompiler. Listing 13.7 shows an excerpt from WSDLCompiler.java. The listing includes a class definition, member variables, a constructor, and the main() method. The member variable mappingXsdJava is a mapping table from types used in WSDL to Java classes. Although we should register all basic types in XML Schema, we only added String and Date classes for now, as you can see in the constructor. The member variable mappingQnameClass is a mapping table from qualified names to Java classes. Apache SOAP requires this mapping table. The main method loads a WSDL document to create a Definition object and creates a WSDLCompiler object. Then it sets up travel service杝pecific information (setTravelInfo), generates the server skeleton (printServerSkeletons), and generates the client stub (printClientStubs).

Listing 13.7 Part of WSDLCompiler (constructor and main() method)
public class WSDLCompiler {
    private Definition def;
    static public String PORT_TYPE="PortType";
    static public String OPERATION="Operation";
    static public String XSD_NAMESPACE=
        "http://www.w3.org/1999/XMLSchema";
    private Hashtable mappingXsdJava = new Hashtable();
    private Hashtable mappingQnameClass = new Hashtable();

    public WSDLCompiler(Definition def) {
        this.def = def;
        mappingXsdJava.put(new QName(XSD_NAMESPACE,"string"),
                            "java.lang.String");
        mappingXsdJava.put(new QName(XSD_NAMESPACE,"timeInstant"),
                            "java.util.Date");
    }
    public static void main(java.lang.String[] args) {
        try {
            InputSource src =
                new InputSource(new FileReader(args[0]));
            Definition def =
                WSDLReader.readWSDL(
                    new URL("http://example.com/wsdlhome/"), src);
            WSDLCompiler comp = new WSDLCompiler(def);
            comp.setTravelInfo();
            System.out.println(
                "-----  Generating Server Skeleton ------");
            comp.printServerSkeletons(new PrintWriter(System.out));
            System.out.println(
                "-----  Generating Client Stub ------");
            comp.printClientStubs(new PrintWriter(System.out));
        } catch(Exception ex) {
            ex.printStackTrace();
        }
    }

Although we can develop a complete compiler, some portion of the compiler depends on our travel service example for simplicity. Listing 13.8 shows member variables and a method specialized for the travel service. The method setTravelInfo first registers the Reservation class to the mapping tables and then prepares a package definition and import declarations for the server and client programs. Ideally, such information should be separated to describe in other files. However, as shown in Table 13.1, because the implementation for types is not available for now, we could not work on types. Except for this portion, WSDLCompiler is not dependent on the travel service.

Listing 13.8 Method and member variables for setting up travel-specific information
private String serverHeader = "";
public String clientHeader = "";
static public String SOAP_CLIENT_CLASS_CONST_DEF =
    "(URL url){\n" +
    "        this.url = url;\n" +
    "    }\n";
static public String SOAP_CLIENT_CLASS_DEF_HEADER =
    "    private URL url;\n";

public void setTravelInfo() throws Exception {
    // Register Java classes for compiler and Apache SOAP
    String travelType="http://xmlbook2/chap13/wsdl/types";
    mappingXsdJava.put(
        new QName(travelType,"ReservationType"),
                  "chap12.travel.Reservation");
    mappingQnameClass.put(
        new QName("urn:xml-soap-travel-demo", "reservation"),
                  "chap12.travel.Reservation");
    // Prepare headers for client and server
    serverHeader =
        "package chap13.generated;\n\n";
    clientHeader =
        "package chap13.generated;\n\n";
    clientHeader += "import java.net.URL;"+"\n";
    clientHeader += "import java.util.*;"+"\n";
    clientHeader += "import org.apache.soap.util.xml.*;"+"\n";
    clientHeader += "import org.apache.soap.*;"+"\n";
    clientHeader += "import org.apache.soap.rpc.*;"+"\n";
    clientHeader += "import org.apache.soap.encoding.*;"+"\n";
    clientHeader +=
        "import org.apache.soap.encoding.soapenc.*;"+"\n\n";
    clientHeader += "import chap12.travel.Reservation;\n\n";
}

Listing 13.9 shows the methods for generating a server skeleton. The printServerSkeletons method invokes the printServerSkeleton method for each binding, and a Java interface is created for each portType. Each method is generated by getMethodSignature. To understand this method, refer to Figure 13.2 and Table 13.1. For example, the method name is found in the operation element, which is traversed via binding and operation under binding.

Listing 13.9 Methods for generating server skeletons
public void printServerSkeletons(Writer wrt) throws Exception {
    Map map = def.getBindings();
    Iterator itr = map.keySet().iterator();
    while(itr.hasNext()){
         Binding binding = (Binding)map.get(itr.next());
         printServerSkeleton(wrt, binding);
    }
    wrt.flush();
}
public void printServerSkeleton(Writer wrt, Binding binding)
    throws Exception
{
    QName name = binding.getQName();
    PortType portType = binding.getPortType();
    wrt.write(serverHeader);
    wrt.write("public interface " +
              removeSuffix(portType.getQName()
                  .getLocalPart(),PORT_TYPE) +
              " {\n");
    SOAPBinding soapBinding = getSOAPBinding(binding);
    if (soapBinding==null){
        throw new Exception(
            "No SOAP binding is defined under "+ name);
    }
    Iterator bindingOps =
        binding.getBindingOperations().iterator();
    while(bindingOps.hasNext()){
        BindingOperation bindingOp =
            (BindingOperation)bindingOps.next();
        SOAPOperation soapOp = getSOAPOperation(bindingOp);
        if (soapOp==null){
            throw new Exception(
                "No SOAP operation is defined under "+
                name + "/" + bindingOp.getName());
        }
        wrt.write("    " + getMethodSignature(bindingOp) +
                  ";\n");
    }
    wrt.write("}\n");
}
private String getMethodSignature(BindingOperation bindingOp)
    throws Exception
{

    String resp="";
    SOAPBody inputSOAPBody =
        getSOAPBody(bindingOp.getBindingInput());
    SOAPBody outputSOAPBody =
        getSOAPBody(bindingOp.getBindingOutput());
    Operation op=bindingOp.getOperation();
    Message inputMessage=op.getInput().getMessage();
    Message outputMessage=op.getOutput().getMessage();
    resp += "public ";
    Part outputPart =
        (Part)outputMessage.getOrderedParts(null).get(0);
    String returnType =
        (String)mappingXsdJava.get(outputPart.getTypeName());
    if(returnType==null)
        throw new Exception(
            "No Java type for " +
            outputPart.getTypeName().toString());
    resp += returnType + " ";
    resp +=
        removeSuffix(op.getName(),OPERATION).toLowerCase()+"(";
    Iterator parts =
        inputMessage.getOrderedParts(null).iterator();
    while(parts.hasNext()){
         Part part = (Part)parts.next();
         String javaType =
             (String)mappingXsdJava.get(part.getTypeName());
         if(javaType==null)
             throw new Exception("No Java type for " +
                                 part.getTypeName().toString());
         resp += javaType + " " + part.getName();
         if (parts.hasNext())
             resp += ", ";
    }
    resp += ") throws Exception";
    return resp;
}

As shown in Table 13.1, SOAP-specific elements cannot be gotten directly from core element (qualified by wsdl) classes. Rather, extensions like SOAP are accessed in a different way. The getSOAPBinding method invoked in printServer Skeleton shows how to get SOAP-specific elements (see Listing 13.10). As you can see, Binding provides a method for getting extensibility elements, and SOAPBinding objects could be included in the elements. Therefore, we check whether each element is an object of the SOAPBinding class.

Listing 13.10 Method for finding SOAPBinding
public SOAPBinding getSOAPBinding(Binding binding) {
    Iterator exEls =
        binding.getExtensibilityElements().iterator();
    SOAPBinding soapBinding=null;
    while(exEls.hasNext()){
        Object ex=exEls.next();
        if (ex instanceof SOAPBinding) {
            soapBinding = (SOAPBinding)ex;
            break;
        }
    }
    return soapBinding;
}

As for client stub generation, we only show the writeMethodStub method in Listing 13.11. As in server skeleton generation, the printClientStubs method is invoked first, then printClientStub is invoked for each binding, and finally writeMethodStub is invoked for each operation. In the method, first the signature is generated, as in server skeleton generation. Because the target platform is Apache SOAP, the main task is to pick up the necessary information from the WSDL document to fill it in in a typical pattern for Apache SOAP requestors.

Listing 13.11 Method for generating client stub code
private void writeMethodStub(Writer wrt,
                             BindingOperation bindingOp)
    throws Exception
{
    String signature = getMethodSignature(bindingOp);
    wrt.write("    " + signature + "{\n");
    wrt.write("        " +
              "SOAPMappingRegistry smr = " +
              "new SOAPMappingRegistry();\n");
    wrt.write("        " +
              "BeanSerializer beanSer = " +
              "new BeanSerializer();\n");
    for (Enumeration keys = mappingQnameClass.keys();
         keys.hasMoreElements() ;)
    {
        QName qname=(QName)keys.nextElement();
        wrt.write("        " +
                  "smr.mapTypes(Constants.NS_URI_SOAP_ENC,\n");
        wrt.write("        " + "             new QName(\"" +
                  qname.getNamespaceURI() + "\", \"" +
                  qname.getLocalPart() + "\"), \n");
        wrt.write("        " + "                       " +
                  mappingQnameClass.get(qname) +
                  ".class, beanSer, beanSer);\n");
    }
    wrt.write("        " + "Call call = new Call ();\n");
    wrt.write("        " +
              "call.setSOAPMappingRegistry(smr);\n");

    SOAPBody inputSOAPBody =
        getSOAPBody(bindingOp.getBindingInput());
    SOAPBody outputSOAPBody =
        getSOAPBody(bindingOp.getBindingOutput());
    Operation op=bindingOp.getOperation();

    wrt.write("        " + "call.setTargetObjectURI (\"" +
              inputSOAPBody.getNamespaceURI() + "\");\n");
    wrt.write("        " + "call.setMethodName (\"" +
              removeSuffix(op.getName(),OPERATION).toLowerCase()
              +"\");\n");
    wrt.write("        " + "call.setEncodingStyleURI" +
              "(Constants.NS_URI_SOAP_ENC);\n\n");
    wrt.write("        " + "Vector params = new Vector ();\n");

    Message inputMessage=op.getInput().getMessage();
    Message outputMessage=op.getOutput().getMessage();
    Iterator parts =
        inputMessage.getOrderedParts(null).iterator();

    while(parts.hasNext()){
        Part part = (Part)parts.next();
        String javaType =
            (String)mappingXsdJava.get(part.getTypeName());
        if(javaType==null)
            throw new Exception(
                "No Java type for " +
                part.getTypeName().toString());
        wrt.write("        " +
                  "params.addElement (new Parameter(\"" +
                  part.getName() + "\", " + javaType +
                  ".class, " + part.getName() +", null));\n");
    }
    wrt.write("        " + "call.setParams (params);\n");
    SOAPOperation soapOp = getSOAPOperation(bindingOp);
    wrt.write("        " +
              "Response resp = call.invoke (this.url, \"" +
              soapOp.getSoapActionURI()  + "\");\n");

    Part outputPart =
        (Part)outputMessage.getOrderedParts(null).get(0);
    String returnType =
        (String)mappingXsdJava.get(outputPart.getTypeName());
    if(returnType==null)
        throw new Exception("No Java type for " +
            outputPart.getTypeName().toString());

    wrt.write("        " + "if (resp.generatedFault ()) {\n");
    wrt.write("        " +
              "    Fault fault = resp.getFault ();\n");
    wrt.write("        " +
              "    throw new Exception(fault.toString());\n");
    wrt.write("        " + "} else {\n");
    wrt.write("        " +
              "    Parameter result = " +
              "resp.getReturnValue ();\n");
    wrt.write("        " + "    return (" + returnType +
              ")result.getValue ();\n");
    wrt.write("        " + "}\n");
    wrt.write("    " + "}\n");
}

In this section, we examined WSDL4J, developing a prototype of a WSDL compiler. Although we briefly walked through the code, we encourage you to look at the code on the CD-ROM and play with it. We are sure that programming with WSDL4J will improve your understanding of WSDL.

13.2.5 JAX-RPC

So far, we have shown how to compile WSDL documents to generate client stub and server skeleton code in greater detail. As shown in Listing 13.6, the client stub tends to depend on a particular SOAP engine. For instance, our stub code is based on Apache SOAP and invokes an object of the Call class to perform RPC. This indicates that it is advantageous to define a standard API for RPC because client stub code generated from WSDL documents can be executed on any SOAP engine. Java API for XML RPC (JAX-RPC) defines an API for XML-based RPC and how to compile WSDL documents.

Figure 13.7 illustrates the JAX-RPC architecture from a requestor point of view. First, client stub code like that in Listing 13.6 is generated from a WSDL document. The requestor application can invoke a remote method via SOAP over HTTP using the stub code. The JAX-RPC runtime is a SOAP engine that implements the JAX-RPC API, and the client stub must invoke the API by definition.

Figure 13.7. JAX-RPC client architecture

graphics/13fig07.gif

Call is a central class of the client-side core API in JAX-RPC and is similar to the Call class in Apache SOAP. Here is an example to show how to use the Call class:

javax.xml.rpc.Service service = //... get a Service instance
javax.xml.rpc.Call call =
    service.createCall(portName, ?<operationName>?);
Object[] params = new Object[] {?<SomeString>?};
Integer ret = (Integer) call.invoke(params);

The Service class (in the first line) indicates the service section in WSDL, and a Call object is created by feeding the port name and operation name to a Service object. Actual RPC invocation is performed by the invoke method, giving the method parameters as an array of Object objects. As you can see, in spite of the API difference, the basic concept of the Call class is similar to one in Apache SOAP. On the other hand, note that JAX-RPC has introduced WSDL- specific classes such as Service. This indicates that JAX-RPC can go beyond the API for SOAP engines to invoke non-SOAP services. In other words, non-SOAP services are described with WSDL, considering WSDL as an IDL of Web services. Such a direction has been shown in enterprise Web services (JSR 109), described in Section 13.5.

We also have to carefully consider the relationship between Java API for XML Messaging (JAXM) and JAX-RPC. In theory, we can provide the DCM capability with JAXM and can provide the RPC capability with JAX-RPC. Accordingly, we should be able to develop a JAX-RPC runtime on top of JAXM. However, JAX-RPC depends only on the javax.xml.soap package of JAXM and does not rely on the transport packages of JAXM. Furthermore, JAX-RPC itself can perform DCM with SOAP.[5] This implies that JAX-RPC may do anything we want, including SOAP-RPC and SOAP-DCM, without JAXM.

[5] However, when DCM is included in WSDL as in Listing 13.3, its processing seems unclear in JAX-RPC (as long as we are concerned with JAX-RPC 0.7).

For now, Apache Axis (mentioned in Chapter 12, Section 12.3.2) partially supports JAX-RPC, and we expect that it will become fully compliant with JAX-RPC. Also, Axis developers are intentionally working on WSDL support. We encourage you to check the latest status of Axis.

    [ directory ] Previous Section Next Section