JAX-RS runtime allows applications to supply entity providers which maps services between representations (via @Produces and @Consumes) and their Java types. The entity provider interfaces MessageBodyReader and MessageBodyWriter defines the contract that supports the conversion of a stream to a Java type and vice versa.
Lets write a simple JAXRS provider for handling a SOAPMessage on the client-side.
import javax.ws.rs.Consumes; import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.ext.MessageBodyReader; import javax.ws.rs.ext.MessageBodyWriter; import javax.ws.rs.ext.Provider; import javax.xml.soap.*; import javax.xml.transform.stream.StreamSource; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Type; @Provider @Consumes(MediaType.TEXT_XML) @Produces(MediaType.TEXT_XML) public class SoapProvider implements MessageBodyWriter, MessageBodyReader { public boolean isWriteable(Class aClass, Type type, Annotation[] annotations, MediaType mediaType) { return SOAPMessage.class.isAssignableFrom(aClass); } public SOAPMessage readFrom(Class soapEnvelopeClass, Type type, Annotation[] annotations, MediaType mediaType, MultivaluedMap stringStringMultivaluedMap, InputStream inputStream) throws IOException, WebApplicationException { try { MessageFactory messageFactory = MessageFactory.newInstance(); StreamSource messageSource = new StreamSource(inputStream); SOAPMessage message = messageFactory.createMessage(); SOAPPart soapPart = message.getSOAPPart(); soapPart.setContent(messageSource); return message; } catch (SOAPException e) { e.printStackTrace(); } return null; } public long getSize(SOAPMessage soapMessage, Class aClass, Type type, Annotation[] annotations, MediaType mediaType) { return -1; } public void writeTo(SOAPMessage soapMessage, Class aClass, Type type, Annotation[] annotations, MediaType mediaType, MultivaluedMap stringObjectMultivaluedMap, OutputStream outputStream) throws IOException, WebApplicationException { try { soapMessage.writeTo(outputStream); } catch (SOAPException e) { e.printStackTrace(); } } public boolean isReadable(Class aClass, Type type, Annotation[] annotations, MediaType mediaType) { return aClass.isAssignableFrom(SOAPMessage.class); } }
I used Jersey client to invoke the Weather Web Service from CDYNE. You can use any wsdl2java tool (I used CXF) to generate the types from the WSDL. Jersey would use the registered provider to map the SOAPMessage to the request and response. And finally, we bind the SOAPBody to the WSDL type using JAXB.
import com.cdyne.ws.weatherws.GetCityWeatherByZIPResponse; import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.client.config.ClientConfig; import com.sun.jersey.api.client.config.DefaultClientConfig; import com.sun.jersey.api.client.filter.LoggingFilter; import javax.xml.bind.JAXBContext; import javax.xml.bind.Unmarshaller; import javax.xml.soap.*; public class RESTyClient { public static void main(String[] args) throws Exception { ClientConfig config = new DefaultClientConfig(); config.getClasses().add(SoapProvider.class); Client c = Client.create(config); c.addFilter(new LoggingFilter()); MessageFactory messageFactory = MessageFactory.newInstance(); SOAPMessage message = messageFactory.createMessage(); SOAPPart soapPart = message.getSOAPPart(); SOAPEnvelope envelope = soapPart.getEnvelope(); SOAPBody body = envelope.getBody(); SOAPElement bodyElement = body.addChildElement(envelope.createName("GetCityWeatherByZIP", "", "http://ws.cdyne.com/WeatherWS/")); bodyElement.addChildElement("ZIP").addTextNode("59102"); message.saveChanges(); WebResource service = c.resource("http://ws.cdyne.com/WeatherWS/Weather.asmx"); // POST the request ClientResponse cr = service.header("SOAPAction", ""http://ws.cdyne.com/WeatherWS/GetCityWeatherByZIP"").post(ClientResponse.class, message); message = cr.getEntity(SOAPMessage.class); JAXBContext ctx = JAXBContext.newInstance(GetCityWeatherByZIPResponse.class); Unmarshaller um = ctx.createUnmarshaller(); GetCityWeatherByZIPResponse response = (um.unmarshal(message.getSOAPPart().getEnvelope().getBody().extractContentAsDocument(), GetCityWeatherByZIPResponse.class)).getValue(); System.out.println("City : " + response.getGetCityWeatherByZIPResult().getCity()); System.out.println("Temperature : " + response.getGetCityWeatherByZIPResult().getTemperature()); } }
Here is a sample output from the test.
Dec 3, 2009 10:02:04 PM com.sun.jersey.api.client.filter.LoggingFilter log INFO: 1 * Client out-bound request 1 > POST http://ws.cdyne.com/WeatherWS/Weather.asmx 1 > SOAPAction: "http://ws.cdyne.com/WeatherWS/GetCityWeatherByZIP" 1 > 59102 Dec 3, 2009 10:02:04 PM com.sun.jersey.api.client.filter.LoggingFilter log INFO: 1 * Client in-bound response 1 < 200 1 < Content-Length: 762 1 < X-AspNet-Version: 2.0.50727 1 < X-Powered-By: ASP.NET 1 < Cache-Control: no-cache 1 < Pragma: no-cache 1 < Content-Type: text/xml; charset=utf-8 1 < Server: Microsoft-IIS/7.0 1 < Date: Fri, 04 Dec 2009 05:02:21 GMT 1 < Expires: -1 1 < true City Found MT Billings Billings 3 Mostly Cloudy 16 51 SW23 30.11F City : Billings Temperature : 16
In fact, this should work across the same way with any other JAX-RS Clients (I know there a quite a few). I have tried with the new kid on the block Apache Wink, which looks very interesting, still in incubation, but I am impressed with their documentation, as I hardly find useful docs with other projects in incubation stage. And the client APIs are similar to that of Jersey client, so it was fairly easy to write one using Wink Client. I will have to reserve another blog entry for exploring Wink’s USP.
import com.cdyne.ws.weatherws.GetCityWeatherByZIPResponse; import org.apache.wink.client.ClientConfig; import org.apache.wink.client.ClientResponse; import org.apache.wink.client.Resource; import org.apache.wink.client.RestClient; import javax.ws.rs.core.Application; import javax.xml.bind.JAXBContext; import javax.xml.bind.Unmarshaller; import javax.xml.soap.*; import java.util.HashSet; import java.util.Set; public class WinkClient { public static void main(String[] args) throws Exception { MessageFactory messageFactory = MessageFactory.newInstance(); SOAPMessage message = messageFactory.createMessage(); SOAPPart soapPart = message.getSOAPPart(); SOAPEnvelope envelope = soapPart.getEnvelope(); SOAPBody body = envelope.getBody(); SOAPElement bodyElement = body.addChildElement(envelope.createName("GetCityWeatherByZIP", "", "http://ws.cdyne.com/WeatherWS/")); bodyElement.addChildElement("ZIP").addTextNode("59102"); message.saveChanges(); ClientConfig config = new ClientConfig(); // Create new JAX-RS Application Application app = new Application() { @Override public Set<Class> getClasses() { HashSet<Class> set = new HashSet<Class>(); set.add(SoapProvider.class); return set; } }; config.applications(app); RestClient client = new RestClient(config); // create the resource instance to interact with Resource resource = client.resource("http://ws.cdyne.com/WeatherWS/Weather.asmx"); // issue the request ClientResponse cr = resource.contentType("text/xml").post(ClientResponse.class, message); message = cr.getEntity(SOAPMessage.class); JAXBContext ctx = JAXBContext.newInstance(GetCityWeatherByZIPResponse.class); Unmarshaller um = ctx.createUnmarshaller(); GetCityWeatherByZIPResponse response = (um.unmarshal(message.getSOAPPart().getEnvelope().getBody().extractContentAsDocument(), GetCityWeatherByZIPResponse.class)).getValue(); System.out.println("Response : " + response.getGetCityWeatherByZIPResult().getCity()); System.out.println("Response : " + response.getGetCityWeatherByZIPResult().getTemperature()); } }
Here are the maven dependencies:
com.sun.jersey jersey-client 1.1.4.1 org.apache.wink wink-client 1.0-incubating
While there are better tools and frameworks which addresses the complexity of building SOAP messages, it was not a big deal to invoke web services from HTTP Clients, after all its just a HTTP POST.