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
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
…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
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()
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.
Comments