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<SOAPMessage>, MessageBodyReader<SOAPMessage> {
    public boolean isWriteable(Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) {
        return SOAPMessage.class.isAssignableFrom(aClass);
    }

    public SOAPMessage readFrom(Class<SOAPMessage> soapEnvelopeClass, Type type, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> 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<String, Object> 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 > 
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Header/>
    <SOAP-ENV:Body>
      <GetCityWeatherByZIP xmlns="http://ws.cdyne.com/WeatherWS/">
        <ZIP>59102</ZIP>
      </GetCityWeatherByZIP>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

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 < 
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <soap:Body>
        <GetCityWeatherByZIPResponse xmlns="http://ws.cdyne.com/WeatherWS/">
            <GetCityWeatherByZIPResult>
                <Success>true</Success>
                <ResponseText>City Found</ResponseText>
                <State>MT</State>
                <City>Billings</City>
                <WeatherStationCity>Billings</WeatherStationCity>
                <WeatherID>3</WeatherID>
                <Description>Mostly Cloudy</Description>
                <Temperature>16</Temperature>
                <RelativeHumidity>51</RelativeHumidity>
                <Wind>SW23</Wind>
                <Pressure>30.11F</Pressure>
                <Visibility />
                <WindChill />
                <Remarks />
            </GetCityWeatherByZIPResult>
        </GetCityWeatherByZIPResponse>
    </soap:Body>
</soap:Envelope>

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:

    <dependencies>
        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-client</artifactId>
            <version>1.1.4.1</version>
        </dependency>
        
        <dependency>
            <groupId>org.apache.wink</groupId>
            <artifactId>wink-client</artifactId>
            <version>1.0-incubating</version>
        </dependency>        
    </dependencies>

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.

Possibly Related Posts:


Comments are closed.