Tuesday, August 28, 2007

JAX-WS Dynamic Dispatch with CXF

Calling a web service is most often done by using some ws implementation stack like Axis2 or CXF/XFire to generate stubs for your language. The stub code is then linked against, to do the actual calls to the web service, marshall and unmarshall data etc.

This posts shows you how to do this completely at runtime, by using the CXF API directly.

If you are willing to bind yourself directly to the CXF API (as opposed to using the standard JAX-WS apis), you can do some nice things. It can do the steps of generating stubs, but completely at runtime. Look at this code:

DynamicClientFactory dcf = DynamicClientFactory.newInstance();
Client client = dcf.createClient("http://host/invoicing.wsdl", DynamicClient.class.getClassLoader());

Object customerParam = Thread.currentThread().getContextClassLoader().loadClass("com.acme.invoicing.Customer").newInstance();

Method setCustIdMethod = customerParam.getClass().getMethod("setCustomerId", String.class);
setCustIdMethod.invoke(customerParam, "CUST-42");

Object[] result = client.invoke("doInvoicingOnCustomer", customerParam);

Here is a bit of explanation of what happens here:
  • Calling createClient on a DynamicClientFactory instance makes CXF load the WSDL, generate stubs and compile them to class files. They are now available on the current threads context classloader.
  • In this example, the invoicing.wsdl service is supposed to have a method doInvoicingOnCustomer(Customer). The wsdl will have Customer defined, which will make CXF generate a Customer class.
  • You can then call loadClass method to load the Customer class and do a newInstance
  • To call customer.setCustomerId(String), we reflect the method out and invoke the setter with the value "CUST-42".
  • And lastly, we call the doInvoicingOnCustomer remote webservice method, giving the Customer instance as parameter
And of course. The Object[] output is typed in generated classes too. Ready to be reflected upon.

The above code binds directly to the CXF API. There is also the possibility of using the standard JAX-WS API for this. There are good examples on using the standard JAX-WS API to do dynamic calls:
But I do not like it. The code in the above two links use the rather low-level API of javax.xml.soap package to construct the call to the server. It seems like you have to now a lot about the needed SOAP message format, QNames to use, etc.

5 comments:

arsenalist said...

Anyone trying to use this piece of code should realize that the Client instance only needs to be created once at application startup.

I prefer the XFire/CXF way rather than the core APIs. Much nicer.

peztctrl said...

...
loadClass("com.acme.invoicing.Customer")
...
This seems to indicate that you have to know to which package the class is generated.
How can you know that before it's generated?

Tech Per said...

By looking into the wsdl.

Edgardo said...

java.lang.IllegalStateException: Unable to create JAXBContext for generated packages

how to fix?

Per Olesen said...

@Edgardo don't know - need more info