Client-side aspects of JAX-RPC Web Services

This article was originally published in VSJ, which is now part of Developer Fusion.
The web service that you are planning to consume will not necessarily have been written using Java tools – you could easily be consuming a web service written in .NET, but it must be an RPC-style web service, as this is what the JAX-RPC toolkit deals with. As in the previous article, you should either download the latest Java Web Services Developer Pack from Sun, or the Sun J2EE 1.4 Reference Implementation beta. Some application servers support JAX-RPC, as well as their native toolkits, and if you have one of these application servers you shouln't need to download anything.

The Service class

At the heart of the client side JAX-RPC classes lies the javax.xml.rpc.Service class, as shown below, which acts as a client side representation of the web service:

The javax.xml.rpc.Service class
The javax.xml.rpc.Service class

To allow for toolkit implementations of this Service class, you will never instantiate an instance of this class but rely on a ServiceFactory class to create the Service object. The Service class is effectively a factory for the Call object, which in turn encapsulates the mechanisms to call a Service Endpoint (a web service implementation).

Using Proxies

Remember that a web service exposes its functionality to clients in a WSDL (Web Services Description Language) document, which essentially reveals the XML schema for the web service and shows which messages are mapped to which ports. This WSDL is typically generated from language interface files – for instance, you may have a web service implementing the Java interface CalculatorIF. If, at the client end, you have the CalculatorIF interface, you can directly create a javax.xml.rpc.Stub object which would act as a client side proxy for the Calculator web service. In JAX-RPC terms, this is called static invocation. The tool provided to create these proxies in the standard JAX-RPC toolkit is called wscompile, and you need to provide a configuration file to inform the wscompile tool of which interfaces to use to create the Stub object, and in which namespace the web service resides. You can call this configuration any name, but by convention you use config.xml:
<?xml version="1.0" encoding="UTF-8"?>
<configuration xmlns=
"http://java.sun.com/xml/ns/
jax-rpc/ri/config">
	<service
	name="MyCalculatorService"
	targetNamespace="urn:qatraining"
	typeNamespace="urn: qatraining "
	packageName="calculator">
	<interface
	name="calculator.CalculatorIF"/>
	</service>
</configuration>
You will notice that this file is very similar to the file you used in the previous article to create the service endpoint. This is because wscompile generates either client or server side bindings depending on the switches you supply. To create the client side proxies you need to run wscompile –gen:client config.xml.

This will create a number of files as follows:

  • The Stub class, which will have the name CalculatorIF_Stub, representing the web service proxy and implementing the CalculatorIF interface.
  • The Service implementation class MyCalculatorService_Impl, which has a single method, getCalculatorIFPort, which returns an instance of the Stub proxy class.
  • The process will also create SOAP serialisers and deserialisers for every method in the interface. Using this generated proxy is fairly straightforward: you create a new Service instance, call the getCalculatorIFPort method to get a stub object, and then set the url service endpoint address before finally calling a method on the web service.
import javax.xml.rpc.Stub;
public class CalculatorClient {
	public static void main(String[]
	args) {
		try {
			Stub stub = (Stub)(new
			MyCalculatorService_Impl
			().getCalculatorIFPort());
			stub._setProperty(
			javax.xml.rpc.Stub.
			ENDPOINT_ADDRESS_PROPERTY,
			http://www.qa.com/services/
			calculator);
			CalculatorIF calculator =
			(CalculatorIF)stub;
			System.out.println(
			calculator.add(2,3);
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}
}

Dynamic Proxy Generation

When you are moving into the collaborative area of web services, and you are using a partner's web service to perhaps perform some B2B function, then it is unlikely you will have the language-specific interfaces used to create the web service. In this case you will use the WSDL document to create the interfaces and necessary stubs at the client end. This process is calleddynamic proxy generation. As with static proxy creation, you need to use the wscompile tool to create the client side bindings. You will however now provide the location of the wsdl document in the configuration xml file as shown below.
<?xml version="1.0" encoding="UTF-8"?>
<configuration xmlns=
"http://java.sun.com/xml/ns/
jax-rpc/ri/config">
	<wsdl location=
	"../appclient/
	MyCalculatorService.wsdl"
	packageName="calculator"/>
</configuration>
After running the wscompile tool you should have exactly the same files as before. The only difference now is that the getter methods in the Service implementation have been created from the WSDL document, and they use JavaBean naming patterns. If the WSDL contained a CalculatorPort binding, then the method that returned the Stub object would be getCalculatorPort(). The client is now created in exactly the same way.

Dynamic Invocation Interface

Perhaps the most flexible way to call a web service is to use Dynamic Invocation Interface (DII). With DII you can discover a web service dynamically, possibly by looking it up in a UDDI registry. This would return a set of ServiceBindings, as shown below:

ServiceBindings
ServiceBindings

…which you could then iterate around until the web service of interest was found as shown in this listing.

Collection cc = org.getServices();
if (cc != null) {
	for (Iterator iterator =
	cc.iterator();
	iterator.hasNext();) {
		Service service =
		(Service) iterator.next();
		if (service == null) continue;
		Collection cb =
		service.getServiceBindings();
		if (cb == null) continue;
		for (Iterator ito =
		cb.iterator();
		ito.hasNext();) {
			ServiceBinding
			serviceBinding =
			(ServiceBinding) ito.next();
			url =
		serviceBinding.getAccessURI();
		}
	}
}
You cannot use a proxy now to access the web service (unless you stop your process, create a dynamic proxy, and then restart it), so you will create a Service object to represent the WSDL document. A web service can wrap many Ports (operations), therefore you will have to write an XML parser that retrieves this information. The first step is to create a Service corresponding to the Service fully qualified name which we extract from the WSDL parameter definitions/service/@name. Note that we must use fully qualified namespaces to create this Service object.

The Call object is then populated with the correct properties in order for it to be used to invoke the service endpoint. In the example below, I am simply setting the values to known correct values. In real life you would retrieve these properties from the WSDL.

Service service =
factory.createService(
new QName("http://www.qa.com",
"MyCalculator");
Call call = service.createCall();
call.setTargetEndpointAddress(url);
call.setPortTypeName(
new QName("http://www.qa.com","
CalculatorIF"));
call.setProperty(
Call.SOAPACTION_USE_PROPERTY,
new Boolean(true));
call.setProperty(
Call.SOAPACTION_URI_PROPERTY, "");
call.setProperty(
Call.ENCODINGSTYLE_URI_PROPERTY,
"http://schemas.xmlsoap.org/soap/
encoding/");
This Call object is now used to create the underlying SOAP message to send to the web service. The operation names, parameters and return type must be exactly the same as specified in the WSDL document.

The arguments to the invoke method are passed as an array of Objects, and the result will be serialised by the JAX-RPC framework into the correct Java type as long as the setReturnType can find a mapping between the XML type and the Java type. For most simple types, and any serialisable type, the default mappings should suffice.

call.setOperationName(new
QName("http://www.qa.com","add"));
call.addParameter( "double_1",
new QName(
"http://www.w3.org/2001/XMLSchema",
"double"), ParameterMode.IN );
call.addParameter( "double_2",
New QName(
"http://www.w3.org/
2001/XMLSchema",
"double"),
ParameterMode.IN);
call.setReturnType(
new QName(
"http://www.w3.org/
2001/XMLSchema",
"double"));
Double result =
(Double)call.invoke(
new Object[]
{new Double (3.2),
new Double (3.4)});
Notice the ParameterMode specified when adding parameters to the Call object. Those familiar with CORBA (and SQL) will be comfortable with parameter modes. As shown below, parameters are classed as input only, output only or input-output:

Parameter passing
Parameter passing

This is somewhat more flexible than the pass by reference and pass by value semantics that we employ in Java remote calls, but does cause us some problems as Java does not fully support these modes of operation. Fortunately the Call interface has two methods which can be used to assist in retrieving values from OUT or INOUT parameters. The first method is getOutputParams, which returns a Map of output Parameters, where each entry in the Map contains the QName of the parameter as the key, and the value contains the returned values, as shown below.

call.addParameter( "double_1",
new QName(
"http://www.w3.org/2001/XMLSchema",
"double"), ParameterMode.INOUT );
call.setOperationName(
new QName("http://ww.qa.com","add"));
call.invoke(new Object[] {
	new Double (100)});
Map map = call.getOutputParams();
Set set = map.entrySet();
for (Iterator i = set.iterator();
i.hasNext(); ) {
	Map.Entry entry =
		(Map.Entry)i.next();
	System.out.println(entry.getKey() +
	" : " +entry.getValue());
}
Alternatively, the method getOutputValues returns a List with the values only.

As an aside, when using proxies to access web services, some mechanism must be found to deal with OUT parameters. Those familiar with CORBA will already have used Holders – these are simple wrapper classes which contain a value which can be set by the JAX-RPC runtime and belong to the package javax.xml.rpc.holders. The Holder object is created at the client end, and the value is populated by the runtime. The value can then be retrieved by the client. Note that this is actually INOUT behaviour, Java cannot really model OUT behaviour.

Holders are provided for all the primitive types and their wrappers, but you will have to create your own holders for any complex types you create:

DoubleHolder celsius =
	new DoubleHolder(100.0);
Stub stub = createProxy();
ConverterIF converter =
	(ConverterIF)stub;
System.out.println("Celsius : "
	+ celsius.value);
converter.cToF(celsius);
System.out.println("Fahrenheit : "
	+ celsius.value);

Dealing with attachments

The SOAP specification allows for attachments to be used to pass arguments or return types, a typical use may be to pass an image as an argument. The SOAP Attachment API for Java (SAAJ) has been created to deal with document style attachments, but obviously JAX-RPC must be able to deal with attachments that are passed as part of an RPC invocation.

Attachments in SOAP are normally bundled together and passed as a multipart/related type. Readers familiar with JavaMail will know that the traditional way of dealing with attachments is to use the JavaBeans Activation Framework (JAF) to read the input stream, and to invoke the correct javax.activation.DataHandler type by instantiating the correct handler using the constructor DataHandler(Object obj, String mime_type).

You could use this technique using JAX-RPC, but some standard mappings exist to make the task a little easier. The standard attachments supported are:

  • image/gif – java.awt.Image
  • image/jpeg – java.awt.Image
  • text/plain – java.lang.String
  • text/xml or application/xml – javax.xml.transform.Source

Getting hold of the Headers

More and more information is going to be stored in optional SOAP headers. For example, a web service could expect an authentication header. As shown in in the "Parameter passing" figure above, JAX-RPC uses the notion of HandlerChains, where each handler in the chain is responsible for dealing with one header. Any handler in the chain may terminate the chain processing at any time. Handlers have the following methods which you must implement:
  • init()
  • destroy()
  • handleRequest()
  • handleResponse()
  • getHeaders()
  • handleFault()
The JAX-RPC framework provides an abstract base Handler called GenericHandler which you can override. As indicated,

Handlers are chained, and the request is simply passed down each Handler to the handleRequest methods. To continue processing you return true, and to terminate the chain you return false. A sample Handler is shown below.

public class MyHandler extends
GenericHandler {
	public Boolean handleRequest(
	MessageContext ctx) {
		try {
			SOAPMessage msg =
			((SOAPMessageContext)ctx).
	getMessage();
			SOAPPart sp =
				msg.getSOAPPart();
			SOAPEnvelop se =
				sp.getEnvelop();
			SOAPHeader header =
				se.getHeader();
			// Now you can process
			// the header
			if (everything fine)
				return true;
				// continue processing
			else {
				return false;
				// stop processing chain
			}
		} catch(Exception ex) {
			// if you throw a
			// RuntimeException
			// here then a SOAPFault
			// will be generated
		}
	}
}
On the client side you deal with the Request HandlerChain, send the request to the web service then deal with the ResponseHandler chain when the response is returned.

To set a handler chain you interact with the Service object, get hold of the HandlerRegistry and add any handlers you have created to the chain, as shown below.

Service service =
	factory.createService(
new QName("http://www.qa.com",
	"MyCalculator");
HandlerRegistry registry =
	service.getHandlerRegistry();
QName servicePort = new QName(
"http://www.qa.com","CalculatorPort");
List handlerChain =
	hr.getHandlerChain(servicePort);
HandlerInfo handlerInfo =
	new HandlerInfo();
handlerInfo.setHandlerClass(
	MyHandler.class);
handlerChain.add(handlerInfo);

Conclusion

The JAX-RPC framework has been approved as part of the Java Community Process, and has recently been released as a maintenance draft supporting the WS-I Basic Profile. The Web Services specification for J2EE containers has also been approved, which means that J2EE application server vendors will have to provide JAX-RPC support in their products. Vendor toolkits will still exist to simplify the job of proxy generation, but the underlying semantics used will be that of JAX-RPC, so knowing the API will give you a "heads up" when using vendor toolkits.


James Winters is a Java specialist responsible for training delivery, course development and product consulting on Java technology at QA. Having published numerous articles, he is currently writing a textbook on Distributed Systems.

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“The trouble with programmers is that you can never tell what a programmer is doing until it's too late.” - Seymour Cray