6.3 The Dynamic Invocation Interface

   

6.3 The Dynamic Invocation Interface

A JAX-RPC implementation typically implements dynamic proxies by using the more primitive dynamic invocation interface (or DII for short). As well as being used internally in this way, the DII is a public API that provides another way for application code to access a web service without needing to generate stub classes. Creating an application that uses the DII involves more coding effort than using either dynamic proxies or precompiled stubs. However, the DII makes it potentially possible to write clients that can work with web services that are not discovered until runtime, in much the same way as the Java reflection feature allows software tools to call methods on classes that it does not know about at compile time.

The steps required to use the dynamic invocation interface are as follows :

  1. A client application gets a Service object for a web service.

  2. Using the Service object, the application creates a Call object that is used to invoke the service's operations. Call is the central class of the DII.

  3. Methods of the Call interface are used to specify which operation is to be called, and to discover and list the Java and XML types of the operation's arguments and its return types.

  4. The application invokes the operation by calling the invoke( ) method of the Call object, which provides the result of the operation as its return value.

The Service interface provides five methods that can be used to obtain Call objects:

public Call createCall( ) throws ServiceException

This method returns a generic Call object that is not associated with any port or operation. It must be configured by application code before it can be used.

public Call createCall(QName portName) throws ServiceException

Returns a Call object that can be used to call any method of the named port. The setOperationName( ) method must be used to specify the operation to be invoked.

public Call createCall(QName portName, QName operationName) throws ServiceException

Returns a Call object associated with the specified operation of the given port.

public Call createCall(QName portName, String operationName) throws ServiceException

This method is the same as the previous one, except that the operation name is supplied as a string instead of a QName . The name must be the local part of the operation name; the namespace is implicitly taken to be the same as the one associated with the port.

public Call[] getCalls(QName portName)

Returns an array of Call objects, each of which is preconfigured to invoke one of the methods of the given port. The getOperationName( ) method can be used to discover which operation a given Call object from the returned array is associated with. This method works only if the Service object was obtained using the variant of the ServiceFactory createService( ) method that accepts a WSDL document location.

To demonstrate how to use the dynamic invocation interface, we'll use a simplified version of the book web service that we've used for earlier examples in this chapter and in Chapter 2. The endpoint interface for this web service is shown in Example 6-5.

Example 6-5. A small web service used to demonstrate the dynamic invocation interface
 package ora.jwsnut.chapter6.smallbookservice;

import java.rmi.Remote;
import java.rmi.RemoteException;
import javax.xml.rpc.holders.StringHolder;

/**
 * The interface definition for the small
 * book web service.
 */
public interface SmallBookQuery extends Remote {
    
    /**
     * Gets the number of books known to the service
     * @return the number of books known to the service.
     */
    public abstract int getBookCount(  ) throws RemoteException;

    /**
     * Gets the title of a given book.
     * @param index the index of the book whose title is required
     * @return the title of the given book, or <code>null</code> if
     * the index is not valid.
     */
    public abstract String getBookTitle(int index) throws RemoteException;
    
    /**
     * Gets the author for a books with a given title
     * @param title the titles of the book
     * @param author an output parameter that will be set to the author of the
     * given book
     * @throws SmallBookServiceException if the book title is unknown
     */
    public abstract void getBookAuthor(String title, StringHolder author) 
           throws SmallBookServiceException, RemoteException;
    
    /**
     * Makes a log entry.
     * @param value the value to be logged.
     */
    public abstract void log(String value) throws RemoteException;
} 

In order to run the example code used in this section, you should first deploy the service by opening a command window, making chapter6\smallbookservice your working directory, and typing the command:

 ant deploy 

Exactly how you use a Call object to invoke a web service method depends on whether the Service object from which it was created has an associated WSDL description of the service. We'll look first at the case where there is a WSDL definition. Later, you'll see how to call services for which you do not have such a definition, which requires a little more work.

As noted earlier in this chapter, 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\SmallBooks beneath the installation directory of the J2EE reference implementation, you will find a file whose name is something like JAX-RPC Small Book Service54134.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//SmallBooks/SmallBookQuery . The fact that there are two "/" characters before SmallBooks 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/SmallBooks/SmallBookQuery .

6.3.1 Dynamic Invocation of a Service Defined by WSDL

If you have access to a WSDL definition of a service, then it is very simple to invoke its methods using the DII. This is because the WSDL definition lists all of the available operations, together with the number and types of the arguments that each operation requires and the type of its return value, if there is one. As you'll see in the next section, in the absence of a WSDL document, you need to use the addParameter( ) and setReturnType( ) methods to supply this information yourself.

The first step when using DII is to obtain a Service object for the web service that you want to access. When the web service provides a WSDL description, you can obtain a Service object associated with the service definition that it contains from a ServiceFactory , in the usual way:

 QName serviceName = new QName(SERVICE_URI, "SmallBookService");
ServiceFactory factory = ServiceFactory.newInstance(  );
Service service = factory.createService(new URL(args[0]), serviceName); 

This code is intended to be used to connect to a service that implements the SmallBookQuery endpoint interface shown in Example 6-5, where the actual location of the WSDL definition is supplied as a command-line argument to the application. The WSDL document for the implementation of this service can be found at the URL http://localhost:8000/SmallBooks/SmallBookQuery?WSDL ; if you examine it, you'll find that the service that it defines is called SmallBookService . The namespace associated with this name is the target namespace of the WSDL document, which, in this case, is urn:jwsnut.chapter6.smallbookservice/wsdl/SmallBookQuery . For the sake of convenience, this URI is shown as SERVICE_URI throughout this section. This service name and namespace URI are used in the previous code to create the QName passed to the ServiceFactory createService( ) method to get the Service object for the service.

The next step is to use the Service object get a Call object for the operation that you want to invoke. The most direct way to achieve this is to use the Service createCall( ) method that accepts both a port name and an operation name as arguments. To get a Call object for the getBookCount( ) method, for example, use the following code:

 QName portName = new QName(SERVICE_URI, "SmallBookQueryPort");
Call call = service.createCall(portName, new QName(SERVICE_URI, 
                               "getBookCount")); 

Both the port name ( SmallBookQueryPort ) and the operation name ( getBookCount ) can be obtained from the WSDL document itself. Note that, as with the service description, both of these names are provided in QName form and therefore include both the simple name and the WSDL namespace value. There is, however, another variant of createCall( ) that allows you to supply the operation name as a simple string, allowing its namespace to be defaulted to that of the port:

 Call call = service.createCall(portName,  "getBookCount"); 

Whichever variant you use, both the port and the operation must be defined in the WSDL document passed to the ServiceFactory createService( ) method, and the operation must be valid for the given port. If these conditions are not met, a ServiceException is thrown.

The other variants of the createCall( ) method can be used to obtain Call objects that are not associated with a specific operation and port. Before attempting to invoke an operation using such an object, however, you need to fully specify both the port to be used and the operation to be performed. For example, the following code creates a Call object for the port SmallBookQueryPort and then calls the setOperationName( ) method to select from that port the operation that will be invoked:

 Call call = service.createCall(new QName(SERVICE_URI, 
     "SmallBookQueryPort"));
call.setOperationName(new QName(SERVICE_URI, "getBookCount")); 

To use a Call object returned by the zero-argument variant of createCall( ) , you need to specify the required port and operation. Unfortunately, the current Call API does not include a method that allows the port to be set programmatically. It does, however, include a method called setPortType( ) , which appears to set an attribute that is never used. It appears that this is an oversight in the definition of the API that renders the zero-argument createCall( ) method useless for application code.

You can use the setOperationName( ) method to change the operation that a Call object is associated with. For example, the following sequence is valid:

 Call call = service.createCall(new QName(SERVICE_URI, 
     "SmallBookQueryPort"));
call.setOperationName(new QName(SERVICE_URI, "getBookCount"));

// Use Call object to invoke the getBookCount(  ) method (code not shown)

call.setOperationName(new QName(SERVICE_URI, "getBookAuthor"));
// Use Call object to invoke the getBookAuthor(  ) method (code not shown) 

This feature allows you to conserve resources by creating only one Call object instead of one Call object per operation invocation.

It appears that, at the time of this writing, there is a bug in the reference implementation that devalues this technique if you associate a Call object with an operation, invoke that operation, then select another operation. Under some circumstances, an attempt to invoke the second operation fails, due to an apparent mismatch between the number of parameters supplied and the number that is expected.

Once you have a fully configured Call object, you can use the setProperty( ) method to set optional properties that might affect the call, such as authentication information. The properties that you can set are described in Section 6.3.5, later in this chapter. Finally, call the remote method using the invoke( ) method:

 public Object invoke(Object[] inputParams) throws RemoteException; 

The inputParams array contains the values to be used as method arguments, in the order in which they appear in the definition of the method in the original Java interface definition (if there is one), or in the operation element of the WSDL document. If the method does not require any arguments, then you can set the inputParams argument to null . Here, for example, is how you invoke the getBookCount( ) method shown in Example 6-5, which does not require any arguments:

 QName portName = new QName(SERVICE_URI, "SmallBookQueryPort");
Call call = service.createCall(portName, new QName(SERVICE_URI, 
     "getBookCount"));
Object result = call.invoke(null); 

In cases in which the remote method returns a Java primitive type, the invoke( ) method returns an instance of the Java wrapper class for that primitive type. In the case of the getBookCount( ) method, which returns an int , the actual return value from invoke( ) is an object of type java.lang.Integer :

 Object result = call.invoke(null);
int bookCount = ((Integer)result).intValue(  ); 

Similarly, to supply the value for an argument that is a Java primitive type, you should pass an instance of the corresponding wrapper class. To illustrate this, the following code invokes the getBookTitle( ) method, which requires an integer argument in the range of 0 to one less than the number of books returned by getBookCount( ) :

 Call call = service.createCall(portName, new QName(SERVICE_URI, 
     "getBookTitle"));
// Get title for book with index = 3
Object result = call.invoke(new Object[] { new Integer(3) });  
String title = (String)result; 

The getBookAuthor( ) method demonstrates a different approach to returning a value from a web service. Here is its definition:

 public void getBookAuthor(String title, StringHolder author) 
   throws SmallBookServiceException; 

The intent of this method is that the caller supplies the title of a book as the first argument, while the service returns the book's author in the StringHolder given as the second argument. Recall from Chapter 2 that Holder classes make it possible to have parameters whose values change as the result of a method call (i.e., output parameters), a feature that Java does not directly support. The specification of the invoke( ) method states that its Object[] argument should contain only those parameters that supply input values. Since author is a return value, the proper way to call this method using the DII is as follows:

 Call call = service.createCall(portName, new QName(SERVICE_URI, 
     "getBookAuthor"));
String title = "J2ME in a Nutshell";
call.invoke(new Object[] { title }); 

However, this code would not work if the WSDL definition that the Call object is associated with was originally created from a Java interface definition. The reason for this is that without additional information, it is not possible for the tool that generates the WSDL definition ( wscompile ) to tell whether the author argument is just an output parameter or whether it both supplies an input value and receives an output value. As a result, it assumes the latter and generates the WSDL shown in Example 6-6.

Example 6-6. The WSDL definitions for the getBookAuthor( ) method
 <message name="SmallBookQuery_getBookAuthor">  <part name="String_1" type="xsd:string"/>   <part name="String_2" type="xsd:string"/>  </message>
<message name="SmallBookQuery_getBookAuthorResponse">  <part name="String_2" type="xsd:string"/>  </message>
<portType name="SmallBookQuery">
  <!-- Some operation elements not shown -->
  <operation name="getBookAuthor" parameterOrder="String_1 String_2">
    <input message="tns:SmallBookQuery_getBookAuthor"/>
    <output message="tns:SmallBookQuery_getBookAuthorResponse"/>
    <fault name="SmallBookServiceException" 
           message="tns:SmallBookServiceException"/>
  </operation>
</portType> 

This WSDL extract shows that the input message for the getBookAuthor operation (called SmallBookQuery_getBookAuthor ) requires two string parameters, while the reply message (called SmallBookQuery_getBookAuthorResponse ) returns a single string value. Since the WSDL specifies two string parameters, the DII invoke( ) call for this operation has to look like this:

 call.invoke(new Object[] { title, null }); 

where null is supplied as the input value for the author parameter, since there is no meaningful value that could possibly be used here.

The fact that you have to supply a value for an argument that is not actually used by the service is unfortunate but is not something that you are likely to encounter in practice, since the WSDL definition for a real web service will almost certainly have been created (or adjusted) so that it does not require inputs for arguments that are output-only. In fact, if you were to edit the WSDL file generated by wsdeploy and change the specification of the SmallBookQuery_getBookAuthor message to the following:

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

then you could, indeed, pass only the single book title argument to the invoke( ) method.

As you saw earlier, the value returned by the invoke( ) method is the value returned by the RPC call itself, if any. So how are the values of any output parameters obtained (as opposed to the method return value)? There are two Call methods that can be used to get the output values:

 public List getOutputValues(  );
public Map getOutputParams(  ); 

The java.util.List returned by the getOutputValues( ) method contains a single entry for each input-output or output-only parameter, the order of which reflects the ordering in the Java method signature (or the ordering implied by the parameterOrder attribute of the operation element in the WSDL definition). For the getBookAuthor( ) method, the following code illustrates one way to get the returned author name:

 Call call = service.createCall(portName, new QName(SERVICE_URI, 
     "getBookAuthor"));
String title = "J2ME in a Nutshell";
call.invoke(new Object[] { title });  List results = call.getOutputValues(  );   String author = (String)result.get(0);  

Alternatively, the getOutputParams( ) method returns a java.util.Map in which the key for each entry is the parameter name, and the associated value is the value returned by the web service. Note, however, that to determine the correct parameter name, you need to inspect the WSDL definition rather than rely on the original Java interface definition (if there is one). In particular, the following code does not work with the JAX-RPC reference implementation:

 Map map = call.getOutputParams(  );
author = (String)map.get(new QName("author")); 

This is because the author parameter from the original method definition actually becomes String_2 in the WSDL definition. Instead, you need to use the following code:

 Map map = call.getOutputParams(  );
author = (String)map.get(new QName("String_2")); 

According to the JAX-RPC specification, the keys for the Map returned by the getOutputParams( ) method should be of type java.lang.String . Therefore, to get the value assigned to the parameter mapped to the name String_2 , the following code should be used:

 Map map = call.getOutputParams(  );
author = (String)map.get("String_2")); 

In the JAX-RPC reference implementation, however, the keys are of type QName rather than String ”hence, the use of QName objects in place of simple strings in the code shown previously. Given that the implementation does not match the specification, expect one or the other to change at some point. It is also likely that vendor implementations will follow the specification and require a string argument.

It might seem a little strange at first that, despite the fact that the getBookAuthor( ) method defines the author argument as type StringHolder , the value retrieved by getOutputValues( ) or getOutputParams( ) is actually a String . In fact, StringHolder is just a convenience class that appears in the Java definition of the web service and does not survive as far as the WSDL description, which instead describes the parameter as a string. When using the DII, you have to work with the actual parameter types that appear in the WSDL description of the service. The service implementation class, however, receives a StringHolder , and, if you used wscompile to generate client-side stubs for the SmallBookQuery interface, the stub for the getBookAuthor( ) method also expects the calling client application to supply a StringHolder object. At runtime, the stub gets the string value returned by the service and installs it in the StringHolder passed by the caller, from where it could be retrieved on completion of the method call. In general, when working with the DII, refer to the WSDL definition for the service at all times.

The example source code for this book contains a client application that uses the code shown in this section to invoke the methods of the SmallBookQuery interface by importing its WSDL definition. You can try it out by making chapter6\dynamicclients your working directory and typing the following commands:

 ant compile-client
ant run-first-client 

The output from this client shows the total number of books as well as the title and author of each book, obtained by using the DII to call the getBookTitle( ) and getBookAuthor( ) methods, respectively. The complete source code for this client application can be found in the file chapter6\dynamicclients\client\ora\jwsnut\chapter6\client\FirstDynamicClient.java .

Note that it has not been necessary to specify the URI of the web service before calling the invoke( ) method, because the selection of a port defined in a WSDL document automatically determines the URI. If you use a Call object that is not associated with a WSDL document (as described in the next section), or if you want to use a target address that is not the same as the one in the WSDL, you can set an explicit address using the setTargetEndpointAddress( ) method of the Call object.

6.3.2 Dynamic Invocation of a Service Not Defined by WSDL

Using the dynamic invocation interface for a service defined in a WSDL document is very convenient for the programmer, since the Call objects are automatically initialized with the number and runtime types of the input and output parameters and with the return value for each available operation. All you have to do is select the port and operation that you want to use, supply the input parameter values, and then invoke the operation. This convenience is obtained at the expense of the runtime cost of the time taken to read and parse the WSDL document when the Service object is obtained. If you don't want to incur this expense, or if you won't have access to the WSDL definition at runtime for any reason, you can programmatically configure the Call objects with the same information. This is, however, very complex, as you'll see.

As before, the first step is to obtain a Service object. In this case, since you don't have a WSDL document, you need to use the single argument createService( ) method and supply only the service name:

 QName serviceName = new QName(SERVICE_URI, "SmallBookService");
ServiceFactory factory = ServiceFactory.newInstance(  );
Service service = factory.createService(serviceName); 

Next, you get a Call object for the method that you want to invoke, such as getBookCount( ) :

 Call call = service.createCall(portName, new QName(SERVICE_URI, 
     "getBookCount")); 

This code is unchanged from the case in which we used a WSDL definition. One important difference, however, is that, in the absence of a WSDL definition, there is no way for the JAX-RPC runtime to check that the port and operation name that you supply are valid. Therefore, any errors made here are not detected until you attempt to invoke the method.

The next step is to configure the Call object with the information that would previously have been obtained from the WSDL document. The attributes that you most commonly need to set are listed in Table 6-2.

Table 6-2. Attributes of the Call object that should be set before invoking an operation

Property

Comment

Parameter definitions

Set using the addParameter( ) method

Return type

Set using the setReturnType( ) method

Service URI

Set using the setTargetEndpointAddress( ) method

Encoding style

Set using the setProperty( ) method

Operation style

Set using the setProperty( ) method

In the case of the getBookCount( ) method, there are no parameters and the return value is an integer. Here is how you would tailor the Call object to match this specification before invoking this method:

 call.setReturnType(XMLType.XSD_INT, java.lang.Integer.class);
call.setProperty(Call.ENCODINGSTYLE_URI_PROPERTY, 
     SOAPConstants.URI_NS_SOAP_ENCODING);
call.setTargetEndpointAddress(args[0]); 

The setReturnType( ) method requires that the data type of the return value be described both as an XML type and as a Java type. The correct Java type is obviously java.lang.Integer , but what about the XML type? If you didn't already know that the XML Schema type that represents an integer is xsd:int , the quickest way to find out the correct value to supply is to look at the WSDL definition for the getBookCount operation, the relevant parts of which are shown in Example 6-7.

Example 6-7. WSDL definitions for the getBookCount operation
 <message name="SmallBookQuery_getBookCountResponse">
  <part name="result" type="  xsd:int  "/>
</message>
<portType name="SmallBookQuery">
  <!-- Not all operations are shown here -->
  <operation name="getBookCount" parameterOrder="">
    <input message="tns:SmallBookQuery_getBookCount"/>
    <output message="tns:SmallBookQuery_getBookCountResponse"/>
  </operation>
</portType> 

The operation element specifies that the return value is defined in the SmallBookQuery_getBookCountResponse message, which defines the type of the result as xsd:int . Instead of hardcoding this constant value (as a QName ), we make use of the javax.xml.rpc.encoding.XMLType class, which defines constant values for many of the data types defined by the XML Schema specification. In this case, XMLType.XSD_INT is actually a QName constructed from the namespace URI for the XML Schema data types and the local name int . See the reference materials at the back of this book for a list of the other values defined by the XMLType class.

The next line uses the setProperty( ) method to set the encoding style to be used when creating the SOAP message for this operation. Unless you are going to use your own private encoding scheme (which also involves writing custom serializers and deserializers), you should always specify the use of the default SOAP encoding rules by setting the ENCODINGSTYLE_URI_PROPERTY property of the Call object. For more information on these properties, see Section 6.3.5, later in this chapter.

Another property of the Call object determines whether the call uses rpc or document semantics. In this case, there is no need to explicitly set the property because the default, rpc , is appropriate here. See Section 6.6, later in this chapter, for a discussion of these two different operation styles.

Finally, the setTargetEndpointAddress( ) method is called to set the URI of the web service to be called. In this case, we get the address from the command line, which amounts to the following:

 call.setTargetAddress("http://localhost:8000/SmallBooks/SmallBookQuery"); 

Once the Call object is configured, invoke the getBookCount( ) method and get the results in the same way as shown earlier:

 Object result = call.invoke(null);
int bookCount = ((Integer)result).intValue(  ); 

Invoking the getBookTitle( ) method involves an extra line of code because it requires an integer argument:

 Call bookTitleCall = service.createCall(portName, 
     new QName(SERVICE_URI, "getBookTitle"));  bookTitleCall.addParameter("int_1", XMLType.XSD_INT  ,  java.lang.Integer.class, ParameterMode.IN);  bookTitleCall.setReturnType(XMLType.XSD_STRING, java.lang.String.class);
bookTitleCall.setProperty(Call.ENCODINGSTYLE_URI_PROPERTY, 
                          SOAPConstants.URI_NS_SOAP_ENCODING);
bookTitleCall.setTargetEndpointAddress(args[0]); 

Specify the types of the method call arguments by calling addParameter( ) once for each of them. The first argument supplies the name of the parameter being configured, which must be obtained from the WSDL definition rather than the original Java interface definition. The WSDL definition for the getBookTitle operation is shown in Example 6-8, where you can see that the part that corresponds to the method argument is called "int_1".

Example 6-8. The WSDL definitions for the getBookTitle operation
 <message name="SmallBookQuery_getBookTitle">
  <part name="  int_1  " type="xsd:int"/>
</message>
<message name="SmallBookQuery_getBookTitleResponse">
  <part name="result" type="xsd:string"/>
</message>
<portType name="SmallBookQuery">
  <operation name="getBookTitle" parameterOrder="int_1">
    <input message="tns:SmallBookQuery_getBookTitle"/>
    <output message="tns:SmallBookQuery_getBookTitleResponse"/>
  </operation>
</portType> 

The next two arguments supply the XML and Java types of the parameter value, which are determined in the same way as they are for setReturnType( ) . In this case, since the argument is an integer, the appropriate values are XMLType.XSD_INT and java.lang.Integer.class , respectively. The final argument specifies whether the parameter is an input, output, or input-output value using constant values defined by the javax.xml.rpc.ParameterMode class. In this case, since the parameter is an input-only value, the appropriate value for this argument is ParameterMode.IN . The other properties are set in the same way as they were for the getBookCount operation, except that the return value in this case is a String rather than an integer.

Now that the Call object has been fully configured, you can use it in the usual way:

 result = bookTitleCall.invoke(new Object[] { new Integer(3) });
String title = (String)result; 

The addParameter( ) , setReturnType( ) , and removeAllParameters( ) methods (the latter of which is not described here) can only be used with a Call object created from a Service object that is not associated with a WSDL definition. If you attempt to use these methods in violation of this rule, a JAXRPCException is thrown. This is reasonable, since the argument and return types for methods defined in a WSDL file are already known and should not be changed.

You can programmatically determine whether a Call object will allow its parameters and return type to be set by calling the isParameterAndReturnSpecRequired( ) method. If this method returns true, you must use addParameter( ) and setReturnType( ) to configure the Call object. If it returns false, you must not call these methods.

Lastly, let's look at how to configure a Call object to invoke the getBookAuthor( ) method. Recall from the previous section that this method does not have a return value. Instead, it uses an output parameter to return the name of the author of the book whose title is supplied as its first argument. Here's how to create and tailor a Call object for this case (the important lines are highlighted):

 Call bookAuthorCall = service.createCall(portName, new QName(SERVICE_URI, 
                                         "getBookAuthor"));  bookAuthorCall.addParameter("String_1", XMLType.XSD_STRING  ,  java.lang.String.class, ParameterMode.IN);   bookAuthorCall.addParameter("String_2", XMLType.XSD_STRING  ,  java.lang.String.class, ParameterMode.OUT);   bookAuthorCall.setReturnType(null);  bookAuthorCall.setProperty(Call.ENCODINGSTYLE_URI_PROPERTY, 
                           SOAPConstants.URI_NS_SOAP_ENCODING);
bookAuthorCall.setTargetEndpointAddress(args[0]); 

Looking at the WSDL definition for this operation (in Example 6-6 earlier in this chapter), you can see that the actual names to be used for the arguments are String_1 and String_2 , respectively. We know that the first argument ( String_1 ) is an input-only value and therefore its parameter mode should be ParameterMode.IN . The second argument, however, does not require a valid value when the method is called ” instead, the value is supplied by the reply message. The appropriate mode for this argument is, therefore, ParameterMode.OUT .

The Java return type of this method is void ; therefore, the setReturnType( ) method is called with argument null to indicate this. The variant of setReturnType( ) used previously requires a QName that describes the XML return type, but does not require the corresponding Java data type, which is inferred based on the XML type. Hence, the following calls are equivalent:

 call.setReturnType(XMLType.XSD_INT);
call.setReturnType(XMLType.XSD_INT, java.lang.Integer.class); 

To invoke the getBookAuthor( ) method and extract the result, the following code is used:

 bookAuthorCall.invoke(new Object[] { title });
List list = bookAuthorCall.getOutputValues(  );
String author = (String)list.get(0); 

Note that, since the second parameter has been defined with mode ParameterMode.OUT , you should not supply a value for it in the argument list passed to the invoke( ) method. If it was defined with mode ParameterMode.INOUT , however, then a valid input value would have been required.

You can find the source code for the client application shown in this section in the file chapter6\dynamicclients\client\ora\jwsnut\chapter6\client\SecondDynamicClient.java . To run this example, which produces the same result as the earlier version that used a WSDL definition to configure its Call objects, make chapter6\dynamicclients your working directory and type the following commands:

 ant compile-client
ant run-second-client 

6.3.3 DII and Exceptions

Some of the methods in the SmallBookQuery interface are defined to throw service-specific exceptions to report error conditions to the caller. For example, the getBookAuthor( ) method throws a SmallBookServiceException when asked for the author of a book whose title it does not recognize:

 public abstract void getBookAuthor(String title, StringHolder author) 
      throws SmallBookServiceException, RemoteException; 

If you use wscompile to generate client-side stubs, and then use those stubs to call this method with an invalid book title, a SmallBookServiceException is thrown from the client's method call, provided that the exception class is defined in such a way that it obeys the rules for properly formed service-specific exceptions, as listed in Section 2.1.2.1.

The current version of the JAX-RPC specification does not require that this behavior be preserved for methods that are invoked using the dynamic invocation interface. In fact, in the reference implementation, when the invoke( ) method is called in circumstances that should result in a SmallBookServiceException , application code actually receives a java.rmi.ServerException , for which the associated message is the name of the service-specific exception thrown by the service implementation ” in this case, ora.jwsnut.chapter6.smallbookservice.SmallBookServiceException . This is not particularly helpful, and, hopefully, a future version of the JAX-RPC specification will require the original exception to be propagated to the caller of the invoke( ) method.

6.3.4 One-Way Calls

When you use the invoke( ) method, the JAX-RPC runtime builds a SOAP message, sends it to the server, and blocks until a response is received, thus giving the same effect as a method invocation using a precompiled client-side stub or a dynamic proxy. In some cases, however, it is useful to be able to call a method and not block until the server completes it. The JAX-RPC specification requires that all implementations support this nonblocking mode, which it describes as a one-way call (a sequence diagram for which can be found in Figure 2-6 in Chapter 2).

To perform a one-way call, obtain and configure (if necessary) a Call object in the usual way, and then use the invokeOneWay( ) method instead of invoke( ) . The client applications used in this section both make such a call to record logging information:

 Call logCall = service.createCall(portName, new QName(SERVICE_URI, "log"));
logCall.addParameter("String_1", XMLType.XSD_STRING, java.lang.String.class,
                     ParameterMode.IN);
logCall.setReturnType(null);
logCall.setProperty(Call.ENCODINGSTYLE_URI_PROPERTY, 
                    SOAPConstants.URI_NS_SOAP_ENCODING);
logCall.setTargetEndpointAddress(args[0]);  logCall.invokeOneWay(new Object[] { "Successful completion." });  

This code makes a one-way call to a method called log( ) that requires a single string argument and does not return anything. Logging is a good candidate for implementation as a nonblocking operation because the client cares only that something is logged, but doesn't want to wait until the log entry is written. One-way operations cannot return a value (for obvious reasons), and they also cannot throw exceptions to report errors encountered in the service implementation.

It is important to note that the extent to which the invokeOneWay( ) method lives up to its claim of being a nonblocking operation is implementation-dependent. Some implementations might choose to provide true nonblocking semantics by using a separate thread to make the method call, so that the invokeOneWay( ) method returns to the caller even before the SOAP message is sent. At the other extreme, invokeOneWay( ) might construct and send the message before it returns control ”therefore, it will probably be only slightly faster than using the invoke( ) method (or perhaps not any faster at all).

6.3.5 Call Object Properties

The Call object provides several standard properties that you can use to customize the way in which a method invocation is performed. The set of properties defined by the JAX-RPC specification is shown in Table 6-3. Note that some of these properties are the same as those defined for use with the Stub interface and listed in Table 2-6.

Table 6-3. Properties associated with the Call object

Property name

Description

Call.USERNAME_PROPERTY

Call.PASSWORD_PROPERTY

The username and password to be supplied to the server when basic authentication is in use. See Section 6.7.6 later in this chapter for a discussion of authentication in the context of JAX-RPC.

Stub.ENDPOINT_ADDRESS_PROPERTY

The URI of the target endpoint. This property is usually set using the convenience method setTargetEndpoint - Address( ) , and is set automatically when the Call object is associated with a WSDL definition.

Call.ENCODINGSTYLE_URI_PROPERTY

Specifies the encoding rules to be used when building SOAP messages. Under normal circumstances, you would use SOAP section 5 encoding, which can be selected by setting this property to the value SOAPConstants.URI_NS_SOAP_ENCODING . This property is set automatically when the Call object is associated with a WSDL definition.

Call.OPERATION_STYLE_PROPERTY

Specifies whether the operation is RPC- or document-style. For typical RPC operations, this attribute should be set to rpc , which is its default value. See Section 6.6 later in this chapter for more information on operation styles. Support for this property is optional.

Call.SESSION_MAINTAIN_PROPERTY

A java.lang.Boolean value that determnes whether the server should enter into an HTTP session with this client when HTTP is the underlying transport mechanism. This property is false by default. The use of sessions as a means of retaining context between method calls is discussed in Section 6.7, later in this chapter.

Call.SOAPACTION_URI_PROPERTY

The URI to be used for the SOAPAction header when making the call. This URI is used only when the SOAPACTION_USE_PROPERTY has the value true. Since JAX-RPC-hosted services do not use SOAPAction , this attribute need not be set when calling such a service. An appropriate value should be used when calling a non-JAX-RPC implementation. This property is set from the WSDL definition of an operation when the Call object is created from a Service that is associated with a WSDL document.

Call.SOAPACTION_USE_PROPERTY

A java.lang.Boolean value that indicates whether a SOAPAction header should be included. If this property has the value true, then the URI given by SOAPACTION_URI_PROPERTY is used if it is set, or "" (an empty string) is used if it is not set.

You can set the value of a property by calling the setProperty( ) method, supplying the property name as the first argument:

 public void setProperty(String name, Object value) throws JAXRPCException; 

An exception is thrown if the property name is not recognized, if it is an optional property that the implementation does not support, or if the supplied value is invalid. You can find out which properties a particular implementation supports by calling the getPropertyNames( ) method, which returns an Iterator in which each entry is a String representing the name of a supported property.

The value of a given property can be obtained using the getProperty( ) method:

 Boolean useSession = (Boolean)call.getProperty(Call.SESSION_MAINTAIN_PROPERTY); 

You can remove a property (thereby making its value undefined) using the removeProperty( ) method.