Coding a JMS client to a JBoss EAP 6 - WildFly JMS cluster
This tutorial demonstrates how to code a remote JMS client application which connects to a full-ha profile configured on EAP 6 / AS 7 domain. We will show how sessions can be balanced on different cluster nodes.
Spring-retry is one of the many side-projects of Spring: the famous dependency injection framework. This library let us automatically re-invoke a method, moreover this operation it’s trasparent to the rest of our application. In this post I will try to illustrate the main features of this API, using a simple example.
Setup
To add Spring-retry to your project, you just need to add the following dependencies to your pom.xml file:
Our purpose is to obtain the current Euro/Dollar exchange rate consuming a REST service. If the server responds with a HTTP code 503, we will relaunch the method unitil the server responds with a 200 code. Here is the service implementation:
@Path("/fetchRate")
public class ChangeService {
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response getChange(@QueryParam("from") String from,
@QueryParam("to") String to) {
Random randomGenerator = new Random();
int randomInt = randomGenerator.nextInt(2);
if (from.equals("EUR") && to.equals("USD")) {
if (randomInt == 1)
return Response.status(200).entity("1.10").build();
else
return Response.status(503)
.entity("Service temporarily not available").build();
}
return Response.status(500).entity("Currency not available").build();
}
}
As you can see, in order to simulate a service which is temporarily unavailable we are using a Random generator which returns a 503 error on 50% of the invocations.
@Retryable
The “heart” of spring-retry is the @Retryable annotation. With the maxAttempts attribute we will set how many times the method should be invoked. With the @Backoff annotation we will configure an initial delay in milliseconds; this delay will increase of a 50% factor for every iteration. Let’s have a look at the full code of the REST client to understand how this annotation works.
public class RealExchangeRateCalculator implements ExchangeRateCalculator {
private static final double BASE_EXCHANGE_RATE = 1.09;
private int attempts = 0;
private SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
@Retryable(maxAttempts=10,value=RuntimeException.class,backoff = @Backoff(delay = 10000,multiplier=2))
public Double getCurrentRate() {
System.out.println("Calculating - Attempt " + attempts + " at " + sdf.format(new Date()));
attempts++;
try {
HttpResponse<JsonNode> response = Unirest.get("http://rate-exchange.herokuapp.com/fetchRate")
.queryString("from", "EUR")
.queryString("to","USD")
.asJson();
switch (response.getStatus()) {
case 200:
return response.getBody().getObject().getDouble("Rate");
case 503:
throw new RuntimeException("Server Response: " + response.getStatus());
default:
throw new IllegalStateException("Server not ready");
}
} catch (UnirestException e) {
throw new RuntimeException(e);
}
}
@Recover
public Double recover(RuntimeException e){
System.out.println("Recovering - returning safe value");
return BASE_EXCHANGE_RATE;
}
}
In case of a RuntimeException, the getCurrentRate method will be automatically reinvoked 10 times. If during the last execution the method fails again, the method annotated with @Recover will be launched. If there’s no recover method, the exception it’s simply throwed up.
XML Configuration
All of our configuration are made via annotations. Spring-retry, just like its greater brother Spring Framework can use the XML configuration. In this case we use the aspect-oriented programming, adding an Interceptor to our beans with this configuration:
The source code for this Spring application is available on Github.
Using Spring Retry with Spring Boot
Let's see now an example about String retry using Spring Boot framework. The logic does not change comparing to the standard Spring application. We will code a Controller class which uses a Database to retrieve a list of Customer objects. Should this fail, a static list of Customer objects will be returned:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@RestController
public class CustomerController {
@Autowired
CustomerRepository repository;
List<Customer> customerList = new ArrayList<Customer>();
@PostConstruct
public void init() {
customerList.add(new Customer(1, "frank"));
customerList.add(new Customer(2, "john"));
}
@RequestMapping("/list")
@Retryable(value = {ServiceNotAvailableException.class}, maxAttempts = 2, backoff = @Backoff(delay = 1000))
public List<Customer> findAll() {
int random = new Random().nextInt(2);
if (random == 1) {
throw new ServiceNotAvailableException("DB Not available! using Spring-retry..");
}
System.out.println("Returning data from DB");
return repository.findAll();
}
@Recover
public List<Customer> getBackendResponseFallback(ServiceNotAvailableException e) {
System.out.println("Returning cached list.");
return customerList;
}
}
As you can see, the logic is contained in the findAll method which uses a Random generator to simulate a failure in accessing the Database. This is allowed up to 2 attempts. If that fails more than maxAttempts, the @Recover method will be triggered, to return the customerList from an ArrayList. We need to add @EnableRetry on the top of our SpringBoot application:
@SpringBootApplication
@EnableRetry
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Upon start, the application will insert some data in the H2 Database, so that the findAll method is able to return a set of records:
INSERT into CUSTOMER (id,name) VALUES (10, 'frank');
INSERT into CUSTOMER (id,name) VALUES (20, 'john');
To connect to the H2 Database, we will include the following configuration in the resources/application.properties file:
This tutorial describes how you can integrate Apache Camel in your WildFly Application Server configuration. At the end of this tuorial you will be able to turn your WildFly application server into a mini service bus!
Hibernate OGM is a framework that lets you use the power of JPA and Hibernate API with a NoSQL database like MongoDB. To make it fun, we will deploy the JPA MongoDB application on WildFly application server.
Some time ago I've found one interesting thread on Jboss.org forum (https://developer.jboss.org/thread/254496) discussing about one undocumented option available in the CLI which allows to use external properties in a script. I've used it to create a general purpose script to install any kind of Datasource. Let's see how to do it.
Configuring WildFly 9 from the CLI in offline mode
WildFly 9 has a new management mode for the Command Line Interface which allows varying your configuration without being connected to a WildFly instance.
Since WildFly 9 you have the ability to suspend/resume the execution of the Application Server. This new feature allows your existing request to complete while preventing new requests to be accepted.