Previous section   Next section

6.1 Using WSDL with JAX-RPC

The book web service example that was used in Chapter 2 demonstrates how to create a web service client application when you have access to a description of the service interface in the form of a Java interface definition. Although this approach is convenient, it is unlikely that you will be able to use it when writing a client for a third-party web service. Web services are not always implemented in Java and, even for those that are, the public definition of the service interface is almost always provided in the form of a WSDL document rather than Java class files.

One way to create a web service client from a WSDL file is to start by pointing the wscompile tool at the WSDL document. To do this, you change the content of the config.xml file so that it contains a wsdl element instead of the configuration element shown in Example 2-9. To see how this works, imagine that the book web service from Chapter 2 had been created by a third party so that you don't have access to Java class files for the BookQuery interface or the BookInfo class that is used by the methods in that interface. You can, however, obtain a WSDL file that describes the service because, as noted in Section 2.2.7.4 in Chapter 2, when a service is deployed using the JAX-RPC reference implementation, you can append the query string ?WSDL to a URL that references the service in order to fetch the WSDL definition of that service. For other implementations, the URL may be different, and in some cases, you may have to download the WSDL document from a registry.

Example 6-1 shows the content of a config.xml file that can be used with wscompile to generate client-side artifacts from a WSDL document.

Example 6-1. A config.xml file referencing a WSDL document
<?xml version="1.0" encoding="UTF-8" ?>
 
<configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config">
    <wsdl location="http://localhost:8000/Books/BookQuery?WSDL" 
          packageName="ora.jwsnut.chapter6.wsdlbookservice"/>
</configuration>

The wsdl element has two attributes:

location

A URI that gives the location of the WSDL document. In most cases, this is a URL. Here, it is the URL of the WSDL file generated for the book web service developed in Chapter 2 and deployed in the Tomcat or J2EE web container. The wscompile utility also accepts a filename so that you can reference a WSDL document held locally, such as one downloaded from a registry.

packageName

The name of the Java package into which all of the generated classes are placed. If the WSDL document contains definitions in more than one namespace, it is possible to arrange for the Java classes that correspond to these definitions to be placed in different packages based on their owning namespaces. For details, see Section 8.1.

The command line used to generate client-side artifacts from WSDL is the same as that required when you supply Java interface definitions, since wscompile distinguishes the two cases based only on the content of config.xml:

wscompile -gen:client -keep -s  generated/client  -d  output/client 
    -classpath classpath config.xml

To see what this generates for the WSDL document corresponding to the book web service, you must first start the web container and deploy the book web service as described in Chapter 2.

If you are using the beta release of J2EE 1.4 to run the examples for this book, you need to work around a bug that prevents clients generated from WSDL documents from working. If you go to the directory repository\applications\Books beneath the installation directory of the J2EE reference implementation, you will find a file whose name will be something like JAX-RPC Book Service25695.wsdl (the numeric part will probably be different on your system). Open this file with an editor and go to the last line, which should contain a <soap:address> tag. You'll see that this tag has an attribute called location, which contains the URL of the deployed book service � something like http://localhost:8000//Books/BookQuery. The fact that there are two "/" characters before Books causes the client to fail when it connects to the service. To fix this problem, just replace the "//" pair with one slash, thus making the address http://localhost:8000/Books/BookQuery. This problem will hopefully be fixed in the FCS release of J2EE 1.4.

Open a command window, make your working directory chapter6\wsdlbookservice relative to the example source code for this book, and type the command:

ant generate-client

This buildfile target runs the wscompile command line shown above, the output from which is written to the following directories:

Name

Directory

Java source code

chapter6\wsdlbookservice\generated\client\ora\jwsnut\chapter6\wsdlbookservice

Compiled class files

chapter6\wsdlbookservice\output\client\ora\jwsnut\chapter6\wsdlbookservice

where the ora\jwsnut\chapter6\wsdlbookservice suffix is determined by the value of the packageName attribute of the wsdl element, as shown in Example 6-1. Table 6-1 lists some of the Java source files that are created by this command.

Table 6-1. A subset of the client-side source files generated by wscompile from a WSDL document

Source

Generated files

Service

BookService.java

 

BookService_Impl.java

 

BookService_SerializerRegistry.java

Exception

BookServiceException.java

 

BookServiceException_SOAPSerializer.java

 

BookServiceException_SOAPBuilder.java

Value type

BookInfo.java

 

BookInfo_SOAPSerializer.java

 

BookInfo_SOAPBuilder.java

BookQuery interface

BookQuery.java

 

BookQuery_Stub.java

 

BookQuery_getAuthor_RequestStruct.java

 

BookQuery_getAuthor_ResponseStruct.java

 

BookQuery_getAuthor_RequestStruct_SOAPBuilder.java

 

BookQuery_getAuthor_ResponseStruct_SOAPBuilder.java

 

BookQuery_getAuthor_RequestStruct_SOAPSerializer.java

 

BookQuery_getAuthor_ResponseStruct_SOAPSerializer.java

 

BookQuery_getBookCount_RequestStruct.java

 

BookQuery_getBookCount_ResponseStruct.java

 

BookQuery_getBookCount_RequestStruct_SOAPSerializer.java

 

BookQuery_getBookCount_ResponseStruct_SOAPSerializer.java

It is interesting to compare this list of source files with the content of Table 2-5 in Chapter 2, which shows what is generated when you start from a Java interface definition. You'll see that you get essentially the same set of files, whether you start from a Java interface definition or the corresponding WSDL document. In the latter case, of course, wscompile generates the class for the BookQuery interface, together with BookInfo and BookServiceException, whereas in Chapter 2, these are the files that we started with. In general, in order to write a client for a web service for which you have only a WSDL definition, you need to create the corresponding Java interface definition (i.e., the equivalent of the BookQuery and BookInfo classes in this example). Here, we obtain those files by using the -gen:client option of wscompile, which also generates the client-side stubs. If you just want to generate the interface files (perhaps because you intend to use one of the methods of accessing the web service described later that do not require client-side stubs), you can use the -import option of wscompile instead, as described in Section 6.4, later in this chapter.

By using the WSDL document generated from the Java service definitions for the book web service as the input to wscompile, we have performed a round-trip from Java source code to WSDL and back again. However, the source code that we end up with does not exactly match what we started with � for one thing, if you compare the content of the generated BookInfo.java file with that created manually in Chapter 2, you'll notice that the parameter order for the constructor is different. The JAX-RPC specification does not require implementations to create exactly the same source code as the result of a round-trip such as this, and, in the real world, you are unlikely to ever need to do this.

Another difference that is not apparent from the source code�but is nevertheless very important�is that when you get the stub for the BookQuery interface using a BookService object generated from a WSDL file, its target endpoint address may already have been set. To illustrate this, here is the code that we used in Chapter 2 to get a list of books from the web service:

// Get a reference to the stub and set the service address
BookService_Impl  service = new BookService_Impl(  );
BookQuery  bookQuery = (BookQuery)service.getBookQueryPort(  );
((Stub)bookQuery)._setProperty(Stub.ENDPOINT_ADDRESS_PROPERTY, args[0]);

BookInfo[] books = bookQuery.getBookInfo(  );

The code required to get the same list of books using the classes generated from the WSDL for this service, which you can find in the file chapter6\wsdlbookservice\client\ora\jwsnut\chapter6\client\WSDLBookServiceClient.java, is slightly different:

BookService_Impl service = new BookService_Impl(  );
BookQuery  bookQuery = (BookQuery)service.getBookQueryPort(  );

BookInfo[] books = bookQuery.getBookInfo(  );

The new code does not explicitly reference the stub class and does not set its ENDPOINT_ADDRESS_PROPERTY. There is no need to set the web service address because the stub that you obtain from the getBookQueryPort( ) method is preconfigured with this information, which is obtained from the soap:address element within the port element corresponding to the BookQuery portType in the WSDL file, if one exists. You can find the relevant portion of the WSDL document by pointing your web browser at the URL http://localhost:8000/Books/BookQuery?WSDL:

<service name="BookService">
  <port name="BookQueryPort" binding="tns:BookQueryBinding">
    <soap:address xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
        location="http://localhost:8000/Books/BookQuery"/>
  </port>
</service>

Note, however, that not all WSDL documents need contain a port element. As noted in Section 5.2.9.2, it is useful to create a WSDL document that describes a generic service (such as an electronic book store) without specifying its actual location. Service providers can then create and advertise their own WSDL documents to import the generic definition and additionally supply the location information for their implementation of that service. If you generate the client-side artifacts from the generic WSDL document (for any conforming electronic book store), then there will be no addressing information with which to preconfigure the stubs.

You can compile and run this example using the commands:

ant compile-client
ant run-client

As a result, you should see the same list of books as that returned by the original client developed in Chapter 2.

If, instead, you get the error message "Missing port information" and you are using J2EE 1.4 beta, you need to fix the problem described in the note earlier in this section.

To get the editor, author, or price of a specific book instead of the complete list, you can use the CLIENT_ARGS property to supply the required command-line arguments. Here are two examples:

ant -DCLIENT_ARGS="author Java Swing" run-client
ant -DCLIENT_ARGS="editor J2ME in a Nutshell" run-client

Notice that, since the deployed WSDL file for this service contains the service address, this version of the client does not require the address to be given as a command-line argument.

6.1.1 Stubs and One-Way Operations

Because the JAX-RPC specification requires only stubs to support request-response operations (as defined in Table 5-2), if you import a WSDL file for a service that contains a one-way operation, you'll need to add an empty output message to the operation element and pass your modified copy of the WSDL document to wscompile. This has the effect of making the operation appear to use the request-response pattern, in which no valid reply is expected.

For example, suppose a service defines a one-way operation to make a log entry, requiring a single string argument. The input message for this operation might be defined like this:

<message name="LogRequest">
  <part name="String_1" type="xsd:string"/>
</message>

The operation might be defined as follows:

<operation name="makeLogEntry" parameterOrder="String_1">
  <input message="tns:LogRequest"/>
</operation>

To convert this to a form acceptable to wscompile, you need to add an empty message:

<message name="LogEmptyResponse"/>

and then reference it from the operation element:

<operation name="makeLogEntry" parameterOrder="String_1">
  <input message="tns:LogRequest"/>
  <output message="tns:LogEmptyResponse"/>
</operation>

  Previous section   Next section