package camelinaction.timeout;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.camel.AsyncCallback;
import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
import org.apache.camel.ExchangeTimedOutException;
import org.apache.camel.impl.DefaultAsyncProducer;
public class TimeoutProducer extends DefaultAsyncProducer {
// use a thread pool for async communication
private ExecutorService executor;
private final long timeout;
public TimeoutProducer(Endpoint endpoint, long timeout) {
super(endpoint);
this.timeout = timeout;
}
@Override
public boolean process(final Exchange exchange, final AsyncCallback callback) {
// validate that the message has some data
try {
String body = exchange.getIn().getBody(String.class);
if (body == null || body.contains("Donkey")) {
throw new IllegalArgumentException("Real developers ride Camels");
}
} catch (Exception e) {
// idiom to catch exception and set on Exchange
exchange.setException(e);
// and need to call callback before returning when using true
callback.done(true);
// return true
return true;
}
CallRemoteSystemTask task = new CallRemoteSystemTask(exchange, callback, timeout);
executor.submit(task);
return false;
}
private class CallRemoteSystemTask implements Runnable {
private final Exchange exchange;
private final AsyncCallback callback;
private final long timeout;
private CallRemoteSystemTask(Exchange exchange, AsyncCallback callback, long timeout) {
this.exchange = exchange;
this.callback = callback;
this.timeout = timeout;
}
@Override
public void run() {
try {
String in = exchange.getIn().getBody(String.class);
// create remote task that simulates calling remote system
Future<String> future = executor.submit(new ERPTask(in));
// get the response from the future, with timeout support
String response = future.get(timeout, TimeUnit.MILLISECONDS);
// set response on body
if (response != null) {
exchange.getOut().setBody(in + ";" + response);
}
} catch (TimeoutException e) {
// timeout happened, so set this as a ExchangeTimedOutException so its a specialized timeout exception
exchange.setException(new ExchangeTimedOutException(exchange, timeout, "Timeout waiting for reply from ERP system"));
} catch (Exception e) {
// in case of any other exception set that on the exchange
exchange.setException(e);
} finally {
// always notify callback we are done (so we do this in finally block)
// we must use done(false) because the process method returned false
log.info("Continue routing");
callback.done(false);
}
}
}
/**
* Task that simulate calling the remote system with timeout support
* <p/>
* Instead of using a 3rd party client to do a remote network call, we
* reuse the thread pool to submit a task, which we can use a Future to detect if there was a timeout or not.
* <p/>
* Ideally the 3rd party clients should offer timeout out of the box, and you should just leverage this, and
* use the timeout configuration from the Camel endpoint as configuration value.
*/
private class ERPTask implements Callable<String> {
private final String request;
private ERPTask(String request) {
this.request = request;
}
@Override
public String call() throws Exception {
// if we order AMQ in Action the system takes too long time to response
boolean block = "ActiveMQ in Action".equals(request);
log.info("Calling ERP with timeout {} sec.", timeout);
// simulate communication with ERP takes 5 seconds
try {
int delay = block ? 10 * 60 * 1000 : 5000;
Thread.sleep(delay);
} catch (InterruptedException e) {
// ignore
}
log.info("ERP reply received");
return "516";
}
}
@Override
protected void doStart() throws Exception {
super.doStart();
// use Camel to create the thread pool for us
this.executor = getEndpoint().getCamelContext().getExecutorServiceManager().newFixedThreadPool(this, "ERP", 10);
}
@Override
protected void doStop() throws Exception {
super.doStop();
// shutdown thread pool when we stop
getEndpoint().getCamelContext().getExecutorServiceManager().shutdown(executor);
}
}