package camelinaction;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.sql.DataSource;
import org.apache.camel.builder.AdviceWithRouteBuilder;
import org.apache.camel.builder.NotifyBuilder;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.test.spring.CamelSpringTestSupport;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
public class PropagationRollbackLastTest extends CamelSpringTestSupport {
private JdbcTemplate jdbc;
@Before
public void setupDatabase() throws Exception {
DataSource ds = context.getRegistry().lookupByNameAndType("myDataSource", DataSource.class);
jdbc = new JdbcTemplate(ds);
jdbc.execute("create table bookorders "
+ "( order_id varchar(10), order_book varchar(50) )");
jdbc.execute("create table bookaudit "
+ "( order_id varchar(10), order_book varchar(50), order_redelivery varchar(5) )");
}
@After
public void dropDatabase() throws Exception {
jdbc.execute("drop table bookorders");
jdbc.execute("drop table bookaudit");
}
@Override
protected AbstractApplicationContext createApplicationContext() {
// we still use the spring xml file to setup the activemq, jta transaction and atomikos
// as that is a lot of work to do in Java code only
return new ClassPathXmlApplicationContext("spring-context.xml");
}
@Override
public boolean isUseAdviceWith() {
return true;
}
@Test
public void testAuditLogRollbackLast() throws Exception {
// we should have 1 original message
NotifyBuilder notify = new NotifyBuilder(context).whenDone(1).create();
// simulate the audit-log will fail
context.getRouteDefinition("audit").adviceWith(context, new AdviceWithRouteBuilder() {
@Override
public void configure() throws Exception {
// simulate error connecting to database
interceptSendToEndpoint("bean:auditLogService").skipSendToOriginalEndpoint()
.throwException(new IOException("Cannot connect to database"));
}
});
context.start();
// there should be 0 row in the database when we start
assertEquals(Long.valueOf(0), jdbc.queryForObject("select count(*) from bookorders", Long.class));
assertEquals(Long.valueOf(0), jdbc.queryForObject("select count(*) from bookaudit", Long.class));
template.sendBody("activemq:queue:inbox", "Camel in Action");
// wait for the route to complete
assertTrue(notify.matches(10, TimeUnit.SECONDS));
// the database need a little sleep time before commits are visible
Thread.sleep(1000);
// and the message was sent to the order queue
String reply = consumer.receiveBody("activemq:queue:order", 10000, String.class);
assertEquals("Camel in Action", reply);
// there should be 0 row in the database with the order, and 0 in the audit-log as it was rolled back only
assertEquals(Long.valueOf(1), jdbc.queryForObject("select count(*) from bookorders", Long.class));
assertEquals(Long.valueOf(0), jdbc.queryForObject("select count(*) from bookaudit", Long.class));
// print the SQL
log.info("The following orders was recorded in the orders ...");
List<Map<String, Object>> rows = jdbc.queryForList("select * from bookorders");
for (Map<String, Object> row : rows) {
log.info("Book order[id={}, book={}]", row.get("order_id"), row.get("order_book"));
}
log.info("The following orders was recorded in the audit-log ...");
rows = jdbc.queryForList("select * from bookaudit");
for (Map<String, Object> row : rows) {
log.info("Book audit-log[id={}, book={}, redelivery={}]", row.get("order_id"), row.get("order_book"), row.get("order_redelivery"));
}
}
@Override
protected RouteBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
@Override
public void configure() throws Exception {
from("activemq:queue:inbox").routeId("inbox")
.transacted("required")
.to("direct:audit")
.to("direct:order")
.to("activemq:queue:order");
from("direct:audit").routeId("audit")
// mark only this last transaction as rollback, which prevent parent transaction from being
// forced to rollback as well
.onException(Exception.class).markRollbackOnlyLast().end()
.transacted("requiresNew")
.to("bean:auditLogService");
from("direct:order").routeId("order")
.transacted("mandatory")
.to("bean:orderService");
}
};
}
}