OPEN - issue BATCH-753: Listener exception handling

Add test for SkipListenerFailedException
This commit is contained in:
dsyer
2008-08-06 10:44:13 +00:00
parent 05d7b6f036
commit c230e9e0d3
11 changed files with 214 additions and 773 deletions

View File

@@ -16,15 +16,19 @@
package org.springframework.batch.core;
import junit.framework.TestCase;
import static org.junit.Assert.assertEquals;
public abstract class AbstractExceptionTests extends TestCase {
import org.junit.Test;
public abstract class AbstractExceptionTests {
@Test
public void testExceptionString() throws Exception {
Exception exception = getException("foo");
assertEquals("foo", exception.getMessage());
}
@Test
public void testExceptionStringThrowable() throws Exception {
Exception exception = getException("foo", new IllegalStateException());
assertEquals("foo", exception.getMessage().substring(0, 3));

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.core.listener;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
/**
* @author Dave Syer
*
*/
public abstract class AbstractDoubleExceptionTests {
@Test
public void testExceptionStringThrowable() throws Exception {
Exception exception = getException("foo", new IllegalStateException(), new RuntimeException("bar"));
assertEquals("foo", exception.getMessage().substring(0, 3));
}
public abstract Exception getException(String msg, RuntimeException cause, Throwable e) throws Exception;
}

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.core.listener;
import org.springframework.batch.core.step.skip.SkipListenerFailedException;
/**
* @author Dave Syer
*
*/
public class StepListenerFailedExceptionTests extends AbstractDoubleExceptionTests {
/* (non-Javadoc)
* @see org.springframework.batch.core.listener.AbstractDoubleExceptionTests#getException(java.lang.String, java.lang.Throwable, java.lang.Throwable)
*/
@Override
public Exception getException(String msg, RuntimeException cause, Throwable e) throws Exception {
return new SkipListenerFailedException(msg, cause, e);
}
}

View File

@@ -1,367 +0,0 @@
/*
* 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.core.step.item;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import junit.framework.TestCase;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.SkipListener;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.UnexpectedJobExecutionException;
import org.springframework.batch.core.listener.SkipListenerSupport;
import org.springframework.batch.core.step.StepSupport;
import org.springframework.batch.core.step.skip.AlwaysSkipItemSkipPolicy;
import org.springframework.batch.item.ClearFailedException;
import org.springframework.batch.item.FlushFailedException;
import org.springframework.batch.item.ItemKeyGenerator;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.MarkFailedException;
import org.springframework.batch.item.NoWorkFoundException;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.ResetFailedException;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/**
* @author Dave Syer
*
*/
public class ItemSkipPolicyItemHandlerTests extends TestCase {
private final SkipWriterStub writer = new SkipWriterStub();
private ItemSkipPolicyItemHandler<Holder> handler = new ItemSkipPolicyItemHandler<Holder>(new SkipReaderStub(), writer);
private StepContribution contribution = new StepContribution(new JobExecution(new JobInstance(new Long(11),
new JobParameters(), "jobName")).createStepExecution(new StepSupport("foo")));
protected void tearDown() throws Exception {
// remove the resource if it exists
handler.mark();
}
public void testReadWithNoSkip() throws Exception {
assertEquals(new Holder("1"), handler.read(contribution));
try {
handler.read(contribution);
fail("Expected SkippableException");
}
catch (SkippableException e) {
// expected
}
assertEquals(0, contribution.getSkipCount());
assertEquals(new Holder("3"), handler.read(contribution));
}
public void testReadWithSkip() throws Exception {
handler.setItemSkipPolicy(new AlwaysSkipItemSkipPolicy());
assertEquals(new Holder("1"), handler.read(contribution));
assertEquals(new Holder("3"), handler.read(contribution));
contribution.combineSkipCounts();
assertEquals(1, contribution.getReadSkipCount());
assertEquals(new Holder("4"), handler.read(contribution));
}
public void testWriteWithNoSkip() throws Exception {
handler.write(new Holder("3"), contribution);
try {
handler.write(new Holder("4"), contribution);
fail("Expected SkippableException");
}
catch (SkippableException e) {
// expected
}
assertEquals(0, contribution.getSkipCount());
}
public void testHandleWithSkip() throws Exception {
handler.setItemSkipPolicy(new AlwaysSkipItemSkipPolicy());
handler.handle(contribution);
handler.handle(contribution);
contribution.combineSkipCounts();
assertEquals(1, contribution.getSkipCount());
// 2 is skipped so 3 was last one processed and now we are at 4
try {
handler.handle(contribution);
fail("Expected SkippableException");
}
catch (SkippableException e) {
// expected
}
assertEquals(3, contribution.getItemCount());
assertEquals(2, contribution.getSkipCount());
// No "4" because it was skipped on write
assertEquals(new Holder("5"), handler.read(contribution));
}
@SuppressWarnings("unchecked")
public void testWriteWithSkipAfterMark() throws Exception {
handler.setItemSkipPolicy(new AlwaysSkipItemSkipPolicy());
try {
handler.write(new Holder("4"), contribution);
fail("Expected SkippableException");
}
catch (SkippableException e) {
// expected
}
handler.handle(contribution);
handler.handle(contribution);
contribution.combineSkipCounts();
assertEquals(2, contribution.getSkipCount());
// 2 is skipped so 3 was last one processed and now we are at 4, which
// was previously skipped
handler.handle(contribution);
assertEquals(null, handler.read(contribution));
assertEquals(3, contribution.getItemCount());
assertEquals(2, contribution.getSkipCount());
assertEquals(1, TransactionSynchronizationManager.getResourceMap().size());
Set<Object> removed = (Set<Object>) TransactionSynchronizationManager.getResourceMap().values().iterator().next();
// one skipped item was detected on read
assertEquals(1, removed.size());
// mark() should remove the set of removed keys
handler.mark();
assertEquals(0, TransactionSynchronizationManager.getResourceMap().size());
}
public void testWriteWithSkipAndItemKeyGenerator() throws Exception {
handler.setItemSkipPolicy(new AlwaysSkipItemSkipPolicy());
handler.setItemKeyGenerator(new ItemKeyGenerator() {
public Object getKey(Object item) {
return ((Holder) item).value;
}
});
handler.write(new Holder("3"), contribution);
try {
handler.write(new Holder("4"), contribution);
fail("Expected SkippableException");
}
catch (SkippableException e) {
// expected
}
assertEquals(1, contribution.getSkipCount());
assertEquals(new Holder("1"), handler.read(contribution));
assertEquals(new Holder("3"), handler.read(contribution));
contribution.combineSkipCounts();
assertEquals(2, contribution.getSkipCount());
// No "4" because it was skipped on write
assertEquals(new Holder("5"), handler.read(contribution));
}
public void testWriteWithSkipWhenMutating() throws Exception {
handler.setItemSkipPolicy(new AlwaysSkipItemSkipPolicy());
writer.mutate = true;
handler.setItemSkipPolicy(new AlwaysSkipItemSkipPolicy());
handler.handle(contribution);
handler.handle(contribution);
contribution.combineSkipCounts();
assertEquals(1, contribution.getSkipCount());
// 2 is skipped so 3 was last one processed and now we are at 4
try {
handler.handle(contribution);
fail("Expected SkippableException");
}
catch (SkippableException e) {
// expected
}
assertEquals(3, contribution.getItemCount());
assertEquals(2, contribution.getSkipCount());
// No "4" because it was skipped on write, even though it is mutating
// its key
assertEquals(new Holder("5"), handler.read(contribution));
}
public void testWriteWithSkipCapacityBreached() throws Exception {
handler.setItemSkipPolicy(new AlwaysSkipItemSkipPolicy());
handler.setSkipCacheCapacity(0);
handler.handle(contribution);
handler.handle(contribution);
contribution.combineSkipCounts();
assertEquals(1, contribution.getSkipCount());
// 2 is skipped so 3 was last one processed and now we are at 4
try {
handler.handle(contribution);
fail("Expected UnexpectedJobExecutionException");
}
catch (UnexpectedJobExecutionException e) {
// expected
String message = e.getMessage();
assertTrue("Message does not contain 'capacity': " + message, message.indexOf("capacity") >= 0);
}
assertEquals(2, contribution.getSkipCount());
// No "4" because it was skipped on write, even though it is mutating
// its key
assertEquals(new Holder("5"), handler.read(contribution));
}
/**
* Skippable write exceptions are not re-thrown when included in the
* {@link ItemSkipPolicyItemHandler#setDoNotRethrowExceptionClasses(Class[])}
*/
public void testWriteWithSkipAndDoNotRethrow() throws Exception {
handler.setItemSkipPolicy(new AlwaysSkipItemSkipPolicy());
handler.setDoNotRethrowExceptionClasses(new Class[] { SkippableException.class });
handler.handle(contribution);
handler.handle(contribution);
contribution.combineSkipCounts();
assertEquals(1, contribution.getSkipCount());
// skippable exception thrown in writer at this point, but it won't be
// re-thrown
handler.handle(contribution);
assertEquals(3, contribution.getItemCount());
assertEquals(2, contribution.getSkipCount());
// No "4" because it was skipped on write, even though it is mutating
// its key
assertEquals(new Holder("5"), handler.read(contribution));
}
/**
* {@link SkipListener#onSkipInWrite(Object, Throwable)} should be called
* also if the exception is not re-thrown (does not cause rollback).
*/
public void testHandleWithSkipAndListeners() throws Exception {
class SkipOnWriteListener extends SkipListenerSupport {
boolean called = false;
public void onSkipInWrite(Object item, Throwable t) {
called = true;
}
}
;
SkipOnWriteListener skipOnWriteListener = new SkipOnWriteListener();
handler.setSkipListeners(new SkipListener[] { skipOnWriteListener });
handler.setDoNotRethrowExceptionClasses(new Class[] { SkippableException.class });
handler.setItemSkipPolicy(new AlwaysSkipItemSkipPolicy());
for (int i = 0; i < 5; i++) {
handler.handle(contribution);
}
assertTrue("onSkipInWrite should be called although the exception is not re-thrown", skipOnWriteListener.called);
}
/**
* Simple item reader that supports skip functionality.
*/
private static class SkipReaderStub implements ItemReader<Holder> {
final String[] values = { "1", "2", "3", "4", "5" };
Collection<Object> processed = new ArrayList<Object>();
int counter = -1;
int marked = 0;
public Holder read() throws Exception, UnexpectedInputException, NoWorkFoundException, ParseException {
counter++;
if (counter == 1) {
throw new SkippableException("exception in reader");
}
if (counter >= values.length) {
return null;
}
Holder item = new Holder(values[counter]);
processed.add(item);
return item;
}
public void mark() throws MarkFailedException {
marked = counter;
}
public void reset() throws ResetFailedException {
counter = marked;
}
}
/**
* Simple item writer that supports skip functionality.
*/
private static class SkipWriterStub implements ItemWriter<Holder> {
boolean mutate = false;
List<Object> written = new ArrayList<Object>();
int flushIndex = -1;
public void clear() throws ClearFailedException {
for (int i = flushIndex + 1; i < written.size(); i++) {
written.remove(i);
}
}
public void flush() throws FlushFailedException {
flushIndex = written.size() - 1;
}
public void write(Holder item) throws Exception {
String value = item.value;
written.add(item);
if (mutate) {
item.value = "done";
}
if (value.equals("4")) {
throw new SkippableException("exception in writer");
}
}
}
private static class SkippableException extends Exception {
public SkippableException(String message) {
super(message);
}
}
private static class Holder {
private String value = null;
public Holder(String value) {
super();
this.value = value;
}
public boolean equals(Object obj) {
return obj instanceof Holder && value.equals(((Holder) obj).value);
}
public int hashCode() {
return value.hashCode();
}
public String toString() {
return "[holder:" + value + "]";
}
}
}

View File

@@ -15,27 +15,31 @@
*/
package org.springframework.batch.core.step.item;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import junit.framework.TestCase;
import org.junit.Before;
import org.junit.Test;
import org.springframework.batch.core.step.skip.LimitCheckingItemSkipPolicy;
import org.springframework.batch.core.step.skip.SkipLimitExceededException;
import org.springframework.batch.item.file.FlatFileParseException;
/**
* @author Lucas Ward
* @author Dave Syer
*
*/
public class SkipLimitReadFailurePolicyTests extends TestCase {
public class LimitCheckingItemSkipPolicyTests {
private LimitCheckingItemSkipPolicy failurePolicy;
protected void setUp() throws Exception {
super.setUp();
@Before
public void setUp() throws Exception {
List<Class<?>> skippableExceptions = new ArrayList<Class<?>>();
skippableExceptions.add(FlatFileParseException.class);
List<Class<?>> fatalExceptions = new ArrayList<Class<?>>();
@@ -43,6 +47,7 @@ public class SkipLimitReadFailurePolicyTests extends TestCase {
failurePolicy = new LimitCheckingItemSkipPolicy(1, skippableExceptions, fatalExceptions);
}
@Test
public void testLimitExceed(){
try{
failurePolicy.shouldSkip(new FlatFileParseException("", ""), 2);
@@ -53,10 +58,12 @@ public class SkipLimitReadFailurePolicyTests extends TestCase {
}
}
@Test
public void testNonSkippableException(){
assertFalse(failurePolicy.shouldSkip(new FileNotFoundException(), 2));
}
@Test
public void testSkip(){
assertTrue(failurePolicy.shouldSkip(new FlatFileParseException("",""), 0));
}

View File

@@ -1,67 +0,0 @@
/*
* 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.core.step.item;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.MarkFailedException;
import org.springframework.batch.item.ResetFailedException;
public class MockItemReader implements ItemReader<String> {
private final int returnItemCount;
private int returnedItemCount;
private boolean fail = false;
public MockItemReader() {
this(-1);
}
public MockItemReader(int returnItemCount) {
this.returnItemCount = returnItemCount;
}
public void setFail(boolean fail) {
this.fail = fail;
}
public void close() {
}
public String read() {
if(fail) {
fail = false;
throw new RuntimeException();
}
if (returnItemCount < 0 || returnedItemCount < returnItemCount) {
return String.valueOf(returnedItemCount++);
}
return null;
}
public Object getKey(Object item) {
return null;
}
public void mark() throws MarkFailedException {
}
public void reset() throws ResetFailedException {
}
}

View File

@@ -14,9 +14,12 @@ import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepListener;
import org.springframework.batch.core.listener.SkipListenerSupport;
import org.springframework.batch.core.step.AbstractStep;
import org.springframework.batch.core.step.JobRepositorySupport;
import org.springframework.batch.core.step.skip.SkipLimitExceededException;
import org.springframework.batch.core.step.skip.SkipListenerFailedException;
import org.springframework.batch.item.ClearFailedException;
import org.springframework.batch.item.FlushFailedException;
import org.springframework.batch.item.ItemReader;
@@ -91,7 +94,7 @@ public class SkipLimitStepFactoryBeanTests extends TestCase {
List<String> expectedOutput = Arrays.asList(StringUtils.commaDelimitedListToStringArray("1,3,5"));
assertEquals(expectedOutput, writer.written);
assertEquals(4, stepExecution.getItemCount().intValue());
}
@@ -117,7 +120,7 @@ public class SkipLimitStepFactoryBeanTests extends TestCase {
// no rollbacks
assertEquals(0, stepExecution.getRollbackCount().intValue());
assertEquals(4, stepExecution.getItemCount().intValue());
}
@@ -208,14 +211,90 @@ public class SkipLimitStepFactoryBeanTests extends TestCase {
assertFalse(reader.processed.contains("2"));
assertTrue(reader.processed.contains("4"));
// failure on "5" tripped the skip limit but "4" failed on write and was skipped and
// RepeatSynchronizationManager.setCompleteOnly() was called in the retry policy to
// failure on "5" tripped the skip limit but "4" failed on write and was
// skipped and
// RepeatSynchronizationManager.setCompleteOnly() was called in the
// retry policy to
// aggressively commit after a recovery ("1" was written at that point)
List<String> expectedOutput = Arrays.asList(StringUtils.commaDelimitedListToStringArray("1"));
assertEquals(expectedOutput, writer.written);
}
/**
* Check items causing errors are skipped as expected.
*/
@SuppressWarnings("unchecked")
public void testSkipListenerFailsOnRead() throws Exception {
reader = new SkipReaderStub(StringUtils.commaDelimitedListToStringArray("1,2,3,4,5,6"), StringUtils
.commaDelimitedListToSet("2,3,5"));
factory.setSkipLimit(3);
factory.setItemReader(reader);
factory.setListeners(new StepListener[] { new SkipListenerSupport() {
@Override
public void onSkipInRead(Throwable t) {
throw new RuntimeException("oops");
}
} });
factory.setSkippableExceptionClasses(new Class[] { Exception.class });
AbstractStep step = (AbstractStep) factory.getObject();
StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
try {
step.execute(stepExecution);
fail("Expected SkipListenerFailedException.");
}
catch (SkipListenerFailedException e) {
assertEquals("oops", e.getCause().getMessage());
}
assertEquals(1, stepExecution.getSkipCount());
assertEquals(1, stepExecution.getReadSkipCount().intValue());
assertEquals(0, stepExecution.getWriteSkipCount().intValue());
}
/**
* Check items causing errors are skipped as expected.
*/
@SuppressWarnings("unchecked")
public void testSkipListenerFailsOnWrite() throws Exception {
reader = new SkipReaderStub(StringUtils.commaDelimitedListToStringArray("1,2,3,4,5,6"), StringUtils
.commaDelimitedListToSet("2,3,5"));
factory.setSkipLimit(3);
factory.setItemReader(reader);
factory.setListeners(new StepListener[] { new SkipListenerSupport() {
@Override
public void onSkipInWrite(Object item, Throwable t) {
throw new RuntimeException("oops");
}
} });
factory.setSkippableExceptionClasses(new Class[] { Exception.class });
AbstractStep step = (AbstractStep) factory.getObject();
StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
try {
step.execute(stepExecution);
fail("Expected SkipListenerFailedException.");
}
catch (SkipListenerFailedException e) {
assertEquals("oops", e.getCause().getMessage());
}
assertEquals(1, stepExecution.getSkipCount());
assertEquals(0, stepExecution.getReadSkipCount().intValue());
assertEquals(1, stepExecution.getWriteSkipCount().intValue());
}
/**
* Check items causing errors are skipped as expected.
*/
@@ -383,7 +462,7 @@ public class SkipLimitStepFactoryBeanTests extends TestCase {
public void clear() throws ClearFailedException {
for (int i = flushIndex + 1; i < written.size(); i++) {
written.remove(written.size()-1);
written.remove(written.size() - 1);
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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.core.step.skip;
import org.springframework.batch.core.listener.AbstractDoubleExceptionTests;
/**
* @author Dave Syer
*
*/
public class SkipListenerFailedExceptionTests extends AbstractDoubleExceptionTests {
/* (non-Javadoc)
* @see org.springframework.batch.core.listener.AbstractDoubleExceptionTests#getException(java.lang.String, java.lang.RuntimeException, java.lang.Throwable)
*/
@Override
public Exception getException(String msg, RuntimeException cause, Throwable e) throws Exception {
return new SkipListenerFailedException(msg, cause, e);
}
}