Enterprise Java

Spring Integration and Web Services

This article is part of our Academy Course titled Spring Integration for EAI.

In this course, you are introduced to Enterprise Application Integration patterns and how Spring Integration addresses them. Next, you delve into the fundamentals of Spring Integration, like channels, transformers and adapters. Check it out here!

1. Introduction

In this tutorial you are going to see the first example of an application enhanced with Spring Integration. In order to accomplish it, this example will focus on the integration with external web services.

First, I will explain what are the necessary adapters that will allow us to invoke a web service from Spring Integration. Next, we will go through a brief explanation of a Spring Web Services project, which will be the external web service that will be invoked from our application. Finishing with the main part of the tutorial, we will implement an application that will invoke the web service.

To conclude the tutorial, we will complete our application with some features provided by Spring Integration, like adding timeouts, using interceptors and learning how to retry a failed invocation.

This tutorial is composed by the following sections:

  • Introduction
  • Explaining web service channel adapters
  • Creating a Spring Web Services project
  • Implementing a Spring Integration flow
  • Adding client timeouts
  • Using interceptors
  • Web Service retry operations

2. Explaining web service channel adapters

The communication with external web services is done by Spring Integration with gateways. As explained in the previous tutorial, you can find two types of gateways: inbound and outbound. In this tutorial we will be using a special type of gateway: an outbound web service gateway. In this section we are going to focus on this type.

In order to use a web service gateway, you will need to specify a new namespace:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:int="http://www.springframework.org/schema/integration"
    xmlns:int-ws="http://www.springframework.org/schema/integration/ws"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
        http://www.springframework.org/schema/integration/ws http://www.springframework.org/schema/integration/ws/spring-integration-ws.xsd">

With the new namespace set, we can now use the web service gateway:

<int-ws:outbound-gateway id="aGateway"
        request-channel="requestChannel" reply-channel="responseChannel"
        uri="http://localhost:8080/spring-ws-tickets/tickets" marshaller="marshaller"
        unmarshaller="marshaller"/>

So, what is the behavior of this gateway? The execution of the flow would be as follows:

  1. A message is sent to the channel requestChannel.
  2. This message is then sent to the web service gateway, which is subscribed to the channel by setting its request-channel attribute.
  3. The gateway sends a request to an external web service, explained in the next section. The uri attribute specifies the destination.
  4. The gateway waits for the external web service until it returns a response.
  5. A response is returned and marshalled by the specified marshaller.
  6. The response is wrapped into a message and sent to the channel responseChannel, specified by the reply-channel attribute.

As you see, you just need to define the flow (request and reply channels) and where to call. The infrastructure details required to send the message are handled by Spring Integration.

2.1. Additional attributes

There are some more attributes that are available for customizing the invocation with the gateway. Below, there is a brief description of the main attributes:

  • Destination provider: This can be used instead of providing the “uri” attribute. In this way, you can implement your own class that will resolve dynamically which endpoint is invoked. You should provide a bean with this interface:
  • public class MyDestinationProvider implements DestinationProvider {    
        @Override
        public URI getDestination() {
            //resolve destination
        }
    }
    

    In the gateway definition, we can use this provider instead of providing the URI directly:

    <int-ws:outbound-gateway id="aGateway"
        request-channel="requestChannel" reply-channel="responseChannel" destination-provider="myDestinationProvider"
        marshaller="marshaller" unmarshaller="marshaller"/>
    
  • Message sender: Allows us to define a WebServiceMessageSender. We will use this to define a client timeout later in this tutorial.
  • Interceptor/Interceptors: You can define client interceptors. This will also be explained in a later section of this tutorial.

2.2. Inbound web service gateway

This section is just a quick reference to the inbound service gateway in order to know how it generally works, since we will not use it in this tutorial.

This gateway will receive a request from an external service, wrap the request into a message and send it into our messaging system. When we have processed the request, a message will be sent back to the gateway in order to deliver a response that the web service is waiting for.

The syntax is similar to the outbound web service gateway:

<int-ws:inbound-gateway id="anotherGateway" request-channel="requestChannel" 
    marshaller="marshaller" unmarshaller="marshaller"/>

As you may recall from previous tutorial, the response will reach the gateway through a temporary message channel. Don’t explicitly define a channel if it is not necessary.

3. Creating a Spring Web Services project

This section explains the project that will expose the web service that will be used by our application. It consists in a web application implemented using the Spring Web Services project.

The application is quite simple. It consists in a service interface that allows the user to order tickets from a cinema service. When an order is requested, the service will process it and a TicketConfirmation is returned.

The diagram below shows how it is structured:

Figure 1
Figure 1

We will explain it from bottom to top.

3.1. The ticket Service interface

Here is the service interface and implementation:

public interface TicketService {

    public TicketConfirmation order(String filmId, Date sessionDate, int quantity);
}

The implementation builds a TicketConfirmation instance from the data provided.

@Service
public class TicketServiceimpl implements TicketService {

    @Override
    public TicketConfirmation order(String filmId, Date sessionDate, int quantity) {
        float amount = 5.95f * quantity;
        TicketConfirmation confirmation = new TicketConfirmation(filmId, sessionDate, quantity, amount);
        
        return confirmation;
    }
}

The TicketConfirmation object is an immutable class that will be used to read the confirmation data:

public final class TicketConfirmation {

	private String confirmationId;
	private String filmId;
	private int quantity;
	private Date sessionDate;
	private float amount;
	
	public TicketConfirmation(String filmId, Date sessionDate, int quantity, float amount) {
		this.confirmationId = UUID.randomUUID().toString();
		this.filmId = filmId;
		this.sessionDate = new Date(sessionDate.getTime());
		this.quantity = quantity;
		this.amount = amount;
	}
	
	
	public String getConfirmationId() {
		return confirmationId;
	}
	
	public String getFilmId() {
		return filmId;
	}
	
	public int getQuantity() {
		return quantity;
	}
	
	public Date getSessionDate() {
		return new Date(sessionDate.getTime());
	}
	
	public float getAmount() {
		return amount;
	}
}

3.2. The ticket endpoint

The endpoint is responsible from receiving requests and delegating the order processing to the Ticket service:

@Endpoint
public class TicketEndpoint {

    @Autowired
    private TicketService ticketService;
    
    @PayloadRoot(localPart="ticketRequest", namespace="http://www.xpadro.spring.samples.com/tickets")
    public @ResponsePayload TicketResponse order(@RequestPayload TicketRequest ticketRequest) throws InterruptedException {
        Calendar sessionDate = Calendar.getInstance();
        sessionDate.set(2013, 9, 26);
        
        TicketConfirmation confirmation = ticketService.order(
                ticketRequest.getFilmId(), DateUtils.toDate(ticketRequest.getSessionDate()), ticketRequest.getQuantity().intValue());
        
        return buildResponse(confirmation);
    }
    
    private TicketResponse buildResponse(TicketConfirmation confirmation) {
        TicketResponse response = new TicketResponse();
        response.setConfirmationId(confirmation.getConfirmationId());
        response.setFilmId(confirmation.getFilmId());
        response.setSessionDate(DateUtils.convertDate(confirmation.getSessionDate()));
        BigInteger quantity = new BigInteger(Integer.toString(confirmation.getQuantity()));
        response.setQuantity(quantity);
        BigDecimal amount = new BigDecimal(Float.toString(confirmation.getAmount()));
        response.setAmount(amount);
        
        return response;
    }
}

The service will receive requests sent with the namespace "http://www.xpadro.spring.samples.com/tickets" and with a ticketRequest request element.

3.3. The service configuration

In the spring configuration we define the web service components:

<!-- Detects @Endpoint since it is a specialization of @Component -->
<context:component-scan base-package="xpadro.spring.ws"/>

<!-- detects @PayloadRoot -->
<ws:annotation-driven/>

<ws:dynamic-wsdl id="ticketDefinition" portTypeName="Tickets" 
                 locationUri="http://localhost:8080/spring-ws-tickets">
    <ws:xsd location="/WEB-INF/schemas/xsd/ticket-service.xsd"/>
</ws:dynamic-wsdl>
 

The web.xml file exposes the MessageDispatcherServlet:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:xpadro/spring/ws/config/root-config.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<servlet>
    <servlet-name>Ticket Servlet</servlet-name>
    <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:xpadro/spring/ws/config/servlet-config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>Ticket Servlet</servlet-name>
    <url-pattern>/tickets/*</url-pattern>
</servlet-mapping>

We now just need to deploy it into the server and it will be ready to serve ticket order requests.

4. Implementing a Spring Integration flow

Our Spring Integration application starts with a simple flow.

Figure 2
Figure 2

The request message will come through the system entry gateway. The message will then be delivered to the web service outbound gateway that will send it to the endpoint and wait for the response. Once received, it will send the response through the response channel and back to the system entry gateway, which will then deliver it to the client.

The client application sends the TicketRequest to the TicketService interface. This interface is intercepted by the gateway. In this way, the TicketRequest object is wrapped into a Spring Integration message and sent to the messaging system.

public interface TicketService {
    /**
     * Entry to the messaging system. 
     * All invocations to this method will be
     * intercepted and sent to the SI "system entry" gateway
     * 
     * @param request
     */
    @Gateway
    public TicketResponse invoke(TicketRequest request);
}

Looking at the gateway configuration, we can see that we linked it to the TicketService interface:

<int:gateway id="systemEntry" default-request-channel="requestChannel" 
    default-reply-channel="responseChannel"
    service-interface="xpadro.spring.integration.ws.gateway.TicketService" />

We have also defined the request and reply channels.

The request message will be sent to the requestChannel channel where a web service outbound gateway is subscribed:

<int-ws:outbound-gateway id="marshallingGateway"
    request-channel="requestChannel" reply-channel="responseChannel"
    uri="http://localhost:8080/spring-ws-tickets/tickets" marshaller="marshaller"
    unmarshaller="marshaller"/>

The responseChannel is configured as its reply channel, where the system entry gateway is subscribed. In this way, the client will receive the response.

The full flow is configured using direct channels. This means that the flow is synchronous; the client will block waiting the web service to respond:

<context:component-scan base-package="xpadro.spring.integration" />

<!-- Entry to the messaging system -->
<int:gateway id="systemEntry" default-request-channel="requestChannel" default-reply-channel="responseChannel"
    service-interface="xpadro.spring.integration.ws.gateway.TicketService" />

<int:channel id="requestChannel"/>

<int-ws:outbound-gateway id="marshallingGateway"
    request-channel="requestChannel" reply-channel="responseChannel"
    uri="http://localhost:8080/spring-ws-tickets/tickets" marshaller="marshaller"
    unmarshaller="marshaller"/>

<oxm:jaxb2-marshaller id="marshaller" contextPath="xpadro.spring.integration.ws.types" />

<int:channel id="responseChannel" />

The system is set; we didn’t have to implement any Java class. All is configured through configuration.

Finishing with the example, let’s see the test that executes this flow:

@ContextConfiguration({"classpath:xpadro/spring/integration/ws/test/config/int-ws-config.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
public class TestInvocation {
    
    @Autowired
    private TicketService service;
    
    @Test
    public void testInvocation() throws InterruptedException, ExecutionException {
        TicketRequest request = new TicketRequest();
        request.setFilmId("aFilm");
        request.setQuantity(new BigInteger("3"));
        request.setSessionDate(DateUtils.convertDate(new Date()));
        
        TicketResponse response = service.invoke(request);
        
        assertNotNull(response);
        assertEquals("aFilm", response.getFilmId());
        assertEquals(new BigInteger("3"), response.getQuantity());
    }
}

During the next sections, we will add some features to this example application.

5. Adding client timeouts

Checking at the gateway’s namespace, we can see that there’s no configuration for setting the invocation timeout. Regardless of this, we can use a message sender.

A message sender is an implementation of the WebServiceMessageSender. One interesting implementation provided by the Spring Web Services project is the HttpComponentsMessageSender class. This class will allow us to add authentication or connection pooling to the invocation by internally using the Apache HttpClient. And what’s more, we will also be able to define read and connection timeouts.

Following with the example, let’s add it the timeouts.

First, we need to define a bean with the above mentioned class. This will be our message sender:

<bean id="messageSender" class="org.springframework.ws.transport.http.HttpComponentsMessageSender">
    <property name="connectionTimeout" value="5000"/>
    <property name="readTimeout" value="10000"/>
</bean>

Next, we will configure the message sender in our web service gateway:

<int-ws:outbound-gateway id="marshallingGateway"
    request-channel="requestChannel" reply-channel="responseChannel"
    uri="http://localhost:8080/spring-ws-tickets/tickets" marshaller="marshaller"
    unmarshaller="marshaller" message-sender="messageSender"/>

That’s it. Now, a WebServiceIOException will be thrown if the timeout is reached.

6. Using interceptors

Another feature included in the namespace of the web service gateway is the possibility to configure client interceptors. These client interceptors are a feature of the Spring Web Services project, and refer to endpoint interceptors on the client side. A ClientInterceptor implementation has the following methods:

public interface ClientInterceptor {

    boolean handleRequest(MessageContext messageContext) throws WebServiceClientException;

    boolean handleResponse(MessageContext messageContext) throws WebServiceClientException;

    boolean handleFault(MessageContext messageContext) throws WebServiceClientException;
}
  • handleRequest: This method is invoked before the endpoint is called.
  • handleResponse: This method is invoked after the endpoint has successfully returned.
  • handleFault: If the endpoint throws a fault, this method is invoked.

Notice that these methods can manipulate a MessageContext, which contains the request and response.

Let’s see this with an example. We are going to implement our custom client interceptor to intercept the invocation before calling the endpoint and we are going to change a request value.

The interceptor implements ClientInterceptor interface:

public class MyInterceptor implements ClientInterceptor {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
        WebServiceMessage message = messageContext.getRequest();
        DOMSource source = (DOMSource) message.getPayloadSource();

        Node quantityNode = source.getNode().getAttributes().getNamedItem("quantity");
        String oldValue = quantityNode.getNodeValue();
        quantityNode.setNodeValue("5");

        logger.info("Before endpoint invocation. Changed quantity old value {} for {}", oldValue, 5);
        
        return true;
    }

    @Override
    public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
        logger.info("endpoint invocation succeeds");
        
        return true;
    }

    @Override
    public boolean handleFault(MessageContext messageContext) throws WebServiceClientException {
        logger.info("endpoint returned a fault");
        
        return true;
    }
}

Now, we need to add our interceptor to the gateway configuration:

<int-ws:outbound-gateway id="marshallingGateway"
    request-channel="requestChannel" reply-channel="responseChannel"
    uri="http://localhost:8080/spring-ws-tickets/tickets" marshaller="marshaller"
    unmarshaller="marshaller" message-sender="messageSender" interceptor="myInterceptor" />

<bean id="myInterceptor" class="xpadro.spring.integration.ws.interceptor.MyInterceptor" />

The web service gateway namespace also allows us to define an interceptors attribute. This lets us to configure a list of client interceptors.

The test will validate that the request value has been modified:

@ContextConfiguration({"classpath:xpadro/spring/integration/ws/test/config/int-ws-config.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
public class TestInvocation {
    
    @Autowired
    private TicketService service;
    
    @Test
    public void testInvocation() throws InterruptedException, ExecutionException {
        TicketRequest request = new TicketRequest();
        request.setFilmId("aFilm");
        request.setQuantity(new BigInteger("3"));
        request.setSessionDate(DateUtils.convertDate(new Date()));
        
        TicketResponse response = service.invoke(request);
        
        assertNotNull(response);
        assertEquals("aFilm", response.getFilmId());
        assertEquals(new BigInteger("5"), response.getQuantity());
    }
}

Before implementing your custom interceptor, take into account that the Spring Web Services project provides several implementations:

  • PayloadValidatingInterceptor: Validates that the payload of the web service message by using a schema. If the validation is not passed, the processing will be cancelled.
  • Wss4jSecurityInterceptor: Web service security endpoint interceptor based on Apache’s WSS4J.
  • XwsSecurityInterceptor: Web service security endpoint interceptor based on Sun’s XML and Web Services Security package.


 

7. Web Service retry operations

Sometimes, we may want to invoke a service and it is temporarily down or maybe the service is online only on certain days. If this happens, we may want to retry the invocation later. Spring Integration offers the possibility to start retrying the service invocation until a condition is met. This condition may be that the service finally responded or we reached a maximum number of attempts. For this feature, Spring Integration offers a retry advice. This advice is backed by the Spring Retry project.

The retry advice is included in the web service outbound gateway. In this way, the gateway delegates the web service invocation to the retry advice. In case the service invocation fails, the advice will keep retrying the operation based on its configuration.

7.1. Defining the retry advice

We have to define a new bean with the RequestHandlerRetryAdvice class:

<bean id="retryAdvice" class="org.springframework.integration.handler.advice.RequestHandlerRetryAdvice" >
    <property name="retryTemplate">
        <bean class="org.springframework.retry.support.RetryTemplate">
            <property name="backOffPolicy">
                <bean class="org.springframework.retry.backoff.FixedBackOffPolicy">
                    <property name="backOffPeriod" value="5000" />
                </bean>
            </property>
            <property name="retryPolicy">
                <bean class="org.springframework.retry.policy.SimpleRetryPolicy">
                    <property name="maxAttempts" value="5" />
                </bean>
            </property>
        </bean>
    </property>
</bean>

We have defined an advice that, in case of a failed invocation, it will keep retrying every five seconds until the service responds or until it has tried five times. We will later see what are these policies defined in the advice.

7.2. Adding the advice to the gateway

Once the advice is defined, we need to include it into the gateway. The Spring Integration Web Services namespace already offers an element for that:

<int-ws:outbound-gateway id="marshallingGateway"
    request-channel="requestChannel" reply-channel="responseChannel"
    uri="http://localhost:8080/spring-ws-tickets/tickets" marshaller="marshaller"
    unmarshaller="marshaller" message-sender="messageSender" interceptor="myInterceptor" >

    <int-ws:request-handler-advice-chain>
        <ref bean="retryAdvice" />
    </int-ws:request-handler-advice-chain>
</int-ws:outbound-gateway>

We have integrated the advice into the gateway. Now, let’s modify our example to see how this works.

7.3. Modifying the web service endpoint

We are going to modify our endpoint in order to keep failing until a specified number of retries is tried. In this case, two times until it returns a response.

@PayloadRoot(localPart="ticketRequest", namespace="http://www.xpadro.spring.samples.com/tickets")
public @ResponsePayload TicketResponse order(@RequestPayload TicketRequest ticketRequest) throws InterruptedException {
    Calendar sessionDate = Calendar.getInstance();
    sessionDate.set(2013, 9, 26);
    
    TicketConfirmation confirmation = ticketService.order(
            ticketRequest.getFilmId(), DateUtils.toDate(ticketRequest.getSessionDate()), ticketRequest.getQuantity().intValue());
    
    TicketResponse response = buildResponse(confirmation);
    
    retries++;
    if (retries < 3) {
        throw new RuntimeException("not enough retries");
    }
    else {
        retries = 0;
    }
    
    return response;
}

Now, we will launch the test and use our previously defined interceptor to see how it logs the attempts:

2014-03-26 08:24:50,535|AbstractEndpoint|started org.springframework.integration.endpoint.EventDrivenConsumer@392044a1
2014-03-26 08:24:50,626|MyInterceptor|Before endpoint invocation. Changed quantity old value 3 for 5
2014-03-26 08:24:51,224|MyInterceptor|endpoint returned a fault
2014-03-26 08:24:56,236|MyInterceptor|Before endpoint invocation. Changed quantity old value 3 for 5
2014-03-26 08:24:56,282|MyInterceptor|endpoint returned a fault
2014-03-26 08:25:01,285|MyInterceptor|Before endpoint invocation. Changed quantity old value 3 for 5
2014-03-26 08:25:01,377|MyInterceptor|endpoint invocation succeeds

The gateway has kept trying the invocation until the service has responded, since the retry advice have a superior number of retry times (five).

7.4. Retry advice policies

The Spring Integration retry advice is backed up on Spring Retry project policies. These policies are explained below:

Back off policy

It establishes a period of time between retries or before the initial retry. The BackOffPolicy interface defines two methods:

BackOffContext start(RetryContext context);

void backOff(BackOffContext backOffContext) throws BackOffInterruptedException;

The start method allows defining an initial behavior. For example, an initial time delay.
The backoff method allows defining a pause between retries.

The Spring Retry project provides several implementations of the back off policy:

  • Stateless back off policies: Maintain no state between invocations.
    1. FixedBackOffPolicy: It pauses for a specified time between retries. No initial delay is set.
    2. NoBackOffPolicy: Retries are executed with no pause at all between them.
  • Stateful back off policies: Maintain a state between invocations.
    1. ExponentialBackOffPolicy: Starting with a specified amount of time, it will be multiplied on each invocation. By default it doubles the time. You can change the multiplier factor.
    2. ExponentialRandomBackOffPolicy: Extends ExponentialBackOffPolicy. The multiplier is set in a random manner.

Retry policy

It allows defining how many times will the retry advice execute the web service invocation before giving up. The RetryPolicy interface defines several methods:

boolean canRetry(RetryContext context);

RetryContext open(RetryContext parent);

void close(RetryContext context);

void registerThrowable(RetryContext context, Throwable throwable);

The canRetry method returns if the operation can be retried. This could happen, for example, if we haven’t reached the maximum number of retries.
The open method is used to acquire all the necessary resources, to keep track of the number of attempts or if an exception was raised during the previous retry.
The registerThrowable method is called after every failed invocation.

The Spring Retry project provides several implementations of the retry policy:

  • SimpleRetryPolicy: Retries the invocation until a maximum number of retries is reached.
  • TimeoutRetryPolicy: It will keep retrying until a timeout is reached. The timeout is started during the open method.
  • NeverRetryPolicy: It just tries the invocation once.
  • AlwaysRetryPolicy: canRetry method always returns true. It will keep retrying until the service responds.
  • ExceptionClassifierRetryPolicy: It defines a different maximum number of attempts depending on the exception thrown.
  • CompositeRetryPolicy: It contains a list of retry policies that will be executed in order.

7.5. Retry operations using a poller

Available retry policies are implemented using time delays, which are fine for most situations, but in this section we are going to implement a custom solution that will let us use a poller that will be configured using a Cron Expression.

Since the invocation may fail, the gateway won’t return the result. We will make the flow asynchronous in order to allow the client to send the service request and proceed. In this way, the flow will keep retrying from another thread until a result is handled by a service activator or the retry limit is reached.

The gateway is as follows:

public interface AsyncTicketService {
    @Gateway
    public void invoke(TicketRequest request);
}

The gateway does not define a reply channel, since no response will be sent. Since it is an asynchronous request, the request channel contains a queue. This will allow its consumer to actively poll the message from another thread:

<int:gateway id="systemEntry" default-request-channel="requestChannel"
    service-interface="xpadro.spring.integration.ws.gateway.AsyncTicketService" />

<int:channel id="requestChannel">
    <int:queue />
</int:channel>

We have included a poller to the web service gateway, since now it will poll for messages:

<int-ws:outbound-gateway id="marshallingGateway"
    request-channel="requestChannel" reply-channel="responseChannel"
    uri="http://localhost:8080/spring-ws-tickets/tickets" marshaller="marshaller"
    unmarshaller="marshaller" interceptor="myInterceptor" >

    <int:poller fixed-rate="500" />
</int-ws:outbound-gateway>

The previous invocation can result in three different results: a correct invocation, a failed invocation that needs to be retried, and a final failed invocation that needs to be logged.

Service invocation correctly invoked

We have a service activator subscribed to the response channel. It is a simple example, so it will just log the result:

<!-- Service is running - Response received -->
<int:channel id="responseChannel" />
<int:service-activator ref="clientServiceActivator" method="handleServiceResult" input-channel="responseChannel" />

Service invocation failed. Retry the operation

If something went wrong, and since it is an asynchronous request, the exception will be wrapped into a MessageHandlingException and sent to the error channel, which is configured by default by Spring Integration.

At this point, we have a router subscribed to the error channel. This router handles how many retries have been tried and based on this, it will redirect the failed message to the appropriate channel. If the operation is to be retried, it will send the message to the retry channel:

@Component("serviceRouter")
public class ServiceRouter {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    private int maxRetries = 3;
    private int currentRetries;
    
    public String handleServiceError(Message<?> msg) {
        logger.info("Handling service failure");
        
        if (maxRetries > 0) {
            currentRetries++;
            if (currentRetries > maxRetries) {
                logger.info("Max retries [{}] reached", maxRetries);
                return "failedChannel"; 
            }
        }

        logger.info("Retry number {} of {}", currentRetries, maxRetries);
        return "retryChannel";
    }
}

The configuration of the router is shown below:

<!-- Service invocation failed -->
<int:router ref="serviceRouter" method="handleServiceError" input-channel="errorChannel"/>
<int:channel id="retryChannel" />
<int:channel id="failedChannel" />

Next, we have these endpoints that are explained below:

<!-- Retry -->
<int:service-activator ref="clientServiceActivator" method="retryFailedInvocation" input-channel="retryChannel" />

<int:inbound-channel-adapter id="retryAdapter" channel="requestChannel" 
    ref="clientServiceActivator" method="retryInvocation" auto-startup="false">

    <int:poller cron="0/5 * * * * *"/>
</int:inbound-channel-adapter>

<!-- Log failed invocation -->
<int:service-activator ref="clientServiceActivator" method="handleFailedInvocation" input-channel="failedChannel" />

The retryAdapter inbound channel adapter will keep polling the request channel but, notice that the attribute auto-startup is set to false. This means this adapter is disabled until someone activates it. We need to do this or otherwise it would start polling from the beginning, and we want to activate it only if a failed invocation occurs.

The service activator will start or stop the adapter depending on the result of the service invocation. When it fails, it will start the adapter in order to start retrying. If the maximum number of retries is reached, the router will redirect the message to the failed channel where the service activator will disable the adapter to stop it from polling. If the invocation finally succeeds, it will log the message and stop the adapter.

@Component("clientServiceActivator")
public class ClientServiceActivator {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    @Qualifier("retryAdapter")
    private AbstractEndpoint retryAdapter;
    
    private Message<?> message;

    public void handleServiceResult(Message<?> msg) {
        logger.info("service successfully invoked. Finishing flow");
        retryAdapter.stop();
    }

    public void retryFailedInvocation(Message<?> msg) {
        logger.info("Service invocation failed. Activating retry trigger...");
        MessageHandlingException exc = (MessageHandlingException) msg.getPayload();
        this.message = exc.getFailedMessage();
        retryAdapter.start();
    }

    public Message<?> retryInvocation() {
        logger.info("Retrying service invocation...");

        return message;
    }

    public void handleFailedInvocation(MessageHandlingException exception) {
        logger.info("Maximum number of retries reached. Finishing flow.");
        retryAdapter.stop();
    }
}

The test class has been modified in order to not to expect a result:

@ContextConfiguration({"classpath:xpadro/spring/integration/ws/test/config/int-ws-async-config.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
public class TestAsyncInvocation {
    
    @Autowired
    private AsyncTicketService service;
    
    @Test
    public void testInvocation() throws InterruptedException, ExecutionException {
        TicketRequest request = new TicketRequest();
        request.setFilmId("aFilm");
        request.setQuantity(new BigInteger("3"));
        request.setSessionDate(DateUtils.convertDate(new Date()));
        
        service.invoke(request);
        Thread.sleep(80000);
    }
}

That’s it. Obviously there is no need to implement all this flow knowing that we can use the retry advice of the Spring Retry project, but the purpose of this example is to gain more knowledge on how to build more complex flows, using activation and deactivation of adapters, router redirections and other features to accomplish your needs.

8. Download source code

You can download the source code regarding the spring integration and web services from here: Spring_Integration_Sample.zip and Spring_WS_Sample.zip

Xavier Padro

Xavier is a software developer working in a consulting firm based in Barcelona. He is specialized in web application development with experience in both frontend and backend. He is interested in everything related to Java and the Spring framework.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

3 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Francesco
Francesco
8 years ago

Se project spring-int-ws-ticket is a JAR pkg but there is no main to run it, so how this project should be run ?

I’ve spring-ws-tickets run on my tomcat but i dont know how to start the client…

praveen
praveen
7 years ago

not able to download spring_integration_sample.zip , please upload it again.

praveen
praveen
7 years ago

source code download is not available , please upload again.

Back to top button