Add spring-batch- to module directory names (so folks can use mvn eclipse:eclipse if they want to).

BATCH-238: Remove hibernate support for the Daos.
This commit is contained in:
dsyer
2007-12-10 21:23:48 +00:00
parent 17705f27ab
commit 8ea331bfc7
884 changed files with 956 additions and 2352 deletions

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.config;
import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;
public class DatasourceTests extends AbstractTransactionalDataSourceSpringContextTests {
protected String[] getConfigLocations() {
return new String[] { "/org/springframework/batch/jms/jms-context.xml" };
}
public void testTemplate() throws Exception {
System.err.println(System.getProperty("java.class.path"));
jdbcTemplate.execute("delete from T_FOOS");
int count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(0, count);
jdbcTemplate.update("INSERT into T_FOOS (id,name,foo_date) values (?,?,null)", new Object[] { Integer.valueOf(0),
"foo" });
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.config;
import java.util.ArrayList;
import java.util.List;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
public class MessagingTests extends AbstractDependencyInjectionSpringContextTests {
private JmsTemplate jmsTemplate;
public void setJmsTemplate(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
protected String[] getConfigLocations() {
return new String[] { "/org/springframework/batch/jms/jms-context.xml" };
}
protected void onSetUp() throws Exception {
super.onSetUp();
Thread.sleep(100L);
getMessages(); // drain queue
jmsTemplate.convertAndSend("queue", "foo");
jmsTemplate.convertAndSend("queue", "bar");
}
public void testMessaging() throws Exception {
List list = getMessages();
System.err.println(list);
assertEquals(2, list.size());
assertTrue(list.contains("foo"));
}
private List getMessages() {
String next = "";
List msgs = new ArrayList();
while (next != null) {
next = (String) jmsTemplate.receiveAndConvert("queue");
if (next != null)
msgs.add(next);
}
return msgs;
}
}

View File

@@ -0,0 +1,207 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.container.jms;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Session;
import junit.framework.TestCase;
import org.easymock.MockControl;
import org.springframework.batch.container.jms.BatchMessageListenerContainer;
import org.springframework.batch.repeat.RepeatCallback;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.batch.repeat.policy.SimpleCompletionPolicy;
import org.springframework.batch.repeat.support.RepeatTemplate;
import org.springframework.util.ReflectionUtils;
public class BatchMessageListenerContainerTests extends TestCase {
BatchMessageListenerContainer container;
int count = 0;
public void testReceiveAndExecuteWithNoCallback() throws Exception {
RepeatTemplate template = new RepeatTemplate() {
public ExitStatus iterate(RepeatCallback callback) {
count++;
return ExitStatus.CONTINUABLE; // means we can continue to operate, but no message is received
}
};
container = new BatchMessageListenerContainer(template);
boolean received = doExecute(null, null);
assertEquals(1, count);
assertFalse("Message received", received);
}
public void testReceiveAndExecuteWithCallback() throws Exception {
RepeatTemplate template = new RepeatTemplate();
template.setCompletionPolicy(new SimpleCompletionPolicy(2));
container = new BatchMessageListenerContainer(template);
MockControl sessionControl = MockControl.createNiceControl(Session.class);
MockControl consumerControl = MockControl.createControl(MessageConsumer.class);
MockControl messageControl = MockControl.createControl(Message.class);
Session session = (Session) sessionControl.getMock();
MessageConsumer consumer = (MessageConsumer) consumerControl.getMock();
Message message = (Message) messageControl.getMock();
// Expect two calls to consumer (chunk size)...
consumerControl.expectAndReturn(consumer.receive(1000), message);
consumerControl.expectAndReturn(consumer.receive(1000), message);
sessionControl.replay();
consumerControl.replay();
messageControl.replay();
boolean received = doExecute(session, consumer);
assertTrue("Message not received", received);
sessionControl.verify();
consumerControl.verify();
messageControl.verify();
}
public void testReceiveAndExecuteWithCallbackReturningNull() throws Exception {
RepeatTemplate template = new RepeatTemplate();
template.setCompletionPolicy(new SimpleCompletionPolicy(2));
container = new BatchMessageListenerContainer(template);
MockControl sessionControl = MockControl.createNiceControl(Session.class);
MockControl consumerControl = MockControl.createControl(MessageConsumer.class);
Session session = (Session) sessionControl.getMock();
MessageConsumer consumer = (MessageConsumer) consumerControl.getMock();
Message message = null;
// Expect one call to consumer (chunk size is 2 but terminates on
// first)...
consumerControl.expectAndReturn(consumer.receive(1000), message);
sessionControl.replay();
consumerControl.replay();
boolean received = doExecute(session, consumer);
assertFalse("Message not received", received);
sessionControl.verify();
consumerControl.verify();
}
public void testTransactionalReceiveAndExecuteWithCallbackThrowingException() throws Exception {
RepeatTemplate template = new RepeatTemplate();
template.setCompletionPolicy(new SimpleCompletionPolicy(2));
container = new BatchMessageListenerContainer(template);
container.setSessionTransacted(true);
boolean received = doTestWithException(new IllegalStateException("No way!"), true, 2);
assertFalse("Message received", received);
}
public void testNonTransactionalReceiveAndExecuteWithCallbackThrowingException() throws Exception {
RepeatTemplate template = new RepeatTemplate();
template.setCompletionPolicy(new SimpleCompletionPolicy(2));
container = new BatchMessageListenerContainer(template);
container.setSessionTransacted(false);
boolean received = doTestWithException(new IllegalStateException("No way!"), false, 2);
assertTrue("Message not received", received);
}
public void testNonTransactionalReceiveAndExecuteWithCallbackThrowingError() throws Exception {
RepeatTemplate template = new RepeatTemplate();
template.setCompletionPolicy(new SimpleCompletionPolicy(2));
container = new BatchMessageListenerContainer(template);
container.setSessionTransacted(false);
try {
boolean received = doTestWithException(new RuntimeException("No way!"), false, 2);
assertTrue("Message not received", received);
}
catch (RuntimeException e) {
assertEquals("No way!", e.getMessage());
fail("Unexpected Error - should be swallowed");
}
}
private boolean doTestWithException(final Throwable t, boolean expectRollback, int expectGetTransactionCount)
throws JMSException, IllegalAccessException {
container.setAcceptMessagesWhileStopping(true);
container.setMessageListener(new MessageListener() {
public void onMessage(Message arg0) {
if (t instanceof RuntimeException)
throw (RuntimeException) t;
else
throw (Error) t;
}
});
MockControl sessionControl = MockControl.createNiceControl(Session.class);
MockControl consumerControl = MockControl.createNiceControl(MessageConsumer.class);
MockControl messageControl = MockControl.createNiceControl(Message.class);
Session session = (Session) sessionControl.getMock();
MessageConsumer consumer = (MessageConsumer) consumerControl.getMock();
Message message = (Message) messageControl.getMock();
sessionControl.expectAndReturn(session.getTransacted(), true, expectGetTransactionCount);
// Expect only one call to consumer (chunk size is 2, but first one
// rolls back terminating batch)...
consumerControl.expectAndReturn(consumer.receive(1000), message);
if (expectRollback) {
session.rollback();
sessionControl.setVoidCallable();
}
sessionControl.replay();
consumerControl.replay();
messageControl.replay();
boolean received = doExecute(session, consumer);
sessionControl.verify();
consumerControl.verify();
messageControl.verify();
return received;
}
private boolean doExecute(Session session, MessageConsumer consumer) throws IllegalAccessException {
Method method = ReflectionUtils.findMethod(container.getClass(), "receiveAndExecute", new Class[] {
Session.class, MessageConsumer.class });
method.setAccessible(true);
boolean received;
try {
received = ((Boolean) method.invoke(container, new Object[] { session, consumer })).booleanValue();
}
catch (InvocationTargetException e) {
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
} else {
throw (Error) e.getCause();
}
}
return received;
}
}

View File

@@ -0,0 +1,80 @@
package org.springframework.batch.io.oxm;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import junit.framework.TestCase;
import org.springframework.batch.io.file.support.StaxEventReaderInputSource;
import org.springframework.batch.io.file.support.oxm.UnmarshallingFragmentDeserializer;
import org.springframework.batch.io.oxm.domain.Trade;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.oxm.Unmarshaller;
public abstract class AbstractStaxEventReaderInputSourceTests extends TestCase {
private StaxEventReaderInputSource source = new StaxEventReaderInputSource();
protected Resource resource = new ClassPathResource(
"org/springframework/batch/io/oxm/input.xml");
protected void setUp() throws Exception {
//TODO sensible resource allocation
source.setResource(resource);
source.setFragmentRootElementName("trade");
UnmarshallingFragmentDeserializer deserializer = new UnmarshallingFragmentDeserializer(getUnmarshaller());
source.setFragmentDeserializer(deserializer);
}
public void testRead() {
Object result;
List results = new ArrayList();
while ((result = source.read()) != null) {
results.add(result);
}
checkResults(results);
}
/**
* @return Unmarshaller specific to the OXM library used
*/
protected abstract Unmarshaller getUnmarshaller() throws Exception;
/**
* @param results list of domain objects returned by input source
*/
protected void checkResults(List results){
assertEquals(3, results.size());
Trade trade1 = (Trade) results.get(0);
assertEquals("XYZ0001", trade1.getIsin());
assertEquals(5, trade1.getQuantity());
assertEquals(BigDecimal.valueOf(11.39), trade1.getPrice());
assertEquals("Customer1", trade1.getCustomer());
Trade trade2 = (Trade) results.get(1);
assertEquals("XYZ0002", trade2.getIsin());
assertEquals(2, trade2.getQuantity());
assertEquals(BigDecimal.valueOf(72.99), trade2.getPrice());
assertEquals("Customer2", trade2.getCustomer());
Trade trade3 = (Trade) results.get(2);
assertEquals("XYZ0003", trade3.getIsin());
assertEquals(9, trade3.getQuantity());
assertEquals(BigDecimal.valueOf(99.99), trade3.getPrice());
assertEquals("Customer3", trade3.getCustomer());
}
protected void tearDown() throws Exception {
source.close();
}
public void setResource(Resource resource) {
this.resource = resource;
}
}

View File

@@ -0,0 +1,74 @@
package org.springframework.batch.io.oxm;
import java.io.File;
import java.io.FileReader;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import junit.framework.TestCase;
import org.custommonkey.xmlunit.XMLAssert;
import org.custommonkey.xmlunit.XMLUnit;
import org.springframework.batch.io.file.support.StaxEventWriterItemWriter;
import org.springframework.batch.io.file.support.oxm.MarshallingObjectToXmlSerializer;
import org.springframework.batch.io.oxm.domain.Trade;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.oxm.Marshaller;
public abstract class AbstractStaxEventWriterItemWriterTests extends TestCase {
private StaxEventWriterItemWriter writer = new StaxEventWriterItemWriter();
private Resource resource;
File outputFile;
protected Resource expected = new ClassPathResource("expected-output.xml", getClass());
protected List objects = new ArrayList() {{
add(new Trade("isin1", 1, new BigDecimal(1.0), "customer1"));
add(new Trade("isin2", 2, new BigDecimal(2.0), "customer2"));
add(new Trade("isin3", 3, new BigDecimal(3.0), "customer3"));
}};
/**
* Write list of domain objects and check the output file.
*/
public void testWrite() throws Exception {
for (Iterator iterator = objects.listIterator(); iterator.hasNext();) {
writer.write(iterator.next());
}
writer.close();
XMLUnit.setIgnoreWhitespace(true);
XMLAssert.assertXMLEqual(new FileReader(expected.getFile()), new FileReader(resource.getFile()));
}
protected void setUp() throws Exception {
//File outputFile = File.createTempFile("AbstractStaxStreamWriterOutputSourceTests", "xml");
outputFile = File.createTempFile(this.getClass().getSimpleName(), ".xml");
resource = new FileSystemResource(outputFile);
writer.setResource(resource);
MarshallingObjectToXmlSerializer mapper = new MarshallingObjectToXmlSerializer(getMarshaller());
writer.setSerializer(mapper);
}
protected void tearDown() throws Exception {
super.tearDown();
outputFile.delete();
}
/**
* @return Marshaller specific for the OXM technology being used.
*/
protected abstract Marshaller getMarshaller() throws Exception;
}

View File

@@ -0,0 +1,20 @@
package org.springframework.batch.io.oxm;
import org.springframework.core.io.ClassPathResource;
import org.springframework.oxm.Marshaller;
import org.springframework.oxm.castor.CastorMarshaller;
public class CastorMarshallingTests extends AbstractStaxEventWriterItemWriterTests {
protected Marshaller getMarshaller() throws Exception {
CastorMarshaller marshaller = new CastorMarshaller();
// marshaller.setTargetClass(Trade.class);
marshaller.setMappingLocation(new ClassPathResource("mapping-castor.xml", getClass()));
// there is no way to call
// org.exolab.castor.xml.Marshaller.setSupressXMLDeclaration();
marshaller.afterPropertiesSet();
return marshaller;
}
}

View File

@@ -0,0 +1,18 @@
package org.springframework.batch.io.oxm;
import org.springframework.core.io.ClassPathResource;
import org.springframework.oxm.Unmarshaller;
import org.springframework.oxm.castor.CastorMarshaller;
public class CastorUnmarshallingTests extends AbstractStaxEventReaderInputSourceTests {
protected Unmarshaller getUnmarshaller() throws Exception {
CastorMarshaller unmarshaller = new CastorMarshaller();
unmarshaller.setMappingLocation(new ClassPathResource("mapping-castor.xml", getClass()));
// alternatively target class can be set
//unmarshaller.setTargetClass(Trade.class);
unmarshaller.afterPropertiesSet();
return unmarshaller;
}
}

View File

@@ -0,0 +1,18 @@
package org.springframework.batch.io.oxm;
import org.springframework.batch.io.oxm.domain.Trade;
import org.springframework.oxm.Marshaller;
import org.springframework.oxm.xstream.XStreamMarshaller;
public class XStreamMarshallingTests extends
AbstractStaxEventWriterItemWriterTests {
protected Marshaller getMarshaller() throws Exception {
XStreamMarshaller marshaller = new XStreamMarshaller();
marshaller.addAlias("trade", Trade.class);
//in XStreamMarshaller.marshalSaxHandlers() method is used SaxWriter, which is configured
//to include enclosing document (SaxWriter.includeEnclosingDocument is always set to TRUE)
return marshaller;
}
}

View File

@@ -0,0 +1,20 @@
package org.springframework.batch.io.oxm;
import java.math.BigDecimal;
import org.springframework.batch.io.oxm.domain.Trade;
import org.springframework.oxm.Unmarshaller;
import org.springframework.oxm.xstream.XStreamMarshaller;
public class XStreamUnmarshallingTests extends AbstractStaxEventReaderInputSourceTests {
protected Unmarshaller getUnmarshaller() throws Exception {
XStreamMarshaller unmarshaller = new XStreamMarshaller();
unmarshaller.addAlias("trade", Trade.class);
unmarshaller.addAlias("isin", String.class);
unmarshaller.addAlias("customer", String.class);
unmarshaller.addAlias("price", BigDecimal.class);
return unmarshaller;
}
}

View File

@@ -0,0 +1,72 @@
package org.springframework.batch.io.oxm.domain;
import java.math.BigDecimal;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
/**
* @author Rob Harrop
*/
public class Trade {
private String isin = "";
private long quantity = 0;
private BigDecimal price = new BigDecimal(0);
private String customer = "";
public Trade() {
}
public Trade(String isin, long quantity, BigDecimal price, String customer){
this.isin = isin;
this.quantity = quantity;
this.price = price;
this.customer = customer;
}
public void setCustomer(String customer) {
this.customer = customer;
}
public void setIsin(String isin) {
this.isin = isin;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public void setQuantity(long quantity) {
this.quantity = quantity;
}
public String getIsin() {
return isin;
}
public BigDecimal getPrice() {
return price;
}
public long getQuantity() {
return quantity;
}
public String getCustomer() {
return customer;
}
public String toString() {
return "Trade: [isin=" + this.isin + ",quantity=" + this.quantity + ",price="
+ this.price + ",customer=" + this.customer + "]";
}
public boolean equals(Object o) {
return EqualsBuilder.reflectionEquals(this, o);
}
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
}

View File

@@ -0,0 +1,196 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.jms;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemProvider;
import org.springframework.batch.item.provider.AbstractItemProvider;
import org.springframework.batch.repeat.RepeatCallback;
import org.springframework.batch.repeat.RepeatContext;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.batch.repeat.policy.SimpleCompletionPolicy;
import org.springframework.batch.repeat.support.RepeatTemplate;
import org.springframework.batch.retry.callback.ItemProviderRetryCallback;
import org.springframework.batch.retry.policy.ItemProviderRetryPolicy;
import org.springframework.batch.retry.policy.SimpleRetryPolicy;
import org.springframework.batch.retry.support.RetryTemplate;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
public class ExternalRetryInBatchTests extends AbstractDependencyInjectionSpringContextTests {
private JmsTemplate jmsTemplate;
private RetryTemplate retryTemplate;
private RepeatTemplate repeatTemplate;
private ItemProvider provider;
private JdbcTemplate jdbcTemplate;
private PlatformTransactionManager transactionManager;
public void setDataSource(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public void setRepeatTemplate(RepeatTemplate repeatTemplate) {
this.repeatTemplate = repeatTemplate;
}
public void setJmsTemplate(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
protected String[] getConfigLocations() {
return new String[] { "/org/springframework/batch/jms/jms-context.xml" };
}
protected void onSetUp() throws Exception {
super.onSetUp();
getMessages(); // drain queue
jdbcTemplate.execute("delete from T_FOOS");
jmsTemplate.convertAndSend("queue", "foo");
jmsTemplate.convertAndSend("queue", "bar");
provider = new AbstractItemProvider() {
public Object next() {
String text = (String) jmsTemplate.receiveAndConvert("queue");
list.add(text);
return text;
}
public boolean recover(Object data, Throwable cause) {
recovered.add(data);
return true;
}
};
retryTemplate = new RetryTemplate();
}
protected void onTearDown() throws Exception {
getMessages(); // drain queue
jdbcTemplate.execute("delete from T_FOOS");
}
private void assertInitialState() {
int count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(0, count);
}
private List list = new ArrayList();
private List recovered = new ArrayList();
public void testExternalRetryRecoveryInBatch() throws Exception {
assertInitialState();
retryTemplate.setRetryPolicy(new ItemProviderRetryPolicy(new SimpleRetryPolicy(1)));
final ItemProviderRetryCallback callback = new ItemProviderRetryCallback(provider, new ItemProcessor() {
public void process(final Object text) {
// No need for transaction here: the whole batch will roll
// back. When it comes back for recovery this code is not
// executed...
jdbcTemplate.update("INSERT into T_FOOS (id,name,foo_date) values (?,?,null)", new Object[] {
Integer.valueOf(list.size()), text });
throw new RuntimeException("Rollback!");
}
});
repeatTemplate.setCompletionPolicy(new SimpleCompletionPolicy(2));
// In a real container this could be an outer retry loop with an
// *internal* retry policy.
for (int i = 0; i < 4; i++) {
try {
new TransactionTemplate(transactionManager).execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
try {
repeatTemplate.iterate(new RepeatCallback() {
public ExitStatus doInIteration(RepeatContext context) throws Exception {
return new ExitStatus(retryTemplate.execute(callback)!=null);
}
});
return null;
}
catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
});
}
catch (Exception e) {
if (i == 0 || i == 2) {
assertEquals("Rollback!", e.getMessage());
}
else {
throw e;
}
}
finally {
System.err.println(i + ": " + recovered);
}
}
List msgs = getMessages();
System.err.println(msgs);
assertEquals(2, recovered.size());
// The database portion committed once...
int count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(0, count);
// ... and so did the message session.
// Both messages were failed and recovered after last retry attempt:
assertEquals("[]", msgs.toString());
assertEquals("[foo, bar]", recovered.toString());
}
private List getMessages() {
String next = "";
List msgs = new ArrayList();
while (next != null) {
next = (String) jmsTemplate.receiveAndConvert("queue");
if (next != null)
msgs.add(next);
}
return msgs;
}
}

View File

@@ -0,0 +1,160 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.repeat.jms;
import java.util.ArrayList;
import java.util.List;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.springframework.batch.container.jms.BatchMessageListenerContainer;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.listener.SessionAwareMessageListener;
import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
public class AsynchronousTests extends AbstractDependencyInjectionSpringContextTests {
protected String[] getConfigLocations() {
return new String[] { "/org/springframework/batch/jms/jms-context.xml" };
}
private BatchMessageListenerContainer container;
private JmsTemplate jmsTemplate;
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void setJmsTemplate(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
public void setContainer(BatchMessageListenerContainer container) {
this.container = container;
}
protected void onSetUp() throws Exception {
super.onSetUp();
String foo = "";
int count = 0;
while (foo != null && count < 100) {
foo = (String) jmsTemplate.receiveAndConvert("queue");
count++;
}
jdbcTemplate.execute("delete from T_FOOS");
// Queue is now drained...
assertNull(foo);
// Add a couple of messages...
jmsTemplate.convertAndSend("queue", "foo");
jmsTemplate.convertAndSend("queue", "bar");
}
protected void onTearDown() throws Exception {
super.onTearDown();
container.stop();
// Need to give the container time to shutdown
Thread.sleep(1000L);
}
List list = new ArrayList();
private void assertInitialState() {
int count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(0, count);
}
public void testSunnyDay() throws Exception {
assertInitialState();
container.setMessageListener(new SessionAwareMessageListener() {
public void onMessage(Message message, Session session) throws JMSException {
list.add(message.toString());
String text = ((TextMessage) message).getText();
jdbcTemplate.update("INSERT into T_FOOS (id,name,foo_date) values (?,?,null)", new Object[] {
Integer.valueOf(list.size()), text });
}
});
container.start();
// Need to sleep for at least a second here...
Thread.sleep(1000L);
System.err.println(jdbcTemplate.queryForList("select * from T_FOOS"));
assertEquals(2, list.size());
String foo = (String) jmsTemplate.receiveAndConvert("queue");
assertEquals(null, foo);
int count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(2, count);
}
public void testRollback() throws Exception {
assertInitialState();
container.setMessageListener(new SessionAwareMessageListener() {
public void onMessage(Message message, Session session) throws JMSException {
list.add(message.toString());
final String text = ((TextMessage) message).getText();
jdbcTemplate.update("INSERT into T_FOOS (id,name,foo_date) values (?,?,null)", new Object[] {
new Integer(list.size()), text });
// This causes the DB to rollback but not the message
if (text.equals("bar")) {
throw new RuntimeException("Rollback!");
}
}
});
container.start();
// Need to sleep for at least a second here...
Thread.sleep(3000L);
// We rolled back so the messages might come in many times...
assertTrue(list.size() >= 1);
System.err.println(jdbcTemplate.queryForList("select * from T_FOOS"));
String text = "";
List msgs = new ArrayList();
while (text != null) {
text = (String) jmsTemplate.receiveAndConvert("queue");
msgs.add(text);
}
System.err.println(msgs);
int count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(0, count);
assertTrue("Foo not on queue", msgs.contains("foo"));
}
}

View File

@@ -0,0 +1,207 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.repeat.jms;
import java.util.ArrayList;
import java.util.List;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Session;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.batch.repeat.RepeatCallback;
import org.springframework.batch.repeat.RepeatContext;
import org.springframework.batch.repeat.support.RepeatTemplate;
import org.springframework.jms.connection.SessionProxy;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.SessionCallback;
import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
public class SynchronousTests extends AbstractTransactionalDataSourceSpringContextTests {
private JmsTemplate jmsTemplate;
private RepeatTemplate repeatTemplate;
public void setJmsTemplate(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
public void setRepeatTemplate(RepeatTemplate repeatTemplate) {
this.repeatTemplate = repeatTemplate;
}
protected String[] getConfigLocations() {
return new String[] { "/org/springframework/batch/jms/jms-context.xml" };
}
protected void onSetUpBeforeTransaction() throws Exception {
super.onSetUpBeforeTransaction();
String foo = "";
int count = 0;
while (foo != null && count < 100) {
foo = (String) jmsTemplate.receiveAndConvert("queue");
count++;
}
jdbcTemplate.execute("delete from T_FOOS");
jmsTemplate.convertAndSend("queue", "foo");
jmsTemplate.convertAndSend("queue", "bar");
}
protected void onSetUpInTransaction() throws Exception {
super.onSetUpInTransaction();
}
private void assertInitialState() {
int count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(0, count);
}
List list = new ArrayList();
public void testCommit() throws Exception {
assertInitialState();
repeatTemplate.iterate(new RepeatCallback() {
public ExitStatus doInIteration(RepeatContext context) throws Exception {
String text = (String) jmsTemplate.receiveAndConvert("queue");
list.add(text);
jdbcTemplate.update("INSERT into T_FOOS (id,name,foo_date) values (?,?,null)", new Object[] {
Integer.valueOf(list.size()), text });
return new ExitStatus(text != null);
}
});
// force commit...
setComplete();
endTransaction();
startNewTransaction();
System.err.println(jdbcTemplate.queryForList("select * from T_FOOS"));
// Database committed so this resord should be there...
int count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(2, count);
// ... the commit should also have cleared the queue, so this should now
// be null
String text = (String) jmsTemplate.receiveAndConvert("queue");
assertEquals(null, text);
}
public void testFullRollback() throws Exception {
assertInitialState();
repeatTemplate.iterate(new RepeatCallback() {
public ExitStatus doInIteration(RepeatContext context) throws Exception {
String text = (String) jmsTemplate.receiveAndConvert("queue");
list.add(text);
jdbcTemplate.update("INSERT into T_FOOS (id,name,foo_date) values (?,?,null)", new Object[] {
Integer.valueOf(list.size()), text });
return new ExitStatus(text != null);
}
});
// force rollback...
endTransaction();
startNewTransaction();
String text = "";
List msgs = new ArrayList();
while (text != null) {
text = (String) jmsTemplate.receiveAndConvert("queue");
msgs.add(text);
}
// The database portion rolled back...
int count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(0, count);
// ... and so did the message session. The rollback should have restored
// the queue, so this should now be non-null
assertTrue("Foo not on queue", msgs.contains("foo"));
}
public void testPartialRollback() throws Exception {
// The JmsTemplate is used elsewhere outside a transaction, so
// we need to use one here that is transaction aware.
final JmsTemplate jmsTemplate = new JmsTemplate((ConnectionFactory) applicationContext
.getBean("txAwareConnectionFactory"));
jmsTemplate.setReceiveTimeout(100L);
jmsTemplate.setSessionTransacted(true);
assertInitialState();
repeatTemplate.iterate(new RepeatCallback() {
public ExitStatus doInIteration(RepeatContext context) throws Exception {
String text = (String) jmsTemplate.receiveAndConvert("queue");
list.add(text);
jdbcTemplate.update("INSERT into T_FOOS (id,name,foo_date) values (?,?,null)", new Object[] {
Integer.valueOf(list.size()), text });
return new ExitStatus(text != null);
}
});
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
public void beforeCommit(boolean readOnly) {
// Simulate a message system failure before the main transaction
// commits...
jmsTemplate.execute(new SessionCallback() {
public Object doInJms(Session session) throws JMSException {
try {
assertTrue("Not a SessionProxy - wrong spring version?", session instanceof SessionProxy);
((SessionProxy) session).getTargetSession().rollback();
}
catch (JMSException e) {
throw e;
}
catch (Exception e) {
// swallow it
e.printStackTrace();
}
return null;
}
});
}
});
// force commit...
setComplete();
endTransaction();
startNewTransaction();
String text = "";
List msgs = new ArrayList();
while (text != null) {
text = (String) jmsTemplate.receiveAndConvert("queue");
msgs.add(text);
}
// The database portion committed...
int count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(2, count);
// ...but the JMS session rolled back, so the message is still there
assertTrue("Foo not on queue", msgs.contains("foo"));
assertTrue("Bar not on queue", msgs.contains("bar"));
}
}

View File

@@ -0,0 +1,233 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.retry.jms;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemProvider;
import org.springframework.batch.item.provider.AbstractItemProvider;
import org.springframework.batch.retry.callback.ItemProviderRetryCallback;
import org.springframework.batch.retry.policy.ItemProviderRetryPolicy;
import org.springframework.batch.retry.support.RetryTemplate;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
public class ExternalRetryTests extends AbstractDependencyInjectionSpringContextTests {
private JmsTemplate jmsTemplate;
private RetryTemplate retryTemplate;
private ItemProvider provider;
private JdbcTemplate jdbcTemplate;
private PlatformTransactionManager transactionManager;
public void setDataSource(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public void setJmsTemplate(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
protected String[] getConfigLocations() {
return new String[] { "/org/springframework/batch/jms/jms-context.xml" };
}
protected void onSetUp() throws Exception {
super.onSetUp();
getMessages(); // drain queue
jdbcTemplate.execute("delete from T_FOOS");
jmsTemplate.convertAndSend("queue", "foo");
provider = new AbstractItemProvider() {
public Object next() {
String text = (String) jmsTemplate.receiveAndConvert("queue");
list.add(text);
return text;
}
public boolean recover(Object data, Throwable cause) {
recovered.add(data);
return true;
}
};
retryTemplate = new RetryTemplate();
}
private void assertInitialState() {
int count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(0, count);
}
private List list = new ArrayList();
private List recovered = new ArrayList();
/**
* Message processing is successful on the second attempt but must receive
* the message again.
*
* @throws Exception
*/
public void testExternalRetrySuccessOnSecondAttempt() throws Exception {
assertInitialState();
retryTemplate.setRetryPolicy(new ItemProviderRetryPolicy());
final ItemProviderRetryCallback callback = new ItemProviderRetryCallback(provider, new ItemProcessor() {
public void process(final Object text) {
jdbcTemplate.update("INSERT into T_FOOS (id,name,foo_date) values (?,?,null)", new Object[] {
Integer.valueOf(list.size()), text });
if (list.size() == 1) {
throw new RuntimeException("Rollback!");
}
}
});
try {
new TransactionTemplate(transactionManager).execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
try {
return retryTemplate.execute(callback);
}
catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
});
fail("Expected Exception");
}
catch (Exception e) {
assertEquals("Rollback!", e.getMessage());
// Client of retry template has to take care of rollback. This would
// be a message listener container in the MDP case.
}
new TransactionTemplate(transactionManager).execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
try {
return retryTemplate.execute(callback);
}
catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
});
List msgs = getMessages();
// The database portion committed once...
int count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(1, count);
// ... and so did the message session.
assertEquals("[]", msgs.toString());
}
/**
* Message processing fails on both attempts.
*
* @throws Exception
*/
public void testExternalRetryWithRecovery() throws Exception {
assertInitialState();
retryTemplate.setRetryPolicy(new ItemProviderRetryPolicy());
final ItemProviderRetryCallback callback = new ItemProviderRetryCallback(provider, new ItemProcessor() {
public void process(final Object text) {
jdbcTemplate.update("INSERT into T_FOOS (id,name,foo_date) values (?,?,null)", new Object[] {
Integer.valueOf(list.size()), text });
throw new RuntimeException("Rollback!");
}
});
Object result = "start";
for (int i = 0; i < 4; i++) {
try {
result = new TransactionTemplate(transactionManager).execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
try {
return retryTemplate.execute(callback);
}
catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
});
}
catch (Exception e) {
if (i < 3)
assertEquals("Rollback!", e.getMessage());
// Client of retry template has to take care of rollback. This
// would
// be a message listener container in the MDP case.
}
}
// Last attempt should return last item.
assertEquals("foo", result);
List msgs = getMessages();
assertEquals(1, recovered.size());
// The database portion committed once...
int count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(0, count);
// ... and so did the message session.
assertEquals("[]", msgs.toString());
}
private List getMessages() {
String next = "";
List msgs = new ArrayList();
while (next != null) {
next = (String) jmsTemplate.receiveAndConvert("queue");
if (next != null)
msgs.add(next);
}
return msgs;
}
}

View File

@@ -0,0 +1,371 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.retry.jms;
import java.util.ArrayList;
import java.util.List;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.provider.JmsItemProvider;
import org.springframework.batch.retry.RetryCallback;
import org.springframework.batch.retry.RetryContext;
import org.springframework.batch.retry.callback.ItemProviderRetryCallback;
import org.springframework.batch.retry.support.RetryTemplate;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
public class SynchronousTests extends AbstractTransactionalDataSourceSpringContextTests {
private JmsTemplate jmsTemplate;
private RetryTemplate retryTemplate;
public void setJmsTemplate(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
protected String[] getConfigLocations() {
return new String[] { "/org/springframework/batch/jms/jms-context.xml" };
}
protected void onSetUpBeforeTransaction() throws Exception {
super.onSetUpBeforeTransaction();
String foo = "";
int count = 0;
while (foo != null && count < 100) {
foo = (String) jmsTemplate.receiveAndConvert("queue");
count++;
}
jdbcTemplate.execute("delete from T_FOOS");
jmsTemplate.convertAndSend("queue", "foo");
jmsTemplate.convertAndSend("queue", "foo");
final String text = (String) jmsTemplate.receiveAndConvert("queue");
assertNotNull(text);
retryTemplate = new RetryTemplate();
}
protected void onSetUpInTransaction() throws Exception {
super.onSetUpInTransaction();
}
private void assertInitialState() {
int count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(0, count);
}
List list = new ArrayList();
/**
* Message processing is successful on the second attempt without having to
* receive the message again.
*
* @throws Exception
*/
public void testInternalRetrySuccessOnSecondAttempt() throws Exception {
assertInitialState();
/*
* We either want the JMS receive to be outside a transaction, or we
* need the database transaction in the retry to be PROPAGATION_NESTED.
* Otherwise JMS will roll back when the retry callback is eventually
* successful because of the previous exception.
* PROPAGATION_REQUIRES_NEW is wrong because it doesn't allow the outer
* transaction to fail and rollback the inner one.
*/
final String text = (String) jmsTemplate.receiveAndConvert("queue");
assertNotNull(text);
retryTemplate.execute(new RetryCallback() {
public Object doWithRetry(RetryContext status) throws Throwable {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_NESTED);
return transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
list.add(text);
System.err.println("Inserting: [" + list.size() + "," + text + "]");
jdbcTemplate.update("INSERT into T_FOOS (id,name,foo_date) values (?,?,null)", new Object[] {
Integer.valueOf(list.size()), text });
if (list.size() == 1) {
throw new RuntimeException("Rollback!");
}
return text;
}
});
}
});
// force commit...
setComplete();
endTransaction();
startNewTransaction();
List msgs = getMessages();
// The database portion committed once...
int count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(1, count);
// ... and so did the message session.
assertEquals("[]", msgs.toString());
}
/**
* Message processing is successful on the second attempt without having to
* receive the message again - uses JmsItemProvider internally.
*
* @throws Exception
*/
public void testInternalRetrySuccessOnSecondAttemptWithItemProvider() throws Exception {
assertInitialState();
JmsItemProvider provider = new JmsItemProvider();
// provider.setItemType(Message.class);
provider.setJmsTemplate(jmsTemplate);
jmsTemplate.setDefaultDestinationName("queue");
retryTemplate.execute(new ItemProviderRetryCallback(provider, new ItemProcessor() {
public void process(final Object text) {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_NESTED);
transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
list.add(text);
System.err.println("Inserting: [" + list.size() + "," + text + "]");
jdbcTemplate.update("INSERT into T_FOOS (id,name,foo_date) values (?,?,null)", new Object[] {
Integer.valueOf(list.size()), text });
if (list.size() == 1) {
throw new RuntimeException("Rollback!");
}
return text;
}
});
}
}));
// force commit...
setComplete();
endTransaction();
startNewTransaction();
List msgs = getMessages();
// The database portion committed once...
int count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(1, count);
// ... and so did the message session.
assertEquals("[]", msgs.toString());
}
/**
* Message processing is successful on the second attempt without having to
* receive the message again.
*
* @throws Exception
*/
public void testInternalRetrySuccessOnFirstAttemptRollbackOuter() throws Exception {
assertInitialState();
/*
* We either want the JMS receive to be outside a transaction, or we
* need the database transaction in the retry to be PROPAGATION_NESTED.
* Otherwise JMS will roll back when the retry callback is eventually
* successful because of the previous exception.
* PROPAGATION_REQUIRES_NEW is wrong because it doesn't allow the outer
* transaction to fail and rollback the inner one.
*/
final String text = (String) jmsTemplate.receiveAndConvert("queue");
retryTemplate.execute(new RetryCallback() {
public Object doWithRetry(RetryContext status) throws Throwable {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_NESTED);
return transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
list.add(text);
System.err.println("Inserting: [" + list.size() + "," + text + "]");
jdbcTemplate.update("INSERT into T_FOOS (id,name,foo_date) values (?,?,null)", new Object[] {
Integer.valueOf(list.size()), text });
return text;
}
});
}
});
// The database transaction has committed...
int count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(1, count);
// force rollback...
endTransaction();
startNewTransaction();
List msgs = getMessages();
// The database portion rolled back...
count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(0, count);
// ... and so did the message session.
assertEquals("[foo]", msgs.toString());
}
/**
* Message processing is successful on the second attempt but must receive
* the message again.
*
* @throws Exception
*/
public void testExternalRetrySuccessOnSecondAttempt() throws Exception {
assertInitialState();
// force commit so that the retry executes in its own transaction (not
// nested)...
setComplete();
endTransaction();
retryTemplate.execute(new RetryCallback() {
public Object doWithRetry(RetryContext status) throws Throwable {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
return transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
// The receive is inside the retry and the
// transaction...
final String text = (String) jmsTemplate.receiveAndConvert("queue");
list.add(text);
jdbcTemplate.update("INSERT into T_FOOS (id,name,foo_date) values (?,?,null)", new Object[] {
Integer.valueOf(list.size()), text });
if (list.size() == 1) {
throw new RuntimeException("Rollback!");
}
return text;
}
});
}
});
startNewTransaction();
List msgs = getMessages();
// The database portion committed once...
int count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(1, count);
// ... and so did the message session.
assertEquals("[]", msgs.toString());
}
/**
* Message processing fails.
*
* @throws Exception
*/
public void testExternalRetryFailOnSecondAttempt() throws Exception {
assertInitialState();
// force commit so that the retry executes in its own transaction (not
// nested)...
setComplete();
endTransaction();
try {
retryTemplate.execute(new RetryCallback() {
public Object doWithRetry(RetryContext status) throws Throwable {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
return transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
// The receieve is inside the retry and the
// transaction...
final String text = (String) jmsTemplate.receiveAndConvert("queue");
list.add(text);
jdbcTemplate.update("INSERT into T_FOOS (id,name,foo_date) values (?,?,null)",
new Object[] { Integer.valueOf(list.size()), text });
throw new RuntimeException("Rollback!");
}
});
}
});
/*
* N.B. the message can be re-directed to an error queue by setting
* an error destination in a JmsItemProvider.
*/
fail("Expected RuntimeException");
}
catch (RuntimeException e) {
assertEquals("Rollback!", e.getMessage());
// expected
}
startNewTransaction();
List msgs = getMessages();
// The database portion rolled back...
int count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(0, count);
// ... and so did the message session.
assertTrue(msgs.contains("foo"));
}
private List getMessages() {
String next = "";
List msgs = new ArrayList();
while (next != null) {
next = (String) jmsTemplate.receiveAndConvert("queue");
if (next != null)
msgs.add(next);
}
return msgs;
}
}

View File

@@ -0,0 +1,363 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.retry.jms;
import java.util.ArrayList;
import java.util.List;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.provider.JmsItemProvider;
import org.springframework.batch.retry.RetryCallback;
import org.springframework.batch.retry.RetryContext;
import org.springframework.batch.retry.callback.ItemProviderRetryCallback;
import org.springframework.batch.retry.support.RetryTemplate;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
public class SynchronousTests extends AbstractTransactionalDataSourceSpringContextTests {
private JmsTemplate jmsTemplate;
private RetryTemplate retryTemplate;
public void setJmsTemplate(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
protected String[] getConfigLocations() {
return new String[] { "/org/springframework/batch/jms/jms-context.xml" };
}
protected void onSetUpBeforeTransaction() throws Exception {
super.onSetUpBeforeTransaction();
String foo = "";
int count = 0;
while (foo != null && count < 100) {
foo = (String) jmsTemplate.receiveAndConvert("queue");
count++;
}
jdbcTemplate.execute("delete from T_FOOS");
jmsTemplate.convertAndSend("queue", "foo");
retryTemplate = new RetryTemplate();
}
protected void onSetUpInTransaction() throws Exception {
super.onSetUpInTransaction();
}
private void assertInitialState() {
int count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(0, count);
}
List list = new ArrayList();
/**
* Message processing is successful on the second attempt without having to
* receive the message again.
*
* @throws Exception
*/
public void testInternalRetrySuccessOnSecondAttempt() throws Exception {
assertInitialState();
/*
* We either want the JMS receive to be outside a transaction, or we
* need the database transaction in the retry to be PROPAGATION_NESTED.
* Otherwise JMS will roll back when the retry callback is eventually
* successful because of the previous exception.
* PROPAGATION_REQUIRES_NEW is wrong because it doesn't allow the outer
* transaction to fail and rollback the inner one.
*/
final String text = (String) jmsTemplate.receiveAndConvert("queue");
retryTemplate.execute(new RetryCallback() {
public Object doWithRetry(RetryContext status) throws Throwable {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_NESTED);
return transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
list.add(text);
System.err.println("Inserting: [" + list.size() + "," + text + "]");
jdbcTemplate.update("INSERT into T_FOOS (id,name,foo_date) values (?,?,null)", new Object[] {
Integer.valueOf(list.size()), text });
if (list.size() == 1) {
throw new RuntimeException("Rollback!");
}
return text;
}
});
}
});
// force commit...
setComplete();
endTransaction();
startNewTransaction();
List msgs = getMessages();
// The database portion committed once...
int count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(1, count);
// ... and so did the message session.
assertEquals("[]", msgs.toString());
}
/**
* Message processing is successful on the second attempt without having to
* receive the message again - uses JmsItemProvider internally.
*
* @throws Exception
*/
public void testInternalRetrySuccessOnSecondAttemptWithItemProvider() throws Exception {
assertInitialState();
JmsItemProvider provider = new JmsItemProvider();
// provider.setItemType(Message.class);
provider.setJmsTemplate(jmsTemplate);
jmsTemplate.setDefaultDestinationName("queue");
retryTemplate.execute(new ItemProviderRetryCallback(provider, new ItemProcessor() {
public void process(final Object text) {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_NESTED);
transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
list.add(text);
System.err.println("Inserting: [" + list.size() + "," + text + "]");
jdbcTemplate.update("INSERT into T_FOOS (id,name,foo_date) values (?,?,null)", new Object[] {
Integer.valueOf(list.size()), text });
if (list.size() == 1) {
throw new RuntimeException("Rollback!");
}
return text;
}
});
}
}));
// force commit...
setComplete();
endTransaction();
startNewTransaction();
List msgs = getMessages();
// The database portion committed once...
int count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(1, count);
// ... and so did the message session.
assertEquals("[]", msgs.toString());
}
/**
* Message processing is successful on the second attempt without having to
* receive the message again.
*
* @throws Exception
*/
public void testInternalRetrySuccessOnFirstAttemptRollbackOuter() throws Exception {
assertInitialState();
/*
* We either want the JMS receive to be outside a transaction, or we
* need the database transaction in the retry to be PROPAGATION_NESTED.
* Otherwise JMS will roll back when the retry callback is eventually
* successful because of the previous exception.
* PROPAGATION_REQUIRES_NEW is wrong because it doesn't allow the outer
* transaction to fail and rollback the inner one.
*/
final String text = (String) jmsTemplate.receiveAndConvert("queue");
retryTemplate.execute(new RetryCallback() {
public Object doWithRetry(RetryContext status) throws Throwable {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_NESTED);
return transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
list.add(text);
System.err.println("Inserting: [" + list.size() + "," + text + "]");
jdbcTemplate.update("INSERT into T_FOOS (id,name,foo_date) values (?,?,null)", new Object[] {
Integer.valueOf(list.size()), text });
return text;
}
});
}
});
// The database transaction has committed...
int count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(1, count);
// force rollback...
endTransaction();
startNewTransaction();
List msgs = getMessages();
// The database portion rolled back...
count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(0, count);
// ... and so did the message session.
assertEquals("[foo]", msgs.toString());
}
/**
* Message processing is successful on the second attempt but must receive
* the message again.
*
* @throws Exception
*/
public void testExternalRetrySuccessOnSecondAttempt() throws Exception {
assertInitialState();
// force commit so that the retry executes in its own transaction (not
// nested)...
setComplete();
endTransaction();
retryTemplate.execute(new RetryCallback() {
public Object doWithRetry(RetryContext status) throws Throwable {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
return transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
// The receieve is inside the retry and the
// transaction...
final String text = (String) jmsTemplate.receiveAndConvert("queue");
list.add(text);
jdbcTemplate.update("INSERT into T_FOOS (id,name,foo_date) values (?,?,null)", new Object[] {
Integer.valueOf(list.size()), text });
if (list.size() == 1) {
throw new RuntimeException("Rollback!");
}
return text;
}
});
}
});
startNewTransaction();
List msgs = getMessages();
// The database portion committed once...
int count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(1, count);
// ... and so did the message session.
assertEquals("[]", msgs.toString());
}
/**
* Message processing fails.
*
* @throws Exception
*/
public void testExternalRetryFailOnSecondAttempt() throws Exception {
assertInitialState();
// force commit so that the retry executes in its own transaction (not
// nested)...
setComplete();
endTransaction();
try {
retryTemplate.execute(new RetryCallback() {
public Object doWithRetry(RetryContext status) throws Throwable {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
return transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
// The receieve is inside the retry and the
// transaction...
final String text = (String) jmsTemplate.receiveAndConvert("queue");
list.add(text);
jdbcTemplate.update("INSERT into T_FOOS (id,name,foo_date) values (?,?,null)",
new Object[] { Integer.valueOf(list.size()), text });
throw new RuntimeException("Rollback!");
}
});
}
});
fail("Expected RuntimeException");
}
catch (RuntimeException e) {
assertEquals("Rollback!", e.getMessage());
// expected
}
startNewTransaction();
List msgs = getMessages();
// The database portion rolled back...
int count = jdbcTemplate.queryForInt("select count(*) from T_FOOS");
assertEquals(0, count);
// ... and so did the message session.
assertTrue(msgs.contains("foo"));
}
private List getMessages() {
String next = "";
List msgs = new ArrayList();
while (next != null) {
next = (String) jmsTemplate.receiveAndConvert("queue");
if (next != null)
msgs.add(next);
}
return msgs;
}
}

View File

@@ -0,0 +1,38 @@
package test.jdbc.datasource;
import java.io.File;
import javax.sql.DataSource;
import org.apache.derby.jdbc.EmbeddedDataSource;
import org.springframework.beans.factory.config.AbstractFactoryBean;
public class DerbyDataSourceFactoryBean extends AbstractFactoryBean {
private String dataDirectory = "derby-home";
DataSource dataSource;
public void setDataDirectory(String dataDirectory) {
this.dataDirectory = dataDirectory;
}
protected Object createInstance() throws Exception {
File directory = new File(dataDirectory);
System.setProperty("derby.system.home", directory.getCanonicalPath());
System.setProperty("derby.storage.fileSyncTransactionLog", "true");
System.setProperty("derby.storage.pageCacheSize", "100");
final EmbeddedDataSource ds = new EmbeddedDataSource();
ds.setDatabaseName("derbydb");
ds.setCreateDatabase("create");
dataSource = ds;
return ds;
}
public Class getObjectType() {
return DataSource.class;
}
}

View File

@@ -0,0 +1,131 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package test.jdbc.datasource;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import javax.sql.DataSource;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
public class InitializingDataSourceFactoryBean extends AbstractFactoryBean {
private Resource initScript;
private Resource destroyScript;
DataSource dataSource;
public void destroy() throws Exception {
super.destroy();
try {
doExecuteScript(destroyScript);
}
catch (Exception e) {
logger.warn("Could not execute destroy script [" + destroyScript + "]", e);
}
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(dataSource);
super.afterPropertiesSet();
}
protected Object createInstance() throws Exception {
Assert.notNull(dataSource);
try {
doExecuteScript(destroyScript);
}
catch (Exception e) {
logger.debug("Could not execute destroy script [" + destroyScript + "]", e);
}
doExecuteScript(initScript);
return dataSource;
}
private void doExecuteScript(final Resource scriptResource) {
if (scriptResource == null || !scriptResource.exists())
return;
TransactionTemplate transactionTemplate = new TransactionTemplate(new DataSourceTransactionManager(dataSource));
if (initScript != null) {
transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
String[] scripts;
try {
scripts = StringUtils.delimitedListToStringArray(stripComments(IOUtils.readLines(scriptResource
.getInputStream())), ";");
}
catch (IOException e) {
throw new BeanInitializationException("Cannot load script from [" + initScript + "]", e);
}
for (int i = 0; i < scripts.length; i++) {
String script = scripts[i].trim();
if (StringUtils.hasText(script)) {
jdbcTemplate.execute(scripts[i]);
}
}
return null;
}
});
}
}
private String stripComments(List list) {
StringBuffer buffer = new StringBuffer();
for (Iterator iter = list.iterator(); iter.hasNext();) {
String line = (String) iter.next();
if (!line.startsWith("//") && !line.startsWith("--")) {
buffer.append(line + "\n");
}
}
return buffer.toString();
}
public Class getObjectType() {
return DataSource.class;
}
public void setInitScript(Resource initScript) {
this.initScript = initScript;
}
public void setDestroyScript(Resource destroyScript) {
this.destroyScript = destroyScript;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="dataSource" class="test.jdbc.datasource.InitializingDataSourceFactoryBean">
<property name="dataSource" ref="hsql" />
<property name="initScript" value="org/springframework/batch/jms/init.sql" />
<property name="destroyScript" value="org/springframework/batch/jms/destroy.sql" />
</bean>
<bean id="hsql" class="org.springframework.jdbc.datasource.DriverManagerDataSource" lazy-init="true"
autowire-candidate="false">
<property name="driverClassName" value="org.hsqldb.jdbcDriver" />
<property name="url" value="jdbc:hsqldb:mem:testdb" />
</bean>
<bean id="derby" class="test.jdbc.datasource.DerbyDataSourceFactoryBean" lazy-init="true" destroy-method="destroy"
autowire-candidate="false">
<property name="dataDirectory" value="derby-home" />
</bean>
<bean id="brokerService" class="org.apache.activemq.broker.BrokerService" init-method="start"
destroy-method="stop">
<property name="brokerName" value="broker" />
<property name="useJmx" value="false"/>
<property name="transportConnectorURIs">
<list>
<value>vm://localhost</value>
</list>
</property>
<property name="persistenceAdapter">
<bean class="org.apache.activemq.store.memory.MemoryPersistenceAdapter"/>
<!-- bean class="org.apache.activemq.store.jdbc.JDBCPersistenceAdapter">
<property name="dataSource" ref="dataSource"/>
<property name="createTablesOnStartup" value="true" />
</bean-->
</property>
</bean>
</beans>

View File

@@ -0,0 +1,8 @@
log4j.rootCategory=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
log4j.category.org.apache.activemq=ERROR
# log4j.category.org.springframework=DEBUG

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<root>
<trade>
<isin>isin1</isin>
<quantity>1</quantity>
<price>1</price>
<customer>customer1</customer>
</trade>
<trade>
<isin>isin2</isin>
<quantity>2</quantity>
<price>2</price>
<customer>customer2</customer>
</trade>
<trade>
<isin>isin3</isin>
<quantity>3</quantity>
<price>3</price>
<customer>customer3</customer>
</trade>
</root>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<records>
<trade>
<isin>XYZ0001</isin>
<quantity>5</quantity>
<price>11.39</price>
<customer>Customer1</customer>
</trade>
<trade>
<isin>XYZ0002</isin>
<quantity>2</quantity>
<price>72.99</price>
<customer>Customer2</customer>
</trade>
<trade>
<isin>XYZ0003</isin>
<quantity>9</quantity>
<price>99.99</price>
<customer>Customer3</customer>
</trade>
</records>

View File

@@ -0,0 +1,23 @@
<mapping>
<class name="org.springframework.batch.io.oxm.domain.Trade">
<map-to xml="trade" />
<field name="isin">
<bind-xml name="isin" node="element" />
</field>
<field name="quantity">
<bind-xml name="quantity" node="element" />
</field>
<field name="price">
<bind-xml name="price" node="element" />
</field>
<field name="customer">
<bind-xml name="customer" node="element" />
</field>
</class>
</mapping>

View File

@@ -0,0 +1 @@
DROP TABLE T_FOOS;

View File

@@ -0,0 +1,5 @@
create table T_FOOS (
id integer not null primary key,
name varchar(80),
foo_date timestamp
);

View File

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<import resource="classpath:/data-source.xml"/>
<!-- Transaction manager for a datasource -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- Transaction manager for jms -->
<bean id="jmsTransactionManager" autowire-candidate="false"
class="org.springframework.jms.connection.JmsTransactionManager">
<property name="connectionFactory" ref="connectionFactory" />
</bean>
<bean id="jmsTemplate"
class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="connectionFactory" />
<property name="receiveTimeout" value="100" />
<!-- This is important... -->
<property name="sessionTransacted" value="true" />
</bean>
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="connectionFactory"
class="org.apache.activemq.ActiveMQConnectionFactory" depends-on="brokerService">
<property name="brokerURL">
<value>vm://localhost</value>
</property>
</bean>
<bean id="txAwareConnectionFactory"
class="org.springframework.jms.connection.TransactionAwareConnectionFactoryProxy">
<property name="targetConnectionFactory" ref="connectionFactory"/>
<property name="synchedLocalTransactionAllowed" value="true" />
</bean>
<bean id="container"
class="org.springframework.batch.container.jms.BatchMessageListenerContainer">
<property name="transactionManager" ref="transactionManager" />
<property name="connectionFactory"
ref="txAwareConnectionFactory" />
<property name="destinationName" value="queue" />
<!-- This is important... it forces the container to acknowledge message receipt,
and avoid duplicate messages in the sunny day case -->
<property name="sessionTransacted" value="true" />
<constructor-arg ref="transactionalBatchTemplate" />
</bean>
<bean id="batchTemplate"
class="org.springframework.batch.repeat.support.RepeatTemplate">
<property name="completionPolicy">
<bean
class="org.springframework.batch.repeat.policy.SimpleCompletionPolicy">
<constructor-arg value="2" />
</bean>
</property>
</bean>
<bean id="transactionalBatchTemplate"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="transactionManager" />
<property name="proxyInterfaces">
<value>
org.springframework.batch.repeat.RepeatOperations
</value>
</property>
<property name="proxyTargetClass" value="false" />
<property name="transactionAttributes"
value="*=PROPAGATION_REQUIRED">
</property>
<property name="target">
<bean
class="org.springframework.batch.repeat.support.RepeatTemplate">
<property name="completionPolicy">
<bean
class="org.springframework.batch.repeat.policy.SimpleCompletionPolicy">
<constructor-arg value="2" />
</bean>
</property>
</bean>
</property>
</bean>
</beans>