OPEN - issue BATCH-777: Parametrise RetryCallback and related interfaces

Move key generator and item identifier to retry interceptor package
This commit is contained in:
dsyer
2008-08-25 16:08:19 +00:00
parent 6e36093b2e
commit d41464c176
19 changed files with 519 additions and 299 deletions

View File

@@ -28,7 +28,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemRecoverer;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.batch.repeat.RepeatCallback;
import org.springframework.batch.repeat.RepeatContext;
@@ -63,7 +62,7 @@ public class ExternalRetryInBatchTests {
@Autowired
private RepeatTemplate repeatTemplate;
private ItemReaderRecoverer provider;
private ItemReader<String> provider;
private SimpleJdbcTemplate jdbcTemplate;
@@ -81,17 +80,12 @@ public class ExternalRetryInBatchTests {
jdbcTemplate.getJdbcOperations().execute("delete from T_FOOS");
jmsTemplate.convertAndSend("queue", "foo");
jmsTemplate.convertAndSend("queue", "bar");
provider = new ItemReaderRecoverer() {
provider = new ItemReader<String>() {
public String read() {
String text = (String) jmsTemplate.receiveAndConvert("queue");
list.add(text);
return text;
}
public String recover(String data, Throwable cause) {
recovered.add(data);
return data;
}
};
retryTemplate = new RetryTemplate();
}
@@ -109,7 +103,7 @@ public class ExternalRetryInBatchTests {
private List<String> list = new ArrayList<String>();
private List<Object> recovered = new ArrayList<Object>();
private List<String> recovered = new ArrayList<String>();
@Test
public void testExternalRetryRecoveryInBatch() throws Exception {
@@ -153,7 +147,8 @@ public class ExternalRetryInBatchTests {
public String recover(RetryContext context) {
// aggressive commit on a recovery
RepeatSynchronizationManager.setCompleteOnly();
return provider.recover(item, context.getLastThrowable());
recovered.add(item);
return item;
}
};
@@ -212,7 +207,4 @@ public class ExternalRetryInBatchTests {
return msgs;
}
private interface ItemReaderRecoverer extends ItemReader<String>, ItemRecoverer<String,String> {
}
}

View File

@@ -29,7 +29,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemRecoverer;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.retry.RecoveryCallback;
import org.springframework.batch.retry.RetryCallback;
@@ -55,7 +54,7 @@ public class ExternalRetryTests {
private RetryTemplate retryTemplate;
private ItemReaderRecoverer<String> provider;
private ItemReader<String> provider;
private SimpleJdbcTemplate simpleJdbcTemplate;
@@ -72,17 +71,12 @@ public class ExternalRetryTests {
getMessages(); // drain queue
simpleJdbcTemplate.getJdbcOperations().execute("delete from T_FOOS");
jmsTemplate.convertAndSend("queue", "foo");
provider = new ItemReaderRecoverer<String>() {
provider = new ItemReader<String>() {
public String read() {
String text = (String) jmsTemplate.receiveAndConvert("queue");
list.add(text);
return text;
}
public String recover(String data, Throwable cause) {
recovered.add(data);
return data;
}
};
retryTemplate = new RetryTemplate();
}
@@ -196,7 +190,8 @@ public class ExternalRetryTests {
final RecoveryCallback<String> recoveryCallback = new RecoveryCallback<String>() {
public String recover(RetryContext context) {
return provider.recover(item, context.getLastThrowable());
recovered.add(item);
return item;
}
};
@@ -254,8 +249,4 @@ public class ExternalRetryTests {
return msgs;
}
private interface ItemReaderRecoverer<T> extends ItemReader<T>, ItemRecoverer<T,T> {
}
}

View File

@@ -16,27 +16,16 @@
package org.springframework.batch.item.jms;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.item.ItemKeyGenerator;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemRecoverer;
import org.springframework.batch.item.NewItemIdentifier;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.jms.JmsException;
import org.springframework.jms.core.JmsOperations;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.util.Assert;
/**
* An {@link ItemReader} for JMS using a {@link JmsTemplate}. The template
* should have a default destination, which will be used to provide items in
* {@link #read()}. If a recovery step is needed, set the error destination and
* the item will be sent there if processing fails in a stateful retry.
* {@link #read()}.<br/><br/>
*
* The implementation is thread safe after its properties are set (normal
* singleton behaviour).
@@ -44,60 +33,7 @@ import org.springframework.util.Assert;
* @author Dave Syer
*
*/
public class JmsItemReader<T> implements ItemReader<T>, ItemRecoverer<T,T>, ItemKeyGenerator,
NewItemIdentifier {
protected Log logger = LogFactory.getLog(getClass());
private JmsOperations jmsTemplate;
private Class<? extends T> itemType;
private String errorDestinationName;
private Destination errorDestination;
/**
* Set the error destination. Should not be the same as the default
* destination of the jms template.
* @param errorDestination a JMS Destination
*/
public void setErrorDestination(Destination errorDestination) {
this.errorDestination = errorDestination;
}
/**
* Set the error destination by name. Will be resolved by the destination
* resolver in the jms template.
*
* @param errorDestinationName the name of a JMS Destination
*/
public void setErrorDestinationName(String errorDestinationName) {
this.errorDestinationName = errorDestinationName;
}
/**
* Setter for jms template.
*
* @param jmsTemplate a {@link JmsOperations} instance
*/
public void setJmsTemplate(JmsOperations jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
/**
* Set the expected type of incoming message payloads. Set this to
* {@link Message} to receive the raw underlying message.
*
* @param itemType the java class of the items to be delivered. Typically
* the same as the class parameter
*
* @throws IllegalStateException if the message payload is of the wrong
* type.
*/
public void setItemType(Class<? extends T> itemType) {
this.itemType = itemType;
}
public class JmsItemReader<T> extends MessageTypeAccessor<T> implements ItemReader<T> {
@SuppressWarnings("unchecked")
public T read() {
@@ -112,72 +48,4 @@ public class JmsItemReader<T> implements ItemReader<T>, ItemRecoverer<T,T>, Item
return (T) result;
}
/**
* Send the message back to the provider using the specified error
* destination property of this reader. If the recovery is successful the
* item itself is returned, otherwise null.
*
* @see org.springframework.batch.item.ItemRecoverer#recover(Object,
* Throwable)
*/
public T recover(T item, Throwable cause) {
try {
if (errorDestination != null) {
jmsTemplate.convertAndSend(errorDestination, item);
}
else if (errorDestinationName != null) {
jmsTemplate.convertAndSend(errorDestinationName, item);
}
else {
// do nothing - it doesn't make sense to send the message back
// to the destination it came from
return null;
}
return item;
}
catch (JmsException e) {
logger.error("Could not recover because of JmsException.", e);
throw e;
}
}
/**
* If the message is a {@link Message} then returns the JMS message ID.
* Otherwise just delegate to parent class.
*
* @see org.springframework.batch.item.ItemKeyGenerator#getKey(java.lang.Object)
*
* @throws UnexpectedInputException if the JMS id cannot be determined from
* a JMS Message
*/
public Object getKey(Object item) {
if (itemType != null && itemType.isAssignableFrom(Message.class)) {
try {
return ((Message) item).getJMSMessageID();
}
catch (JMSException e) {
throw new UnexpectedInputException("Could not extract message ID", e);
}
}
return item;
}
/**
* If the item is a message, check the JMS re-delivered flag, otherwise
* return false to be on the safe side.
*
* @see org.springframework.batch.item.NewItemIdentifier#isNew(java.lang.Object)
*/
public boolean isNew(Object item) {
if (itemType != null && itemType.isAssignableFrom(Message.class)) {
try {
return !((Message) item).getJMSRedelivered();
}
catch (JMSException e) {
throw new UnexpectedInputException("Could not extract message ID", e);
}
}
return false;
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.item.jms;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.item.ItemWriter;
import org.springframework.jms.core.JmsOperations;
import org.springframework.jms.core.JmsTemplate;
/**
* An {@link ItemWriter} for JMS using a {@link JmsTemplate}. The template
* should have a default destination, which will be used to send items in
* {@link #write(List)}.<br/><br/>
*
* The implementation is thread safe after its properties are set (normal
* singleton behaviour).
*
* @author Dave Syer
*
*/
public class JmsItemWriter<T> implements ItemWriter<T> {
protected Log logger = LogFactory.getLog(getClass());
private JmsOperations jmsTemplate;
/**
* Setter for jms template.
*
* @param jmsTemplate a {@link JmsOperations} instance
*/
public void setJmsTemplate(JmsOperations jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
/**
* Send the items one-by-one to the default destination of the jms template.
*
* @see org.springframework.batch.item.ItemWriter#write(java.util.List)
*/
public void write(List<? extends T> items) throws Exception {
for (T item : items) {
jmsTemplate.convertAndSend(item);
}
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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.item.jms;
import javax.jms.JMSException;
import javax.jms.Message;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.batch.retry.interceptor.MethodArgumentsKeyGenerator;
/**
* A {@link MethodArgumentsKeyGenerator} for JMS
*
* @author Dave Syer
*
*/
public class JmsMethodArgumentsKeyGenerator implements MethodArgumentsKeyGenerator {
/**
* If the message is a {@link Message} then returns the JMS message ID.
* Otherwise just return the first argument.
*
* @see org.springframework.batch.retry.interceptor.MethodArgumentsKeyGenerator#getKey(Object[])
*
* @throws UnexpectedInputException if the JMS id cannot be determined from
* a JMS Message
* @throws IllegalArgumentException if the arguments are empty
*/
public Object getKey(Object[] items) {
for (Object item : items) {
if (item instanceof Message) {
try {
return ((Message) item).getJMSMessageID();
}
catch (JMSException e) {
throw new UnexpectedInputException("Could not extract message ID", e);
}
}
}
if (items.length == 0) {
throw new IllegalArgumentException(
"Method parameters are empty. The key generator cannot determine a unique key.");
}
return items[0];
}
}

View File

@@ -0,0 +1,63 @@
/*
* 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.item.jms;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.retry.interceptor.MethodInvocationRecoverer;
import org.springframework.jms.JmsException;
import org.springframework.jms.core.JmsOperations;
/**
* @author Dave Syer
*
*/
public class JmsMethodInvocationRecoverer<T> implements MethodInvocationRecoverer<T> {
protected Log logger = LogFactory.getLog(getClass());
private JmsOperations jmsTemplate;
/**
* Setter for jms template.
*
* @param jmsTemplate a {@link JmsOperations} instance
*/
public void setJmsTemplate(JmsOperations jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
/**
* Send one message per item in the arguments list using the default destination of
* the jms template. If the recovery is successful null is returned.
*
* @see org.springframework.batch.retry.interceptor.MethodInvocationRecoverer#recover(Object[],
* Throwable)
*/
public T recover(Object[] items, Throwable cause) {
try {
for (Object item : items) {
jmsTemplate.convertAndSend(item);
}
return null;
}
catch (JmsException e) {
logger.error("Could not recover because of JmsException.", e);
throw e;
}
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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.item.jms;
import javax.jms.JMSException;
import javax.jms.Message;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.batch.retry.interceptor.NewMethodArgumentsIdentifier;
/**
* A {@link NewMethodArgumentsIdentifier} for JMS that looks for a message in
* the arguments and checks its delivery status.
*
* @author Dave Syer
*
*/
public class JmsNewMethodArgumentsIdentifier<T> implements NewMethodArgumentsIdentifier {
/**
* If any of the arguments is a message, check the JMS re-delivered flag and
* return it, otherwise return false to be on the safe side.
*
* @see org.springframework.batch.retry.interceptor.NewMethodArgumentsIdentifier#isNew(java.lang.Object[])
*/
public boolean isNew(Object[] args) {
for (Object item : args) {
if (item instanceof Message) {
try {
return !((Message) item).getJMSRedelivered();
}
catch (JMSException e) {
throw new UnexpectedInputException("Could not extract message ID", e);
}
}
}
return false;
}
}

View File

@@ -0,0 +1,68 @@
/*
* 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.item.jms;
import javax.jms.Message;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.jms.core.JmsOperations;
/**
* Base class for JMS concerns.
*
* @author Dave Syer
*
*/
class MessageTypeAccessor<T> {
protected Log logger = LogFactory.getLog(getClass());
protected Class<? extends T> itemType;
protected JmsOperations jmsTemplate;
/**
* Setter for jms template.
*
* @param jmsTemplate a {@link JmsOperations} instance
*/
public void setJmsTemplate(JmsOperations jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
/**
* Set the expected type of incoming message payloads. Set this to
* {@link Message} to receive the raw underlying message.
*
* @param itemType the java class of the items to be delivered. Typically
* the same as the class parameter
*
* @throws IllegalStateException if the message payload is of the wrong
* type.
*/
public void setItemType(Class<? extends T> itemType) {
this.itemType = itemType;
}
public boolean isMessageType() {
return itemType != null && itemType.isAssignableFrom(Message.class);
}
}

View File

@@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.item;
package org.springframework.batch.retry.interceptor;
import org.springframework.batch.item.ItemReader;
/**
* Extension of the {@link ItemReader} interface that allows items to be
@@ -22,7 +24,7 @@ package org.springframework.batch.item;
* @author Dave Syer
*
*/
public interface ItemKeyGenerator {
public interface MethodArgumentsKeyGenerator {
/**
* Get a unique identifier for the item that can be used to cache it between
@@ -31,6 +33,6 @@ public interface ItemKeyGenerator {
* @param item the current item.
* @return a unique identifier.
*/
Object getKey(Object item);
Object getKey(Object[] item);
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.batch.item;
package org.springframework.batch.retry.interceptor;
/**
@@ -22,7 +22,7 @@ package org.springframework.batch.item;
*
* @author Dave Syer
*/
public interface ItemRecoverer<T,S> {
public interface MethodInvocationRecoverer<T> {
/**
* Recover gracefully from an error. Clients can call this if processing of
@@ -30,11 +30,11 @@ public interface ItemRecoverer<T,S> {
* to decide whether to try more corrective action or perhaps throw an
* exception.
*
* @param data
* the item that failed.
* @param args
* the arguments for the method invocation that failed.
* @param cause
* the cause of the failure that led to this recovery.
* @return the value to be returned to the caller
*/
T recover(S data, Throwable cause);
T recover(Object[] args, Throwable cause);
}

View File

@@ -14,24 +14,24 @@
* limitations under the License.
*/
package org.springframework.batch.item;
package org.springframework.batch.retry.interceptor;
/**
* Strategy interface to distinguish a new item from one that has been processed
* before and one that has not, e.g. by examining a message flag.
* Strategy interface to distinguish new arguments from ones that have been
* processed before, e.g. by examining a message flag.
*
* @author Dave Syer
*
*/
public interface NewItemIdentifier {
public interface NewMethodArgumentsIdentifier {
/**
* Inspect the item and determine if it has never been processed before.
* The safest choice when the answer is indeterminate is 'false'.
* Inspect the arguments and determine if they have never been processed
* before. The safest choice when the answer is indeterminate is 'false'.
*
* @param item the current item.
* @param args the current method arguments.
* @return true if the item is known to have never been processed before.
*/
boolean isNew(Object item);
boolean isNew(Object[] args);
}

View File

@@ -20,9 +20,6 @@ import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.item.ItemKeyGenerator;
import org.springframework.batch.item.ItemRecoverer;
import org.springframework.batch.item.NewItemIdentifier;
import org.springframework.batch.retry.ExhaustedRetryException;
import org.springframework.batch.retry.RecoveryCallback;
import org.springframework.batch.retry.RetryCallback;
@@ -40,8 +37,8 @@ import org.springframework.util.ObjectUtils;
* a method on a service if it fails. The argument to the service method is
* treated as an item to be remembered in case the call fails. So the retry
* operation is stateful, and the item that failed is tracked by its unique key
* (via {@link ItemKeyGenerator}) until the retry is exhausted, at which point
* the {@link ItemRecoverer} is called.<br/>
* (via {@link MethodArgumentsKeyGenerator}) until the retry is exhausted, at which point
* the {@link MethodInvocationRecoverer} is called.<br/>
*
* The main use case for this is where the service is transactional, via a
* transaction interceptor on the interceptor chain. In this case the retry (and
@@ -57,11 +54,11 @@ public class StatefulRetryOperationsInterceptor implements MethodInterceptor {
private transient Log logger = LogFactory.getLog(getClass());
private ItemKeyGenerator keyGenerator;
private MethodArgumentsKeyGenerator keyGenerator;
private ItemRecoverer<? extends Object,Object[]> recoverer;
private MethodInvocationRecoverer<? extends Object> recoverer;
private NewItemIdentifier newItemIdentifier;
private NewMethodArgumentsIdentifier newMethodArgumentsIdentifier;
private final RetryTemplate retryTemplate = new RetryTemplate();
@@ -74,7 +71,7 @@ public class StatefulRetryOperationsInterceptor implements MethodInterceptor {
}
/**
* Public setter for the {@link ItemRecoverer} to use if the retry is
* Public setter for the {@link MethodInvocationRecoverer} to use if the retry is
* exhausted. The recoverer should be able to return an object of the same
* type as the target object because its return value will be used to return
* to the caller in the case of a recovery.<br/>
@@ -82,13 +79,13 @@ public class StatefulRetryOperationsInterceptor implements MethodInterceptor {
* If no recoverer is set then an exhausted retry will result in an
* {@link ExhaustedRetryException}.
*
* @param recoverer the {@link ItemRecoverer} to set
* @param recoverer the {@link MethodInvocationRecoverer} to set
*/
public void setRecoverer(ItemRecoverer<? extends Object,Object[]> recoverer) {
public void setRecoverer(MethodInvocationRecoverer<? extends Object> recoverer) {
this.recoverer = recoverer;
}
public void setKeyGenerator(ItemKeyGenerator keyGenerator) {
public void setKeyGenerator(MethodArgumentsKeyGenerator keyGenerator) {
this.keyGenerator = keyGenerator;
}
@@ -103,29 +100,29 @@ public class StatefulRetryOperationsInterceptor implements MethodInterceptor {
}
/**
* Public setter for the {@link NewItemIdentifier}. Only set this if the
* Public setter for the {@link NewMethodArgumentsIdentifier}. Only set this if the
* arguments to the intercepted method can be inspected to find out if they
* have never been processed before.
* @param newItemIdentifier the {@link NewItemIdentifier} to set
* @param newMethodArgumentsIdentifier the {@link NewMethodArgumentsIdentifier} to set
*/
public void setNewItemIdentifier(NewItemIdentifier newItemIdentifier) {
this.newItemIdentifier = newItemIdentifier;
public void setNewItemIdentifier(NewMethodArgumentsIdentifier newMethodArgumentsIdentifier) {
this.newMethodArgumentsIdentifier = newMethodArgumentsIdentifier;
}
/**
* Wrap the method invocation in a stateful retry with the policy and other
* helpers provided. If there is a failure the exception will generally be
* re-thrown. The only time it is not re-thrown is when retry is exhausted
* and the recovery path is taken (though the {@link ItemRecoverer} provided
* and the recovery path is taken (though the {@link MethodInvocationRecoverer} provided
* if there is one). In that case the value returned from the method
* invocation will be the value returned by the recoverer (so the return
* type for that should be the same as the intercepted method).
*
* @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
* @see ItemRecoverer#recover(Object, Throwable)
* @see MethodInvocationRecoverer#recover(Object[], Throwable)
*
* @throws ExhaustedRetryException if the retry is exhausted and no
* {@link ItemRecoverer} is provided.
* {@link MethodInvocationRecoverer} is provided.
*/
public Object invoke(final MethodInvocation invocation) throws Throwable {
@@ -141,7 +138,7 @@ public class StatefulRetryOperationsInterceptor implements MethodInterceptor {
}
final Object item = arg;
RetryState retryState = new RetryState(keyGenerator != null ? keyGenerator.getKey(item) : item, newItemIdentifier != null ? newItemIdentifier.isNew(item) : false );
RetryState retryState = new RetryState(keyGenerator != null ? keyGenerator.getKey(args) : item, newMethodArgumentsIdentifier != null ? newMethodArgumentsIdentifier.isNew(args) : false );
Object result = retryTemplate.execute(new MethodInvocationRetryCallback(invocation), new ItemRecovererCallback(args, recoverer), retryState);
@@ -192,12 +189,12 @@ public class StatefulRetryOperationsInterceptor implements MethodInterceptor {
private final Object[] args;
private final ItemRecoverer<? extends Object,Object[]> recoverer;
private final MethodInvocationRecoverer<? extends Object> recoverer;
/**
* @param args the item that failed.
*/
private ItemRecovererCallback(Object[] args, ItemRecoverer<? extends Object,Object[]> recoverer) {
private ItemRecovererCallback(Object[] args, MethodInvocationRecoverer<? extends Object> recoverer) {
this.args = args;
this.recoverer = recoverer;
}

View File

@@ -16,19 +16,21 @@
package org.springframework.batch.item;
import org.springframework.batch.retry.interceptor.MethodInvocationRecoverer;
import junit.framework.TestCase;
public class ItemRecoveryHandlerTests extends TestCase {
ItemRecoverer<String, String> recoverer = new ItemRecoverer<String,String>() {
public String recover(String data, Throwable cause) {
MethodInvocationRecoverer<String> recoverer = new MethodInvocationRecoverer<String>() {
public String recover(Object[] data, Throwable cause) {
return null;
}
};
public void testRecover() throws Exception {
try {
recoverer.recover("foo", null);
recoverer.recover(new Object[]{"foo"}, null);
} catch (Exception e) {
fail("Unexpected Exception");
}

View File

@@ -23,7 +23,6 @@ import static org.junit.Assert.fail;
import java.util.Date;
import javax.jms.Message;
import javax.jms.Queue;
import org.easymock.EasyMock;
import org.junit.Test;
@@ -106,84 +105,4 @@ public class JmsItemReaderTests {
EasyMock.verify(jmsTemplate);
}
@Test
public void testRecoverWithNoDestination() throws Exception {
JmsOperations jmsTemplate = EasyMock.createMock(JmsOperations.class);
EasyMock.replay(jmsTemplate);
itemReader.setJmsTemplate(jmsTemplate);
itemReader.setItemType(String.class);
itemReader.recover("foo", null);
EasyMock.verify(jmsTemplate);
}
@Test
public void testErrorQueueWithDestinationName() throws Exception {
JmsOperations jmsTemplate = EasyMock.createMock(JmsOperations.class);
jmsTemplate.convertAndSend("queue", "foo");
EasyMock.expectLastCall();
EasyMock.replay(jmsTemplate);
itemReader.setJmsTemplate(jmsTemplate);
itemReader.setItemType(String.class);
itemReader.setErrorDestinationName("queue");
itemReader.recover("foo", null);
EasyMock.verify(jmsTemplate);
}
@Test
public void testErrorQueueWithDestination() throws Exception {
JmsOperations jmsTemplate = EasyMock.createMock(JmsOperations.class);
Queue queue = EasyMock.createMock(Queue.class);
jmsTemplate.convertAndSend(queue, "foo");
EasyMock.expectLastCall();
EasyMock.replay(jmsTemplate, queue);
itemReader.setJmsTemplate(jmsTemplate);
itemReader.setItemType(String.class);
itemReader.setErrorDestination(queue);
itemReader.recover("foo", null);
EasyMock.verify(jmsTemplate, queue);
}
@Test
public void testGetKeyFromMessage() throws Exception {
Message message = EasyMock.createMock(Message.class);
EasyMock.expect(message.getJMSMessageID()).andReturn("foo");
EasyMock.replay(message);
JmsItemReader<Message> itemReader = new JmsItemReader<Message>();
itemReader.setItemType(Message.class);
assertEquals("foo", itemReader.getKey(message));
EasyMock.verify(message);
}
@Test
public void testGetKeyFromNonMessage() throws Exception {
itemReader.setItemType(String.class);
assertEquals("foo", itemReader.getKey("foo"));
}
@Test
public void testIsNewForMessage() throws Exception {
Message message = EasyMock.createMock(Message.class);
EasyMock.expect(message.getJMSRedelivered()).andReturn(true);
EasyMock.replay(message);
JmsItemReader<Message> itemReader = new JmsItemReader<Message>();
itemReader.setItemType(Message.class);
assertEquals(false, itemReader.isNew(message));
EasyMock.verify(message);
}
@Test
public void testIsNewForNonMessage() throws Exception {
itemReader.setItemType(String.class);
assertEquals(false, itemReader.isNew("foo"));
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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.item.jms;
import static org.junit.Assert.assertEquals;
import javax.jms.Message;
import org.easymock.EasyMock;
import org.junit.Test;
/**
* @author Dave Syer
*
*/
public class JmsMethodArgumentsKeyGeneratorTests {
private JmsMethodArgumentsKeyGenerator methodArgumentsKeyGenerator = new JmsMethodArgumentsKeyGenerator();
@Test
public void testGetKeyFromMessage() throws Exception {
Message message = EasyMock.createMock(Message.class);
EasyMock.expect(message.getJMSMessageID()).andReturn("foo");
EasyMock.replay(message);
JmsItemReader<Message> itemReader = new JmsItemReader<Message>();
itemReader.setItemType(Message.class);
assertEquals("foo", methodArgumentsKeyGenerator.getKey(new Object[]{message}));
EasyMock.verify(message);
}
@Test
public void testGetKeyFromNonMessage() throws Exception {
assertEquals("foo", methodArgumentsKeyGenerator.getKey(new Object[]{"foo"}));
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.item.jms;
import org.easymock.EasyMock;
import org.junit.Test;
import org.springframework.jms.core.JmsOperations;
/**
* @author Dave Syer
*
*/
public class JmsMethodInvocationRecovererTests {
private JmsMethodInvocationRecoverer<String> itemReader = new JmsMethodInvocationRecoverer<String>();
@Test
public void testRecoverWithNoDestination() throws Exception {
JmsOperations jmsTemplate = EasyMock.createMock(JmsOperations.class);
jmsTemplate.convertAndSend("foo");
EasyMock.replay(jmsTemplate);
itemReader.setJmsTemplate(jmsTemplate);
itemReader.recover(new Object[] { "foo" }, null);
EasyMock.verify(jmsTemplate);
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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.item.jms;
import static org.junit.Assert.assertEquals;
import javax.jms.Message;
import org.easymock.EasyMock;
import org.junit.Test;
/**
* @author Dave Syer
*
*/
public class JmsNewMethodArgumentsIdentifierTests {
private JmsNewMethodArgumentsIdentifier<String> newMethodArgumentsIdentifier = new JmsNewMethodArgumentsIdentifier<String>();
@Test
public void testIsNewForMessage() throws Exception {
Message message = EasyMock.createMock(Message.class);
EasyMock.expect(message.getJMSRedelivered()).andReturn(true);
EasyMock.replay(message);
assertEquals(false, newMethodArgumentsIdentifier.isNew(new Object[]{message}));
EasyMock.verify(message);
}
@Test
public void testIsNewForNonMessage() throws Exception {
assertEquals(false, newMethodArgumentsIdentifier.isNew(new Object[]{"foo"}));
}
}

View File

@@ -27,7 +27,6 @@ import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.target.SingletonTargetSource;
import org.springframework.batch.item.ItemRecoverer;
import org.springframework.batch.retry.ExhaustedRetryException;
import org.springframework.batch.retry.policy.AlwaysRetryPolicy;
import org.springframework.batch.retry.policy.NeverRetryPolicy;
@@ -170,7 +169,7 @@ public class StatefulRetryOperationsInterceptorTests extends TestCase {
assertTrue("Wrong message: " + message, message.startsWith("Not enough calls"));
}
assertEquals(1, count);
interceptor.setRecoverer(new ItemRecoverer<Object, Object[]>() {
interceptor.setRecoverer(new MethodInvocationRecoverer<Object>() {
public Object recover(Object[] data, Throwable cause) {
count++;
return null;
@@ -192,7 +191,7 @@ public class StatefulRetryOperationsInterceptorTests extends TestCase {
assertTrue("Wrong message: " + message, message.startsWith("Not enough calls"));
}
assertEquals(1, count);
interceptor.setRecoverer(new ItemRecoverer<Collection<String>, Object[]>() {
interceptor.setRecoverer(new MethodInvocationRecoverer<Collection<String>>() {
public Collection<String> recover(Object[] data, Throwable cause) {
count++;
return Collections.singleton((String)data[0]);

View File

@@ -15,10 +15,10 @@ import org.junit.Test;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.NameMatchMethodPointcut;
import org.springframework.batch.item.ItemKeyGenerator;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemRecoverer;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.batch.retry.interceptor.MethodArgumentsKeyGenerator;
import org.springframework.batch.retry.interceptor.MethodInvocationRecoverer;
import org.springframework.batch.retry.interceptor.StatefulRetryOperationsInterceptor;
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
import org.springframework.batch.support.transaction.TransactionAwareProxyFactory;
@@ -51,16 +51,13 @@ public class PollableSourceRetryTests {
processed.add(str);
}
ItemKeyGenerator itemKeyGenerator = new ItemKeyGenerator() {
MethodArgumentsKeyGenerator methodArgumentsKeyGenerator = new MethodArgumentsKeyGenerator() {
@SuppressWarnings("unchecked")
public Object getKey(Object item) {
public Object getKey(Object[] item) {
if (item == null) {
return "NULL";
}
if (item.getClass().isArray()) {
item = ((Object[]) item)[0];
}
return ((Message<Object>) item).getPayload();
return ((Message<Object>) item[0]).getPayload();
}
};
@@ -266,7 +263,7 @@ public class PollableSourceRetryTests {
MessageTarget target = getChannel(handler);
// this was the old dispatch advice chain
target = (MessageTarget) getProxy(target, MessageTarget.class,
new Advice[] { getRetryOperationsInterceptor(itemKeyGenerator) }, "send");
new Advice[] { getRetryOperationsInterceptor(methodArgumentsKeyGenerator) }, "send");
PollingDispatcher trigger = getPollingDispatcher(source, target, transactionManager, 1);
TaskScheduler scheduler = getSchedulerWithErrorHandler(trigger);
@@ -309,7 +306,7 @@ public class PollableSourceRetryTests {
MessageTarget target = getChannel(handler);
// this was the old dispatch advice chain
target = (MessageTarget) getProxy(target, MessageTarget.class,
new Advice[] { getRetryOperationsInterceptor(itemKeyGenerator) }, "send");
new Advice[] { getRetryOperationsInterceptor(methodArgumentsKeyGenerator) }, "send");
PollingDispatcher trigger = getPollingDispatcher(source, target, transactionManager, 3);
TaskScheduler scheduler = getSchedulerWithErrorHandler(trigger);
@@ -398,12 +395,12 @@ public class PollableSourceRetryTests {
}
/**
* @param itemKeyGenerator
* @param methodArgumentsKeyGenerator
* @return
*/
private StatefulRetryOperationsInterceptor getRetryOperationsInterceptor(ItemKeyGenerator itemKeyGenerator) {
private StatefulRetryOperationsInterceptor getRetryOperationsInterceptor(MethodArgumentsKeyGenerator methodArgumentsKeyGenerator) {
StatefulRetryOperationsInterceptor advice = new StatefulRetryOperationsInterceptor();
advice.setRecoverer(new ItemRecoverer<Boolean, Object[]>() {
advice.setRecoverer(new MethodInvocationRecoverer<Boolean>() {
@SuppressWarnings("unchecked")
public Boolean recover(Object[] data, Throwable cause) {
if (data == null) {
@@ -415,7 +412,7 @@ public class PollableSourceRetryTests {
return true;
}
});
advice.setKeyGenerator(itemKeyGenerator);
advice.setKeyGenerator(methodArgumentsKeyGenerator);
return advice;
}