Java Web Service Client

There are times that this is fine usually when you need to support only one web service and it is static. The downside is that a web service is just a protocol for communication, and we have now coupled our code to this specific web service and its interface.

First level of abstraction: spring templates

Spring has a template class for multiple services. The class for this is: WebServiceTemplate. This class supports defaultUri if all your calls are to the same web service.

The simplest function of this class is:

boolean sendSourceAndReceiveToResult(String uri, Source requestPayload, WebServiceMessageCallback requestCallback, final Result responseResult);

 

Through this function you can send to any web service. You define the url and the xml payload for the service. Spring will add all the soap headers needed (except for the soap action – we will see this later on).

 

An example:

 

String url = "http://www.w3schools.com/webservices/tempconvert.asmx";

String xml =” <CelsiusToFahrenheit xmlns="http://tempuri.org/"> <Celsius>22</Celsius>

</CelsiusToFahrenheit>”;

 

StreamSource webServiceInput = new StreamSource(new StringReader(xmlDoc));

StringWriter finalResponseWriter = new StringWriter();

 

StreamResult webServiceOutput = new StreamResult(finalResponseWriter);

webServiceTemplate.sendSourceAndReceiveToResult(url, webServiceInput,

                           webServiceOutput);

 

If succeeded the result of the soap request is stored in the finalResponseWriter object.

 

The soap action needs to be added to the soap request, and cannot be automatically added. To add support for this there is the callback method. For example:

 

new WebServiceMessageCallback() {

public void doWithMessage(WebServiceMessage message) {

// Please see the WSDL for more details.

 ((SoapMessage) message).setSoapAction(“http://tempuri.org/CelsiusToFahrenheit”);

}

 

A more generic approach can be added by the following function:

public boolean sendSourceAndReceiveToResult(String uri,

              Source requestPayload, final Result responseResult,

              final String soapAction) {

 

       return super.sendSourceAndReceiveToResult(uri, requestPayload,

              new WebServiceMessageCallback() {

                     public void doWithMessage(WebServiceMessage message) {

                           // Please see the WSDL for more details.

                           ((SoapMessage) message).setSoapAction(soapAction);

                     }

              }, responseResult);

}

 

 

The spring template will allow the beginning of decoupling. We do not create specific classes for the web service, and if the provider is changed the we can paramitize the web service properties to an ini file and the xml request to another class to generate per web service interface.

 

Second level of abstraction: spring integration

With the soap template we still need write an abstraction level to create the xml requests per web service client.  In addition if the web service needs to be changed with another protocol the code now needs to be redone. To get full decoupling model we will use spring integration. To fully understand spring integration see http://www.springsource.org/spring-integration.

 

To make the code as nice as possible as in the example above we want to create a class CelsiusToFahrenheit and have the option to send a request to a service to get the result. What needs to be done is to serialize the class to the specific service protocol. This abstraction can be done with spring integration.

First we will look at the code that sends the class to get the Celsius to Fahrenheit conversion.

 

CelsiusToFahrenheit cToF = new CelsiusToFahrenheit(22);

                          

MessagingTemplate messagingTemplate = new MessagingTemplate(celsiusToFahrenheit);

              Message<?> response = messagingTemplate.sendAndReceive(new GenericMessage<CelsiusToFahrenheit>(cToF));

CelsiusToFahrenheitResponse celsiusToFahrenheitResponse = (CelsiusToFahrenheitResponse)response.getPayload();

 

To accomplish this we need to convert the CelsiusToFahrenheit class to xml using jaxb. We then send the xml on the web service. The result of the web service is then unmarshalled from xml to the class CelsiusToFahrenheitResponse.

 

The spring integration for this is:

 

<bean id="c2fMarshallingTransformer"

       class="org.springframework.integration.xml.transformer.MarshallingTransformer">

       <constructor-arg>

              <bean class="org.springframework.oxm.jaxb.Jaxb2Marshaller">

                     <property name="classesToBeBound">

                           <array>

                                  <value> CelsiusToFahrenheit

                                  </value>

                           </array>

                     </property>

              </bean>

       </constructor-arg>

       <constructor-arg>

              <bean

                     class="org.springframework.integration.xml.transformer.ResultToDocumentTransformer" />

       </constructor-arg>

</bean>

 

<bean id="c2fUnmarshallingTransformer"

       class="org.springframework.integration.xml.transformer.UnmarshallingTransformer">

       <constructor-arg>

              <bean class="org.springframework.oxm.jaxb.Jaxb2Marshaller">

                     <property name="classesToBeBound">

                           <array>

                                  <value>CelsiusToFahrenheitResponse

                                  </value>

                           </array>

                     </property>

              </bean>

       </constructor-arg>

</bean>

 

 

<int:channel id="celsiusToFahrenheit" />

<int:chain input-channel="celsiusToFahrenheit">

       <int:transformer ref="c2fMarshallingTransformer" />

       <ws:header-enricher>

              <ws:soap-action value="http://tempuri.org/CelsiusToFahrenheit" />

       </ws:header-enricher>

       <ws:outbound-gateway

              uri="http://www.w3schools.com/webservices/tempconvert.asmx">

       </ws:outbound-gateway>

       <int:transformer ref="c2fUnmarshallingTransformer" />

</int:chain>

 

 

Other issues with Soap Requests

Whether using spring integration or spring template there are issues with web services that need to be overcome.

SSL – with or without credentials

The first is to allow sending web services over ssl and might or might not include using user credentials. To do this spring has two implementations for the sending of the web service. One is HttpComponentsMessageSender. This implementation uses HttpClient as the underlying connection. The HttpClient has many features that we will use.

By using the url of https, the HttpComponentsMessageSender knows to send all requests via ssl. To use user credentials the following needs to be configured on the message sender object:

 

 

<bean id="messageSender"

       class="org.springframework.ws.transport.http.HttpComponentsMessageSender">

       <property name="connectionTimeout" value="3000" />

       <property name="readTimeout" value="5000" />

       <property name="credentials">

              <bean class="org.apache.http.auth.UsernamePasswordCredentials">

                     <constructor-arg value="username:password" />

              </bean>

       </property>

</bean>

 

 

Timeout

To add timeout parameters we also use the HttpClient as above and will configure:

<bean id="messageSender"

       class="org.springframework.ws.transport.http.HttpComponentsMessageSender">

       <property name="connectionTimeout" value="3000" />

       <property name="readTimeout" value="5000" />

</bean>

 

 

Retries

We will again use the built in options of the HttpClient.

HttpClient support retry by using the following interface:

 

public interface HttpRequestRetryHandler {

    boolean retryRequest(IOException exception, int executionCount, HttpContext context);

}

 

You can implement this in your own way if needed. There is a default retry handler: DefaultHttpRequestRetryHandler.

The default handler will retry 3 times if not succeeded. The default handler knows to distinguish between a failed connection (and will retry) to other failures that retry will not help (like UnknownHostException, ConnectException, SSLException, InterruptedIOException). In the case that the exception is not in the list above the default handler calls the function handleAsIdempotent to determine if it is a read only request.

In the case of web services all http requests are post, and the protocol does not notify if the request is read only or not. So we will add a new class SoapHttpRequestRetryHandler that will override the handleAsIdempotent and allow to configure in the xml file the read only soap actions.

 

public class SoapHttpRequestRetryHandler extends DefaultHttpRequestRetryHandler {

 

       private List<String> readOnlySoapActions = new LinkedList<String>();

      

       public SoapHttpRequestRetryHandler(int retryCount, boolean requestSentRetryEnabled, List<String> readOnlySoapActions){

              super(retryCount, requestSentRetryEnabled);

              this.readOnlySoapActions = readOnlySoapActions;

       }

 

      

       private String getSoapAction(HttpRequest request){

              return request.getFirstHeader("SOAPAction").getValue();

       }

      

       @Override

       protected boolean handleAsIdempotent(HttpRequest request) {

              if (readOnlySoapActions.indexOf(getSoapAction(request))!=-1)

                     return true;

              return super.handleAsIdempotent(request);

       }

      

}

 

Example xml:

<bean id="trustedMessageSender"

       class="TrustSSLHttpComponentsMessageSender">

       <property name="readOnlySoapActions">

              <value>"http://tempuri.org/CelsiusToFahrenheit"</value>

       </property>         

</bean>

 

Untrusted SSL Clients

 

In the case of a trusted site, spring soap template knows to use the sites certificate to encrypt all data. If the site is not trusted, then spring will raise an exception:

SunCertPathBuilderException: unable to find valid certification path to requested target.

 

In the case you do not want this, you need to register a new class SSLSocketFactory with the httpclient, and add the method:

 

public boolean isTrusted(final X509Certificate[] chain, String authType) throws CertificateException {

return true;

}

 

For example:

 

private static DefaultHttpClient trustEveryoneSslHttpClient() {

    try {

        SchemeRegistry registry = new SchemeRegistry();

 

        SSLSocketFactory socketFactory = new SSLSocketFactory(new TrustStrategy() {

 

            public boolean isTrusted(final X509Certificate[] chain, String authType) throws CertificateException {

                // Oh, I am easy...

                return true;

            }

 

        }, org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

 

        registry.register(new Scheme("https", 443, socketFactory));

        registry.register(new Scheme("http", 80, new PlainSocketFactory()));

       

        PoolingClientConnectionManager mgr = new PoolingClientConnectionManager(registry);

        DefaultHttpClient client = new DefaultHttpClient(mgr, new DefaultHttpClient().getParams());

        return client;

    } catch (GeneralSecurityException e) {

        throw new RuntimeException(e);

    }

}     

 

 

 

Backend/Data Architect

Backend Group
Thank you for your interest!

We will contact you as soon as possible.

Send us a message

Oops, something went wrong
Please try again or contact us by email at info@tikalk.com