IN PROGRESS BATCH-919: Draft refactoring introducing ChunkProvider and ChunkProcessor
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
package org.springframework.batch.core.step.item;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.batch.retry.ExhaustedRetryException;
|
||||
import org.springframework.batch.retry.RecoveryCallback;
|
||||
import org.springframework.batch.retry.RetryCallback;
|
||||
import org.springframework.batch.retry.RetryContext;
|
||||
import org.springframework.batch.retry.RetryState;
|
||||
import org.springframework.batch.retry.policy.SimpleRetryPolicy;
|
||||
import org.springframework.batch.retry.support.DefaultRetryState;
|
||||
|
||||
public class BatchRetryTemplateTests {
|
||||
|
||||
private static class RecoverableException extends Exception {
|
||||
|
||||
public RecoverableException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private int count = 0;
|
||||
|
||||
private List<String> outputs = new ArrayList<String>();
|
||||
|
||||
@Test
|
||||
public void testSuccessfulAttempt() throws Exception {
|
||||
|
||||
BatchRetryTemplate template = new BatchRetryTemplate();
|
||||
|
||||
String result = template.execute(new RetryCallback<String>() {
|
||||
public String doWithRetry(RetryContext context) throws Exception {
|
||||
assertTrue("Wrong context type: " + context.getClass().getSimpleName(), context.getClass().getSimpleName().contains("Batch"));
|
||||
return "2";
|
||||
}
|
||||
}, Arrays.<RetryState> asList(new DefaultRetryState("1")));
|
||||
|
||||
assertEquals("2", result);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnSuccessfulAttemptAndRetry() throws Exception {
|
||||
|
||||
BatchRetryTemplate template = new BatchRetryTemplate();
|
||||
|
||||
RetryCallback<String[]> retryCallback = new RetryCallback<String[]>() {
|
||||
public String[] doWithRetry(RetryContext context) throws Exception {
|
||||
assertEquals(count, context.getRetryCount());
|
||||
if (count++ == 0) {
|
||||
throw new RecoverableException("Recoverable");
|
||||
}
|
||||
return new String[] { "a", "b" };
|
||||
}
|
||||
};
|
||||
|
||||
List<RetryState> states = Arrays.<RetryState> asList(new DefaultRetryState("1"), new DefaultRetryState("2"));
|
||||
try {
|
||||
template.execute(retryCallback, states);
|
||||
fail("Expected RecoverableException");
|
||||
}
|
||||
catch (RecoverableException e) {
|
||||
assertEquals("Recoverable", e.getMessage());
|
||||
}
|
||||
String[] result = template.execute(retryCallback, states);
|
||||
|
||||
assertEquals("[a, b]", Arrays.toString(result));
|
||||
|
||||
}
|
||||
|
||||
@Test(expected = ExhaustedRetryException.class)
|
||||
public void testExhaustedRetry() throws Exception {
|
||||
|
||||
BatchRetryTemplate template = new BatchRetryTemplate();
|
||||
template.setRetryPolicy(new SimpleRetryPolicy(1));
|
||||
|
||||
RetryCallback<String[]> retryCallback = new RetryCallback<String[]>() {
|
||||
public String[] doWithRetry(RetryContext context) throws Exception {
|
||||
if (count++ < 2) {
|
||||
throw new RecoverableException("Recoverable");
|
||||
}
|
||||
return outputs.toArray(new String[0]);
|
||||
}
|
||||
};
|
||||
|
||||
outputs = Arrays.asList("a", "b");
|
||||
try {
|
||||
template.execute(retryCallback, BatchRetryTemplate.createState(outputs));
|
||||
fail("Expected RecoverableException");
|
||||
}
|
||||
catch (RecoverableException e) {
|
||||
assertEquals("Recoverable", e.getMessage());
|
||||
}
|
||||
outputs = Arrays.asList("a", "c");
|
||||
template.execute(retryCallback, BatchRetryTemplate.createState(outputs));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExhaustedRetryAfterShuffle() throws Exception {
|
||||
|
||||
BatchRetryTemplate template = new BatchRetryTemplate();
|
||||
template.setRetryPolicy(new SimpleRetryPolicy(1));
|
||||
|
||||
RetryCallback<String[]> retryCallback = new RetryCallback<String[]>() {
|
||||
public String[] doWithRetry(RetryContext context) throws Exception {
|
||||
if (count++ < 1) {
|
||||
throw new RecoverableException("Recoverable");
|
||||
}
|
||||
return outputs.toArray(new String[0]);
|
||||
}
|
||||
};
|
||||
|
||||
outputs = Arrays.asList("a", "b");
|
||||
try {
|
||||
template.execute(retryCallback, BatchRetryTemplate.createState(outputs));
|
||||
fail("Expected RecoverableException");
|
||||
}
|
||||
catch (RecoverableException e) {
|
||||
assertEquals("Recoverable", e.getMessage());
|
||||
}
|
||||
|
||||
outputs = Arrays.asList("b", "c");
|
||||
try {
|
||||
template.execute(retryCallback, BatchRetryTemplate.createState(outputs));
|
||||
fail("Expected ExhaustedRetryException");
|
||||
}
|
||||
catch (ExhaustedRetryException e) {
|
||||
}
|
||||
|
||||
// "c" is not tarred with same brush as "b" because it was never
|
||||
// processed on account of the exhausted retry
|
||||
outputs = Arrays.asList("d", "c");
|
||||
String[] result = template.execute(retryCallback, BatchRetryTemplate.createState(outputs));
|
||||
assertEquals("[d, c]", Arrays.toString(result));
|
||||
|
||||
// "a" is still marked as a failure from the first chunk
|
||||
outputs = Arrays.asList("a", "e");
|
||||
try {
|
||||
template.execute(retryCallback, BatchRetryTemplate.createState(outputs));
|
||||
fail("Expected ExhaustedRetryException");
|
||||
}
|
||||
catch (ExhaustedRetryException e) {
|
||||
}
|
||||
|
||||
outputs = Arrays.asList("e", "f");
|
||||
result = template.execute(retryCallback, BatchRetryTemplate.createState(outputs));
|
||||
assertEquals("[e, f]", Arrays.toString(result));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExhaustedRetryWithRecovery() throws Exception {
|
||||
|
||||
BatchRetryTemplate template = new BatchRetryTemplate();
|
||||
template.setRetryPolicy(new SimpleRetryPolicy(1));
|
||||
|
||||
RetryCallback<String[]> retryCallback = new RetryCallback<String[]>() {
|
||||
public String[] doWithRetry(RetryContext context) throws Exception {
|
||||
if (count++ < 2) {
|
||||
throw new RecoverableException("Recoverable");
|
||||
}
|
||||
return outputs.toArray(new String[0]);
|
||||
}
|
||||
};
|
||||
|
||||
RecoveryCallback<String[]> recoveryCallback = new RecoveryCallback<String[]>() {
|
||||
public String[] recover(RetryContext context) throws Exception {
|
||||
List<String> recovered = new ArrayList<String>();
|
||||
for (String item : outputs) {
|
||||
recovered.add("r:"+item);
|
||||
}
|
||||
return recovered.toArray(new String[0]);
|
||||
}
|
||||
};
|
||||
|
||||
outputs = Arrays.asList("a", "b");
|
||||
try {
|
||||
template.execute(retryCallback, recoveryCallback, BatchRetryTemplate.createState(outputs));
|
||||
fail("Expected RecoverableException");
|
||||
}
|
||||
catch (RecoverableException e) {
|
||||
assertEquals("Recoverable", e.getMessage());
|
||||
}
|
||||
|
||||
outputs = Arrays.asList("b", "c");
|
||||
String[] result = template.execute(retryCallback, recoveryCallback, BatchRetryTemplate.createState(outputs));
|
||||
assertEquals("[r:b, r:c]", Arrays.toString(result));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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 static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobInstance;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.repeat.context.RepeatContextSupport;
|
||||
import org.springframework.core.AttributeAccessor;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ChunkOrientedTaskletTests {
|
||||
|
||||
private AttributeAccessor context = new RepeatContextSupport(null);
|
||||
|
||||
@Test
|
||||
public void testHandle() throws Exception {
|
||||
ChunkOrientedTasklet<String> handler = new ChunkOrientedTasklet<String>(new ChunkProvider<String>() {
|
||||
public Chunk<String> provide(StepContribution contribution) throws Exception {
|
||||
contribution.incrementReadCount();
|
||||
Chunk<String> chunk = new Chunk<String>();
|
||||
chunk.add("foo");
|
||||
return chunk;
|
||||
}
|
||||
public void postProcess(StepContribution contribution, Chunk<String> chunk) {};
|
||||
}, new ChunkProcessor<String>() {
|
||||
public void process(StepContribution contribution, Chunk<String> chunk) {
|
||||
contribution.incrementWriteCount(1);
|
||||
}
|
||||
});
|
||||
StepContribution contribution = new StepContribution(new StepExecution("foo", new JobExecution(new JobInstance(
|
||||
123L, new JobParameters(), "job"))));
|
||||
handler.execute(contribution, context);
|
||||
assertEquals(1, contribution.getReadCount());
|
||||
assertEquals(1, contribution.getWriteCount());
|
||||
assertEquals(0, context.attributeNames().length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFail() throws Exception {
|
||||
ChunkOrientedTasklet<String> handler = new ChunkOrientedTasklet<String>(new ChunkProvider<String>() {
|
||||
public Chunk<String> provide(StepContribution contribution) throws Exception {
|
||||
throw new RuntimeException("Foo!");
|
||||
}
|
||||
public void postProcess(StepContribution contribution, Chunk<String> chunk) {};
|
||||
}, new ChunkProcessor<String>() {
|
||||
public void process(StepContribution contribution, Chunk<String> chunk) {
|
||||
fail("Not expecting to get this far");
|
||||
}
|
||||
});
|
||||
StepContribution contribution = new StepContribution(new StepExecution("foo", new JobExecution(new JobInstance(
|
||||
123L, new JobParameters(), "job"))));
|
||||
try {
|
||||
handler.execute(contribution, context);
|
||||
fail("Expected RuntimeException");
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
assertEquals("Foo!", e.getMessage());
|
||||
}
|
||||
assertEquals(0, contribution.getReadCount());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,468 +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 static org.junit.Assert.*;
|
||||
import static org.easymock.EasyMock.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.batch.core.SkipListener;
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||
import org.springframework.batch.core.step.skip.NeverSkipItemSkipPolicy;
|
||||
import org.springframework.batch.core.step.skip.SkipPolicy;
|
||||
import org.springframework.batch.core.step.skip.SkipLimitExceededException;
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.batch.item.NoWorkFoundException;
|
||||
import org.springframework.batch.item.ParseException;
|
||||
import org.springframework.batch.item.UnexpectedInputException;
|
||||
import org.springframework.batch.repeat.policy.SimpleCompletionPolicy;
|
||||
import org.springframework.batch.repeat.support.RepeatTemplate;
|
||||
import org.springframework.batch.retry.RetryException;
|
||||
import org.springframework.batch.retry.policy.NeverRetryPolicy;
|
||||
import org.springframework.batch.retry.support.RetryTemplate;
|
||||
import org.springframework.batch.support.Classifier;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class FaultTolerantChunkOrientedTaskletTests {
|
||||
|
||||
private Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private int count = 0;
|
||||
|
||||
private int limit = 3;
|
||||
|
||||
private int skipLimit = 2;
|
||||
|
||||
private List<String> written = new ArrayList<String>();
|
||||
|
||||
private List<Integer> processed = new ArrayList<Integer>();
|
||||
|
||||
private FaultTolerantChunkOrientedTasklet<Integer, String> tasklet;
|
||||
|
||||
private RepeatTemplate chunkOperations = new RepeatTemplate();
|
||||
|
||||
private ItemReader<Integer> itemReader = new ItemReader<Integer>() {
|
||||
public Integer read() {
|
||||
return count++ >= limit ? null : count;
|
||||
};
|
||||
};
|
||||
|
||||
private ItemWriter<String> itemWriter = new ItemWriter<String>() {
|
||||
public void write(List<? extends String> items) throws Exception {
|
||||
written.addAll(items);
|
||||
}
|
||||
};
|
||||
|
||||
private ItemProcessor<Integer, String> itemProcessor = new ItemProcessor<Integer, String>() {
|
||||
public String process(Integer item) throws Exception {
|
||||
return "" + item;
|
||||
}
|
||||
};
|
||||
|
||||
private RetryTemplate retryTemplate = new RetryTemplate();
|
||||
|
||||
private Classifier<Throwable, Boolean> rollbackClassifier = new Classifier<Throwable, Boolean>() {
|
||||
public Boolean classify(Throwable classifiable) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
private SkipPolicy readSkipPolicy = new SkipPolicy() {
|
||||
public boolean shouldSkip(Throwable t, int skipCount) throws SkipLimitExceededException {
|
||||
if (skipCount < skipLimit) {
|
||||
return true;
|
||||
}
|
||||
throw new SkipLimitExceededException(skipLimit, t);
|
||||
}
|
||||
};
|
||||
|
||||
private SkipPolicy writeSkipPolicy = readSkipPolicy;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
retryTemplate.setRetryPolicy(new NeverRetryPolicy());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicHandle() throws Exception {
|
||||
tasklet = new FaultTolerantChunkOrientedTasklet<Integer, String>(itemReader, itemProcessor, itemWriter,
|
||||
chunkOperations, retryTemplate, rollbackClassifier, readSkipPolicy, writeSkipPolicy, writeSkipPolicy);
|
||||
StepContribution contribution = new StepExecution("foo", null).createStepContribution();
|
||||
tasklet.execute(contribution, new ChunkContext());
|
||||
assertEquals(limit, contribution.getReadCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipOnRead() throws Exception {
|
||||
tasklet = new FaultTolerantChunkOrientedTasklet<Integer, String>(new ItemReader<Integer>() {
|
||||
public Integer read() throws Exception, UnexpectedInputException, NoWorkFoundException, ParseException {
|
||||
throw new RuntimeException("Barf!");
|
||||
}
|
||||
}, itemProcessor, itemWriter, chunkOperations, retryTemplate, rollbackClassifier, readSkipPolicy,
|
||||
writeSkipPolicy, writeSkipPolicy);
|
||||
chunkOperations.setCompletionPolicy(new SimpleCompletionPolicy(1));
|
||||
StepContribution contribution = new StepExecution("foo", null).createStepContribution();
|
||||
ChunkContext attributes = new ChunkContext();
|
||||
try {
|
||||
tasklet.execute(contribution, attributes);
|
||||
fail("Expected SkipLimitExceededException");
|
||||
}
|
||||
catch (SkipLimitExceededException e) {
|
||||
// expected
|
||||
}
|
||||
assertEquals(0, contribution.getReadCount());
|
||||
assertEquals(2, contribution.getReadSkipCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipSingleItemOnWrite() throws Exception {
|
||||
tasklet = new FaultTolerantChunkOrientedTasklet<Integer, String>(itemReader, itemProcessor,
|
||||
new ItemWriter<String>() {
|
||||
public void write(List<? extends String> items) throws Exception {
|
||||
written.addAll(items);
|
||||
throw new RuntimeException("Barf!");
|
||||
}
|
||||
}, chunkOperations, retryTemplate, rollbackClassifier, readSkipPolicy, writeSkipPolicy, writeSkipPolicy);
|
||||
chunkOperations.setCompletionPolicy(new SimpleCompletionPolicy(1));
|
||||
StepContribution contribution = new StepExecution("foo", null).createStepContribution();
|
||||
ChunkContext attributes = new ChunkContext();
|
||||
try {
|
||||
tasklet.execute(contribution, attributes);
|
||||
fail("Expected RuntimeException");
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertEquals("Barf!", e.getMessage());
|
||||
}
|
||||
assertTrue(attributes.hasAttribute("SKIPPED_OUTPUTS_KEY"));
|
||||
tasklet.execute(contribution, attributes);
|
||||
assertEquals(1, contribution.getReadCount());
|
||||
assertEquals(1, contribution.getWriteSkipCount());
|
||||
assertEquals(1, written.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipMultipleItemsOnWrite() throws Exception {
|
||||
tasklet = new FaultTolerantChunkOrientedTasklet<Integer, String>(itemReader, itemProcessor,
|
||||
new ItemWriter<String>() {
|
||||
public void write(List<? extends String> items) throws Exception {
|
||||
logger.debug("Writing items: " + items);
|
||||
written.addAll(items);
|
||||
throw new RuntimeException("Barf!");
|
||||
}
|
||||
}, chunkOperations, retryTemplate, rollbackClassifier, readSkipPolicy, writeSkipPolicy, writeSkipPolicy);
|
||||
chunkOperations.setCompletionPolicy(new SimpleCompletionPolicy(2));
|
||||
StepContribution contribution = new StepExecution("foo", null).createStepContribution();
|
||||
ChunkContext attributes = new ChunkContext();
|
||||
|
||||
// Count to 3: (try + skip + skip)
|
||||
for (int i = 0; i < 3; i++) {
|
||||
try {
|
||||
tasklet.execute(contribution, attributes);
|
||||
fail("Expected RuntimeException on i=" + i);
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertEquals("Barf!", e.getMessage());
|
||||
}
|
||||
assertTrue(attributes.hasAttribute("SKIPPED_OUTPUTS_KEY"));
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Exception> skips = (Map<String, Exception>) attributes.getAttribute("SKIPPED_OUTPUTS_KEY");
|
||||
assertEquals(1, skips.size());
|
||||
// The last recovery for this chunk...
|
||||
tasklet.execute(contribution, attributes);
|
||||
|
||||
attributes = new ChunkContext();
|
||||
try {
|
||||
tasklet.execute(contribution, attributes);
|
||||
fail("Expected RuntimeException");
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertEquals("Barf!", e.getMessage());
|
||||
}
|
||||
try {
|
||||
tasklet.execute(contribution, attributes);
|
||||
fail("Expected SkipLimitExceededException");
|
||||
}
|
||||
catch (SkipLimitExceededException e) {
|
||||
// expected
|
||||
}
|
||||
assertTrue(attributes.hasAttribute("SKIPPED_OUTPUTS_KEY"));
|
||||
assertEquals(3, contribution.getReadCount());
|
||||
assertEquals(0, contribution.getFilterCount());
|
||||
assertEquals(2, contribution.getWriteSkipCount());
|
||||
assertEquals(5, written.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipSingleItemOnProcess() throws Exception {
|
||||
tasklet = new FaultTolerantChunkOrientedTasklet<Integer, String>(itemReader,
|
||||
new ItemProcessor<Integer, String>() {
|
||||
public String process(Integer item) throws Exception {
|
||||
logger.debug("Processing item: " + item);
|
||||
processed.add(item);
|
||||
if (item == 3) {
|
||||
throw new RuntimeException("Barf!");
|
||||
}
|
||||
return "p" + item;
|
||||
}
|
||||
}, itemWriter, chunkOperations, retryTemplate, rollbackClassifier, readSkipPolicy, writeSkipPolicy,
|
||||
writeSkipPolicy);
|
||||
chunkOperations.setCompletionPolicy(new SimpleCompletionPolicy(3));
|
||||
StepContribution contribution = new StepExecution("foo", null).createStepContribution();
|
||||
ChunkContext attributes = new ChunkContext();
|
||||
|
||||
// try
|
||||
try {
|
||||
tasklet.execute(contribution, attributes);
|
||||
fail("Expected RuntimeException");
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertEquals("Barf!", e.getMessage());
|
||||
}
|
||||
assertTrue(attributes.hasAttribute("INPUT_BUFFER_KEY"));
|
||||
|
||||
// skip...
|
||||
tasklet.execute(contribution, attributes);
|
||||
|
||||
assertEquals(3, contribution.getReadCount());
|
||||
assertEquals(1, contribution.getProcessSkipCount());
|
||||
assertEquals(5, processed.size());
|
||||
assertEquals("[p1, p2]", written.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipOverLimitOnProcess() throws Exception {
|
||||
tasklet = new FaultTolerantChunkOrientedTasklet<Integer, String>(itemReader,
|
||||
new ItemProcessor<Integer, String>() {
|
||||
public String process(Integer item) throws Exception {
|
||||
logger.debug("Processing item: " + item);
|
||||
processed.add(item);
|
||||
throw new RuntimeException("Barf!");
|
||||
}
|
||||
}, itemWriter, chunkOperations, retryTemplate, rollbackClassifier, readSkipPolicy, writeSkipPolicy,
|
||||
writeSkipPolicy);
|
||||
chunkOperations.setCompletionPolicy(new SimpleCompletionPolicy(2));
|
||||
StepContribution contribution = new StepExecution("foo", null).createStepContribution();
|
||||
ChunkContext attributes = new ChunkContext();
|
||||
|
||||
// Count to 2: (try first + fail) + (skip first + try second + fail)
|
||||
for (int i = 0; i < 2; i++) {
|
||||
try {
|
||||
tasklet.execute(contribution, attributes);
|
||||
fail("Expected RuntimeException on i=" + i);
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertEquals("Barf!", e.getMessage());
|
||||
}
|
||||
assertTrue(attributes.hasAttribute("INPUT_BUFFER_KEY"));
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<Integer, Exception> skips = (Map<Integer, Exception>) attributes.getAttribute("SKIPPED_INPUTS_KEY");
|
||||
assertEquals(1, skips.size());
|
||||
|
||||
// The last recovery for this chunk...
|
||||
tasklet.execute(contribution, attributes);
|
||||
|
||||
attributes = new ChunkContext();
|
||||
try {
|
||||
tasklet.execute(contribution, attributes);
|
||||
fail("Expected RuntimeException");
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertEquals("Barf!", e.getMessage());
|
||||
}
|
||||
try {
|
||||
tasklet.execute(contribution, attributes);
|
||||
fail("Expected SkipLimitExceededException");
|
||||
}
|
||||
catch (SkipLimitExceededException e) {
|
||||
// expected
|
||||
}
|
||||
assertTrue(attributes.hasAttribute("INPUT_BUFFER_KEY"));
|
||||
assertEquals(3, contribution.getReadCount());
|
||||
assertEquals(2, contribution.getProcessSkipCount());
|
||||
// Just before the skip at the end we process once more
|
||||
assertEquals(3, processed.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* When writer throws an exception that causes rollback, items are
|
||||
* re-processed in next iteration.
|
||||
*/
|
||||
@Test
|
||||
public void testReprocessAfterWriterRollback() {
|
||||
final String WRITER_FAILED_MESSAGE = "writer failed";
|
||||
final int CHUNK_SIZE = 2;
|
||||
tasklet = new FaultTolerantChunkOrientedTasklet<Integer, String>(itemReader,
|
||||
new ItemProcessor<Integer, String>() {
|
||||
public String process(Integer item) throws Exception {
|
||||
processed.add(item);
|
||||
return String.valueOf(item);
|
||||
}
|
||||
}, new ItemWriter<String>() {
|
||||
public void write(List<? extends String> items) throws Exception {
|
||||
throw new RuntimeException(WRITER_FAILED_MESSAGE);
|
||||
}
|
||||
|
||||
}, chunkOperations, retryTemplate, rollbackClassifier, readSkipPolicy, writeSkipPolicy, writeSkipPolicy);
|
||||
chunkOperations.setCompletionPolicy(new SimpleCompletionPolicy(CHUNK_SIZE));
|
||||
StepContribution contribution = new StepExecution("foo", null).createStepContribution();
|
||||
ChunkContext attributes = new ChunkContext();
|
||||
|
||||
for (int i = 1; i <= 2; i++) {
|
||||
try {
|
||||
tasklet.execute(contribution, attributes);
|
||||
fail();
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertEquals(WRITER_FAILED_MESSAGE, e.getMessage());
|
||||
assertEquals(i * CHUNK_SIZE, processed.size());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure skip counts are correct when items are skipped on both process
|
||||
* and write in the same chunk.
|
||||
*/
|
||||
@Test
|
||||
public void testSkipItemOnProcessAndWrite() throws Exception {
|
||||
final String WRITER_FAILED_MESSAGE = "writer failed";
|
||||
final String PROCESSOR_FAILED_MESSAGE = "processor failed";
|
||||
final RuntimeException writerException = new RuntimeException(WRITER_FAILED_MESSAGE);
|
||||
final RuntimeException processorException = new RuntimeException(PROCESSOR_FAILED_MESSAGE);
|
||||
final int CHUNK_SIZE = 2;
|
||||
tasklet = new FaultTolerantChunkOrientedTasklet<Integer, String>(itemReader,
|
||||
new ItemProcessor<Integer, String>() {
|
||||
public String process(Integer item) throws Exception {
|
||||
if (item == 1) {
|
||||
throw processorException;
|
||||
}
|
||||
processed.add(item);
|
||||
return String.valueOf(item);
|
||||
}
|
||||
}, new ItemWriter<String>() {
|
||||
public void write(List<? extends String> items) throws Exception {
|
||||
throw writerException;
|
||||
}
|
||||
|
||||
}, chunkOperations, retryTemplate, rollbackClassifier, readSkipPolicy, writeSkipPolicy, writeSkipPolicy);
|
||||
chunkOperations.setCompletionPolicy(new SimpleCompletionPolicy(CHUNK_SIZE));
|
||||
StepContribution contribution = new StepExecution("foo", null).createStepContribution();
|
||||
ChunkContext attributes = new ChunkContext();
|
||||
|
||||
// mock checks skip listener is called as expected
|
||||
@SuppressWarnings("unchecked")
|
||||
SkipListener<Integer, String> skipListener = createStrictMock(SkipListener.class);
|
||||
tasklet.registerListener(skipListener);
|
||||
skipListener.onSkipInProcess(1, processorException);
|
||||
expectLastCall().once();
|
||||
skipListener.onSkipInWrite("2", writerException);
|
||||
expectLastCall().once();
|
||||
replay(skipListener);
|
||||
|
||||
// processor fails first
|
||||
try {
|
||||
tasklet.execute(contribution, attributes);
|
||||
fail();
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertEquals(PROCESSOR_FAILED_MESSAGE, e.getMessage());
|
||||
}
|
||||
|
||||
// we've only rolled back, nothing has been skipped yet
|
||||
assertEquals(0, contribution.getProcessSkipCount());
|
||||
assertEquals(0, contribution.getWriteSkipCount());
|
||||
|
||||
try {
|
||||
tasklet.execute(contribution, attributes);
|
||||
fail();
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertEquals(WRITER_FAILED_MESSAGE, e.getMessage());
|
||||
}
|
||||
|
||||
// processor skipped failed item, writer fails and causes rollback
|
||||
assertEquals(1, contribution.getProcessSkipCount());
|
||||
assertEquals(0, contribution.getWriteSkipCount());
|
||||
|
||||
tasklet.execute(contribution, attributes);
|
||||
|
||||
// both processor and writer skipped
|
||||
assertEquals(1, contribution.getProcessSkipCount());
|
||||
assertEquals(1, contribution.getWriteSkipCount());
|
||||
|
||||
verify(skipListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRethrowNonSkippableExceptionOnWriteAsap() throws Exception {
|
||||
final List<String> chunk = Arrays.asList(new String[] { "1", "2" });
|
||||
final Exception ex = new RuntimeException();
|
||||
final StepContribution contribution = new StepExecution("foo", null).createStepContribution();
|
||||
final Map<String, Exception> skipped = new HashMap<String, Exception>();
|
||||
writeSkipPolicy = new NeverSkipItemSkipPolicy();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ItemWriter<String> itemWriter = createMock(ItemWriter.class);
|
||||
itemWriter.write(chunk);
|
||||
expectLastCall().andThrow(ex);
|
||||
replay(itemWriter);
|
||||
tasklet = new FaultTolerantChunkOrientedTasklet<Integer, String>(itemReader, itemProcessor, itemWriter,
|
||||
chunkOperations, retryTemplate, rollbackClassifier, readSkipPolicy, writeSkipPolicy, writeSkipPolicy);
|
||||
|
||||
try {
|
||||
tasklet.write(chunk, contribution, skipped);
|
||||
fail();
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertSame(ex, e);
|
||||
}
|
||||
|
||||
try {
|
||||
tasklet.write(chunk, contribution, skipped);
|
||||
fail();
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertTrue(e instanceof RetryException);
|
||||
assertSame(ex, e.getCause());
|
||||
}
|
||||
|
||||
/*
|
||||
* writer was called only on first failed attempt, exception is rethrown
|
||||
* immediately when chunk is reprocessed because it is not skippable
|
||||
*/
|
||||
verify(itemWriter);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.springframework.batch.core.step.item;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.batch.item.support.PassthroughItemProcessor;
|
||||
|
||||
public class FaultTolerantChunkProcessorTests {
|
||||
|
||||
private BatchRetryTemplate batchRetryTemplate = new BatchRetryTemplate();
|
||||
|
||||
private List<String> list = new ArrayList<String>();
|
||||
|
||||
@Test
|
||||
public void testWrite() throws Exception {
|
||||
FaultTolerantChunkProcessor<String, String> processor = new FaultTolerantChunkProcessor<String, String>(
|
||||
new PassthroughItemProcessor<String>(), new ItemWriter<String>() {
|
||||
public void write(List<? extends String> items) throws Exception {
|
||||
list.addAll(items);
|
||||
}
|
||||
}, batchRetryTemplate);
|
||||
Chunk<String> inputs = new Chunk<String>();
|
||||
inputs.add("1");
|
||||
inputs.add("2");
|
||||
processor.process(new StepExecution("foo", new JobExecution(0L)).createStepContribution(), inputs);
|
||||
assertEquals(2, list.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransform() throws Exception {
|
||||
FaultTolerantChunkProcessor<String, String> processor = new FaultTolerantChunkProcessor<String, String>(
|
||||
new ItemProcessor<String, String>() {
|
||||
public String process(String item) throws Exception {
|
||||
return item.equals("1") ? null : item;
|
||||
}
|
||||
}, new ItemWriter<String>() {
|
||||
public void write(List<? extends String> items) throws Exception {
|
||||
list.addAll(items);
|
||||
}
|
||||
}, batchRetryTemplate);
|
||||
Chunk<String> inputs = new Chunk<String>();
|
||||
inputs.add("1");
|
||||
inputs.add("2");
|
||||
processor.process(new StepExecution("foo", new JobExecution(0L)).createStepContribution(), inputs);
|
||||
assertEquals(1, list.size());
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
package org.springframework.batch.core.step.item;
|
||||
|
||||
import static org.easymock.EasyMock.createStrictMock;
|
||||
import static org.easymock.EasyMock.expectLastCall;
|
||||
import static org.easymock.EasyMock.replay;
|
||||
import static org.easymock.EasyMock.verify;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.easymock.EasyMock.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
@@ -21,16 +23,11 @@ import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.SkipListener;
|
||||
import org.springframework.batch.core.Step;
|
||||
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.JobRepositorySupport;
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.batch.item.support.ListItemReader;
|
||||
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
|
||||
import org.springframework.batch.support.transaction.TransactionAwareProxyFactory;
|
||||
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
public class FaultTolerantStepFactoryBeanNonBufferingTests {
|
||||
@@ -66,7 +63,7 @@ public class FaultTolerantStepFactoryBeanNonBufferingTests {
|
||||
factory.setSkipLimit(2);
|
||||
factory.setIsReaderTransactionalQueue(true);
|
||||
|
||||
JobInstance jobInstance = new JobInstance(1L, new JobParameters(), "skipJob");
|
||||
JobInstance jobInstance = new JobInstance(new Long(1), new JobParameters(), "skipJob");
|
||||
jobExecution = new JobExecution(jobInstance);
|
||||
}
|
||||
|
||||
@@ -77,12 +74,13 @@ public class FaultTolerantStepFactoryBeanNonBufferingTests {
|
||||
public void testSkip() throws Exception {
|
||||
@SuppressWarnings("unchecked")
|
||||
SkipListener<Integer, String> skipListener = createStrictMock(SkipListener.class);
|
||||
skipListener.onSkipInWrite("3", SkipWriterStub.exception);
|
||||
expectLastCall().once();
|
||||
skipListener.onSkipInWrite("4", SkipWriterStub.exception);
|
||||
expectLastCall().once();
|
||||
replay(skipListener);
|
||||
|
||||
|
||||
factory.setListeners(new SkipListener[] { skipListener });
|
||||
factory.setSkipLimit(1);
|
||||
Step step = (Step) factory.getObject();
|
||||
|
||||
StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
|
||||
@@ -90,199 +88,23 @@ public class FaultTolerantStepFactoryBeanNonBufferingTests {
|
||||
|
||||
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
|
||||
|
||||
assertEquals(1, stepExecution.getSkipCount());
|
||||
assertEquals(0, stepExecution.getReadSkipCount());
|
||||
assertEquals(1, stepExecution.getWriteSkipCount());
|
||||
|
||||
// only one exception caused rollback, but more than once because it
|
||||
// has to go back and split the chunk up to isolate the failed item
|
||||
assertEquals(2, stepExecution.getRollbackCount());
|
||||
|
||||
assertFalse(writer.written.contains("4"));
|
||||
|
||||
List<String> expectedOutput = Arrays.asList(StringUtils.commaDelimitedListToStringArray("1,2,3,5"));
|
||||
assertEquals(expectedOutput, writer.written);
|
||||
|
||||
// 5 items + 2 rollbacks re-reading 2 items each time
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
|
||||
verify(skipListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipOverLimit() throws Exception {
|
||||
SkipProcessorStub processor = new SkipProcessorStub(Arrays.asList(StringUtils
|
||||
.commaDelimitedListToStringArray("3")));
|
||||
processor.rollback = false;
|
||||
|
||||
factory.setItemProcessor(processor);
|
||||
|
||||
factory.setSkipLimit(1);
|
||||
|
||||
Step step = (Step) factory.getObject();
|
||||
|
||||
StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
|
||||
|
||||
step.execute(stepExecution);
|
||||
|
||||
assertEquals(1, stepExecution.getSkipCount());
|
||||
|
||||
assertFalse(writer.written.contains("4"));
|
||||
|
||||
// failure on "4" tripped the skip limit so only first chunk was written
|
||||
List<String> expectedOutput = Arrays.asList(StringUtils.commaDelimitedListToStringArray("1,2"));
|
||||
assertEquals(expectedOutput, writer.written);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception in listener causes failure regardless of skip limit.
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testSkipListenerFailsOnWrite() throws Exception {
|
||||
|
||||
factory.setSkipLimit(7); // some high limit
|
||||
factory.setItemReader(reader);
|
||||
factory.setListeners(new StepListener[] { new SkipListenerSupport<String, String>() {
|
||||
@Override
|
||||
public void onSkipInWrite(String item, Throwable t) {
|
||||
throw new RuntimeException("oops");
|
||||
}
|
||||
} });
|
||||
factory.setSkippableExceptionClasses(Collections.<Class<? extends Throwable>> singleton(Exception.class));
|
||||
|
||||
Step step = (Step) factory.getObject();
|
||||
|
||||
StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
|
||||
|
||||
step.execute(stepExecution);
|
||||
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
|
||||
assertEquals("oops", stepExecution.getFailureExceptions().get(0).getCause().getMessage());
|
||||
assertEquals(1, stepExecution.getSkipCount());
|
||||
assertEquals(0, stepExecution.getReadSkipCount());
|
||||
assertEquals(1, stepExecution.getWriteSkipCount());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipOnWriteNotDoubleCounted() throws Exception {
|
||||
|
||||
writer = new SkipWriterStub(Arrays.asList(StringUtils.commaDelimitedListToStringArray("4,5")));
|
||||
|
||||
factory.setSkipLimit(4);
|
||||
factory.setItemReader(reader);
|
||||
factory.setItemWriter(writer);
|
||||
factory.setCommitInterval(3); // includes all expected skips
|
||||
|
||||
Step step = (Step) factory.getObject();
|
||||
|
||||
StepExecution stepExecution = jobExecution.createStepExecution(step.getName());
|
||||
|
||||
step.execute(stepExecution);
|
||||
assertEquals(2, stepExecution.getSkipCount());
|
||||
assertEquals(0, stepExecution.getReadSkipCount());
|
||||
assertEquals(2, stepExecution.getWriteSkipCount());
|
||||
|
||||
List<String> expectedOutput = Arrays.asList(StringUtils.commaDelimitedListToStringArray("1,2,3"));
|
||||
// only one exception caused rollback, and only once in this case
|
||||
// because all items in that chunk were skipped immediately
|
||||
assertEquals(1, stepExecution.getRollbackCount());
|
||||
|
||||
assertFalse(writer.written.contains("4"));
|
||||
|
||||
List<String> expectedOutput = Arrays.asList(StringUtils.commaDelimitedListToStringArray("1,2,5"));
|
||||
assertEquals(expectedOutput, writer.written);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultSkipPolicy() throws Exception {
|
||||
factory.setSkippableExceptionClasses(Collections.<Class<? extends Throwable>> singleton(Exception.class));
|
||||
factory.setSkipLimit(1);
|
||||
List<String> items = Arrays.asList(new String[] { "a", "b", "c" });
|
||||
ItemReader<String> provider = new ListItemReader<String>(TransactionAwareProxyFactory
|
||||
.createTransactionalList(items)) {
|
||||
public String read() {
|
||||
String item = super.read();
|
||||
count++;
|
||||
if ("b".equals(item)) {
|
||||
throw new RuntimeException("Read error - planned failure.");
|
||||
}
|
||||
return item;
|
||||
}
|
||||
};
|
||||
factory.setItemReader(provider);
|
||||
Step step = (Step) factory.getObject();
|
||||
|
||||
StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
|
||||
step.execute(stepExecution);
|
||||
|
||||
assertEquals(1, stepExecution.getSkipCount());
|
||||
// b is processed once and skipped, plus 1, plus c, plus the null at end
|
||||
assertEquals(4, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scenario: Exception in processor that shouldn't cause rollback
|
||||
*/
|
||||
@Test
|
||||
public void testProcessorNoRollback() throws Exception {
|
||||
|
||||
factory.setTransactionAttribute(new DefaultTransactionAttribute());
|
||||
SkipProcessorStub processor = new SkipProcessorStub(Arrays.asList(StringUtils
|
||||
.commaDelimitedListToStringArray("1,3")));
|
||||
factory.setItemProcessor(processor);
|
||||
|
||||
final Collection<String> NO_FAILURES = Collections.emptyList();
|
||||
factory.setItemWriter(new SkipWriterStub(NO_FAILURES));
|
||||
|
||||
Step step = (Step) factory.getObject();
|
||||
StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
|
||||
|
||||
processor.rollback = false;
|
||||
step.execute(stepExecution);
|
||||
assertEquals(2, stepExecution.getSkipCount());
|
||||
assertEquals(0, stepExecution.getRollbackCount());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Scenario: Exception in processor that should cause rollback
|
||||
*/
|
||||
@Test
|
||||
public void testProcessorRollback() throws Exception {
|
||||
SkipProcessorStub processor = new SkipProcessorStub(Arrays.asList(StringUtils
|
||||
.commaDelimitedListToStringArray("1,3")));
|
||||
factory.setItemProcessor(processor);
|
||||
|
||||
final Collection<String> NO_FAILURES = Collections.emptyList();
|
||||
factory.setItemWriter(new SkipWriterStub(NO_FAILURES));
|
||||
|
||||
Step step = (Step) factory.getObject();
|
||||
StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
|
||||
|
||||
processor.rollback = true;
|
||||
step.execute(stepExecution);
|
||||
assertEquals(2, stepExecution.getSkipCount());
|
||||
assertEquals(2, stepExecution.getRollbackCount());
|
||||
}
|
||||
|
||||
private static class SkipProcessorStub implements ItemProcessor<String, String> {
|
||||
|
||||
private final Collection<String> failures;
|
||||
|
||||
private boolean rollback = false;
|
||||
|
||||
public SkipProcessorStub(Collection<String> failures) {
|
||||
this.failures = failures;
|
||||
}
|
||||
|
||||
public String process(String item) throws Exception {
|
||||
if (failures.contains(item)) {
|
||||
if (rollback) {
|
||||
throw new SkippableRuntimeException("should cause rollback");
|
||||
}
|
||||
else {
|
||||
throw new SkippableException("shouldn't cause rollback");
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}
|
||||
// 5 items + 1 rollbacks reading 2 items each time
|
||||
assertEquals(7, stepExecution.getReadCount());
|
||||
|
||||
verify(skipListener);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -299,9 +121,8 @@ public class FaultTolerantStepFactoryBeanNonBufferingTests {
|
||||
|
||||
private final Collection<String> failures;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public SkipWriterStub() {
|
||||
this(StringUtils.commaDelimitedListToSet("4"));
|
||||
this(Arrays.asList("4"));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -312,6 +133,7 @@ public class FaultTolerantStepFactoryBeanNonBufferingTests {
|
||||
}
|
||||
|
||||
public void write(List<? extends String> items) throws Exception {
|
||||
logger.debug("Writing: " + items);
|
||||
for (String item : items) {
|
||||
if (failures.contains(item)) {
|
||||
logger.debug("Throwing write exception on [" + item + "]");
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
*/
|
||||
package org.springframework.batch.core.step.item;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -34,7 +35,6 @@ import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.StepListener;
|
||||
import org.springframework.batch.core.job.JobSupport;
|
||||
import org.springframework.batch.core.listener.SkipListenerSupport;
|
||||
import org.springframework.batch.core.repository.dao.MapExecutionContextDao;
|
||||
import org.springframework.batch.core.repository.dao.MapJobExecutionDao;
|
||||
@@ -109,11 +109,9 @@ public class FaultTolerantStepFactoryBeanRetryTests {
|
||||
});
|
||||
factory.setCommitInterval(1); // trivial by default
|
||||
|
||||
JobSupport job = new JobSupport("jobName");
|
||||
job.setRestartable(true);
|
||||
JobParameters jobParameters = new JobParametersBuilder().addString("statefulTest", "make_this_unique")
|
||||
.toJobParameters();
|
||||
jobExecution = repository.createJobExecution(job.getName(), jobParameters);
|
||||
jobExecution = repository.createJobExecution("job", jobParameters);
|
||||
jobExecution.setEndTime(new Date());
|
||||
|
||||
}
|
||||
|
||||
@@ -59,6 +59,8 @@ public class FaultTolerantStepFactoryBeanTests {
|
||||
|
||||
protected int count;
|
||||
|
||||
private Collection<String> NO_FAILURES = Collections.emptyList();
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
factory.setBeanName("stepName");
|
||||
@@ -70,7 +72,7 @@ public class FaultTolerantStepFactoryBeanTests {
|
||||
factory.setSkippableExceptionClasses(skippableExceptions);
|
||||
factory.setSkipLimit(2);
|
||||
|
||||
JobInstance jobInstance = new JobInstance(1L, new JobParameters(), "skipJob");
|
||||
JobInstance jobInstance = new JobInstance(new Long(1), new JobParameters(), "skipJob");
|
||||
jobExecution = new JobExecution(jobInstance);
|
||||
}
|
||||
|
||||
@@ -131,29 +133,94 @@ public class FaultTolerantStepFactoryBeanTests {
|
||||
* Check items causing errors are skipped as expected.
|
||||
*/
|
||||
@Test
|
||||
public void testSkip() throws Exception {
|
||||
public void testReadSkip() throws Exception {
|
||||
|
||||
writer = new SkipWriterStub(NO_FAILURES);
|
||||
factory.setItemWriter(writer);
|
||||
Step step = (Step) factory.getObject();
|
||||
|
||||
StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
|
||||
step.execute(stepExecution);
|
||||
|
||||
assertEquals(2, stepExecution.getSkipCount());
|
||||
assertEquals(1, stepExecution.getReadSkipCount());
|
||||
assertEquals(1, stepExecution.getWriteSkipCount());
|
||||
System.err.println(writer.written);
|
||||
|
||||
// only write exception caused rollback, but more than once because it
|
||||
// has to go back and split the chunk up to isolate the failed item
|
||||
assertEquals(2, stepExecution.getRollbackCount());
|
||||
assertEquals(1, stepExecution.getSkipCount());
|
||||
assertEquals(1, stepExecution.getReadSkipCount());
|
||||
assertEquals(4, stepExecution.getReadCount());
|
||||
assertEquals(0, stepExecution.getWriteSkipCount());
|
||||
assertEquals(0, stepExecution.getRollbackCount());
|
||||
|
||||
// writer did not skip "2" as it never made it to writer, only "4" did
|
||||
assertTrue(reader.processed.contains("4"));
|
||||
assertFalse(writer.written.contains("4"));
|
||||
assertFalse(reader.processed.contains("2"));
|
||||
|
||||
List<String> expectedOutput = Arrays.asList(StringUtils.commaDelimitedListToStringArray("1,3,5"));
|
||||
List<String> expectedOutput = Arrays.asList(StringUtils.commaDelimitedListToStringArray("1,3,4,5"));
|
||||
assertEquals(expectedOutput, writer.written);
|
||||
|
||||
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check items causing errors are skipped as expected.
|
||||
*/
|
||||
@Test
|
||||
public void testProcessSkip() throws Exception {
|
||||
|
||||
reader = new SkipReaderStub(new String[] { "1", "2", "3", "4", "5" }, NO_FAILURES);
|
||||
factory.setItemReader(reader);
|
||||
writer = new SkipWriterStub(NO_FAILURES);
|
||||
factory.setItemWriter(writer);
|
||||
SkipProcessorStub processor = new SkipProcessorStub(Arrays.asList(new String[] { "4" }));
|
||||
factory.setItemProcessor(processor);
|
||||
Step step = (Step) factory.getObject();
|
||||
|
||||
StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
|
||||
step.execute(stepExecution);
|
||||
|
||||
assertEquals(1, stepExecution.getSkipCount());
|
||||
assertEquals(0, stepExecution.getReadSkipCount());
|
||||
assertEquals(5, stepExecution.getReadCount());
|
||||
assertEquals(1, stepExecution.getProcessSkipCount());
|
||||
assertEquals(1, stepExecution.getRollbackCount());
|
||||
|
||||
// writer skips "4"
|
||||
assertTrue(reader.processed.contains("4"));
|
||||
assertFalse(writer.written.contains("4"));
|
||||
|
||||
List<String> expectedOutput = Arrays.asList(StringUtils.commaDelimitedListToStringArray("1,2,3,5"));
|
||||
assertEquals(expectedOutput, writer.written);
|
||||
|
||||
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check items causing errors are skipped as expected.
|
||||
*/
|
||||
@Test
|
||||
public void testWriteSkip() throws Exception {
|
||||
|
||||
reader = new SkipReaderStub(new String[] { "1", "2", "3", "4", "5" }, NO_FAILURES);
|
||||
factory.setItemReader(reader);
|
||||
Step step = (Step) factory.getObject();
|
||||
|
||||
StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
|
||||
step.execute(stepExecution);
|
||||
|
||||
assertEquals(1, stepExecution.getSkipCount());
|
||||
assertEquals(0, stepExecution.getReadSkipCount());
|
||||
assertEquals(5, stepExecution.getReadCount());
|
||||
assertEquals(1, stepExecution.getWriteSkipCount());
|
||||
assertEquals(2, stepExecution.getRollbackCount());
|
||||
|
||||
// writer skips "4"
|
||||
assertTrue(reader.processed.contains("4"));
|
||||
assertFalse(writer.written.contains("4"));
|
||||
|
||||
List<String> expectedOutput = Arrays.asList(StringUtils.commaDelimitedListToStringArray("1,2,3,5"));
|
||||
assertEquals(expectedOutput, writer.written);
|
||||
|
||||
assertEquals(4, stepExecution.getReadCount());
|
||||
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
|
||||
|
||||
}
|
||||
@@ -172,7 +239,7 @@ public class FaultTolerantStepFactoryBeanTests {
|
||||
Step step = (Step) factory.getObject();
|
||||
|
||||
StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
|
||||
|
||||
|
||||
step.execute(stepExecution);
|
||||
|
||||
assertEquals(1, stepExecution.getSkipCount());
|
||||
@@ -308,8 +375,8 @@ public class FaultTolerantStepFactoryBeanTests {
|
||||
@Test
|
||||
public void testSkipListenerFailsOnWrite() throws Exception {
|
||||
|
||||
reader = new SkipReaderStub(StringUtils.commaDelimitedListToStringArray("1,2,3,4,5,6"), Arrays
|
||||
.asList(StringUtils.commaDelimitedListToStringArray("2,3,5")));
|
||||
reader = new SkipReaderStub(StringUtils.commaDelimitedListToStringArray("1,2,3,4,5,6"), Collections
|
||||
.<String> emptyList());
|
||||
|
||||
factory.setSkipLimit(3);
|
||||
factory.setItemReader(reader);
|
||||
@@ -328,8 +395,8 @@ public class FaultTolerantStepFactoryBeanTests {
|
||||
step.execute(stepExecution);
|
||||
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
|
||||
assertEquals("oops", stepExecution.getFailureExceptions().get(0).getCause().getMessage());
|
||||
assertEquals(3, stepExecution.getSkipCount());
|
||||
assertEquals(2, stepExecution.getReadSkipCount());
|
||||
assertEquals(1, stepExecution.getSkipCount());
|
||||
assertEquals(0, stepExecution.getReadSkipCount());
|
||||
assertEquals(1, stepExecution.getWriteSkipCount());
|
||||
|
||||
}
|
||||
@@ -456,8 +523,6 @@ public class FaultTolerantStepFactoryBeanTests {
|
||||
|
||||
}
|
||||
|
||||
// TODO: test with transactional reader (e.g. list with tx proxy)
|
||||
|
||||
/**
|
||||
* Scenario: Exception in processor that shouldn't cause rollback
|
||||
*/
|
||||
@@ -468,7 +533,6 @@ public class FaultTolerantStepFactoryBeanTests {
|
||||
.commaDelimitedListToStringArray("1,3")));
|
||||
factory.setItemProcessor(processor);
|
||||
|
||||
final Collection<String> NO_FAILURES = Collections.emptyList();
|
||||
factory.setItemReader(new SkipReaderStub(new String[] { "1", "2", "3", "4" }, NO_FAILURES));
|
||||
factory.setItemWriter(new SkipWriterStub(NO_FAILURES));
|
||||
|
||||
@@ -491,7 +555,6 @@ public class FaultTolerantStepFactoryBeanTests {
|
||||
.commaDelimitedListToStringArray("1,3")));
|
||||
factory.setItemProcessor(processor);
|
||||
|
||||
final Collection<String> NO_FAILURES = Collections.emptyList();
|
||||
factory.setItemReader(new SkipReaderStub(new String[] { "1", "2", "3", "4" }, NO_FAILURES));
|
||||
factory.setItemWriter(new SkipWriterStub(NO_FAILURES));
|
||||
|
||||
@@ -512,16 +575,19 @@ public class FaultTolerantStepFactoryBeanTests {
|
||||
return item;
|
||||
}
|
||||
});
|
||||
final Collection<String> NO_FAILURES = Collections.emptyList();
|
||||
factory.setItemReader(new SkipReaderStub(new String[] { "1", "2", "3", "4" }, NO_FAILURES));
|
||||
|
||||
Step step = (Step) factory.getObject();
|
||||
StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
|
||||
step.execute(stepExecution);
|
||||
// 1,2,3,4,3,4,3,4 - two re-processing attempts until the item is
|
||||
|
||||
assertEquals(1, stepExecution.getSkipCount());
|
||||
assertEquals(2, stepExecution.getRollbackCount());
|
||||
|
||||
// 1,2,3,4,3,4,3 - two re-processing attempts until the item is
|
||||
// identified and skipped
|
||||
assertEquals(8, processed.size());
|
||||
assertEquals("[1, 2, 3, 4, 3, 4, 3, 4]", processed.toString());
|
||||
assertEquals(7, processed.size());
|
||||
assertEquals("[1, 2, 3, 4, 3, 4, 3]", processed.toString());
|
||||
|
||||
}
|
||||
|
||||
@@ -603,9 +669,8 @@ public class FaultTolerantStepFactoryBeanTests {
|
||||
|
||||
private final Collection<String> failures;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public SkipWriterStub() {
|
||||
this(StringUtils.commaDelimitedListToSet("4"));
|
||||
this(Arrays.asList("4"));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,173 +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 static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobInstance;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.batch.item.NoWorkFoundException;
|
||||
import org.springframework.batch.item.ParseException;
|
||||
import org.springframework.batch.item.UnexpectedInputException;
|
||||
import org.springframework.batch.item.support.PassthroughItemProcessor;
|
||||
import org.springframework.batch.item.validator.ValidationException;
|
||||
import org.springframework.batch.repeat.context.RepeatContextSupport;
|
||||
import org.springframework.batch.repeat.policy.SimpleCompletionPolicy;
|
||||
import org.springframework.batch.repeat.support.RepeatTemplate;
|
||||
import org.springframework.core.AttributeAccessor;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class SimpleChunkOrientedTaskletTests {
|
||||
|
||||
private StubItemReader itemReader = new StubItemReader();
|
||||
|
||||
private StubItemWriter itemWriter = new StubItemWriter();
|
||||
|
||||
private RepeatTemplate repeatTemplate = new RepeatTemplate();
|
||||
|
||||
private AttributeAccessor context = new RepeatContextSupport(null);
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
repeatTemplate.setCompletionPolicy(new SimpleCompletionPolicy(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandle() throws Exception {
|
||||
SimpleChunkOrientedTasklet<String, String> handler = new SimpleChunkOrientedTasklet<String, String>(itemReader,
|
||||
new PassthroughItemProcessor<String>(), itemWriter, repeatTemplate);
|
||||
StepContribution contribution = new StepContribution(new StepExecution("foo", new JobExecution(new JobInstance(
|
||||
123L, new JobParameters(), "job"))));
|
||||
handler.execute(contribution, context);
|
||||
assertEquals(2, itemReader.count);
|
||||
assertEquals("12", itemWriter.values);
|
||||
assertEquals(2, contribution.getReadCount());
|
||||
assertEquals(2, contribution.getWriteCount());
|
||||
assertEquals(0, contribution.getFilterCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleWithItemProcessorFailure() throws Exception {
|
||||
SimpleChunkOrientedTasklet<String, String> handler = new SimpleChunkOrientedTasklet<String, String>(itemReader,
|
||||
new StubItemProcessor(), itemWriter, repeatTemplate);
|
||||
StepContribution contribution = new StepContribution(new StepExecution("foo", new JobExecution(new JobInstance(
|
||||
123L, new JobParameters(), "job"))));
|
||||
try {
|
||||
handler.execute(contribution, context);
|
||||
fail("Expected ValidationException");
|
||||
}
|
||||
catch (ValidationException e) {
|
||||
// expected
|
||||
}
|
||||
assertEquals(2, itemReader.count);
|
||||
assertEquals(2, contribution.getReadCount());
|
||||
assertEquals(0, contribution.getWriteCount());
|
||||
assertEquals(0, contribution.getFilterCount());
|
||||
assertEquals("", itemWriter.values);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleCompositeItem() throws Exception {
|
||||
SimpleChunkOrientedTasklet<String, String> handler = new SimpleChunkOrientedTasklet<String, String>(itemReader,
|
||||
new AggregateItemProcessor(), itemWriter, repeatTemplate);
|
||||
StepContribution contribution = new StepContribution(new StepExecution("foo", new JobExecution(new JobInstance(
|
||||
123L, new JobParameters(), "job"))));
|
||||
handler.execute(contribution, context);
|
||||
assertEquals(2, itemReader.count);
|
||||
assertEquals(2, contribution.getReadCount());
|
||||
assertEquals(1, contribution.getFilterCount());
|
||||
assertEquals(1, contribution.getWriteCount());
|
||||
assertEquals("12", itemWriter.values);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
private final class AggregateItemProcessor implements ItemProcessor<String, String> {
|
||||
private int count = 0;
|
||||
|
||||
private String value = "";
|
||||
|
||||
public String process(String item) throws Exception {
|
||||
value += item;
|
||||
if (count++ < 1) {
|
||||
return null;
|
||||
}
|
||||
String result = value;
|
||||
value = "";
|
||||
count = 0;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
private static class StubItemProcessor implements ItemProcessor<String, String> {
|
||||
public String process(String item) throws Exception {
|
||||
if ("2".equals(item)) {
|
||||
throw new ValidationException("Planned failure");
|
||||
}
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
private static final class StubItemWriter implements ItemWriter<String> {
|
||||
private String values = "";
|
||||
|
||||
public void write(List<? extends String> items) throws Exception {
|
||||
for (String item : items) {
|
||||
values += item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
private final class StubItemReader implements ItemReader<String> {
|
||||
private int count = 0;
|
||||
|
||||
public String read() throws Exception, UnexpectedInputException, NoWorkFoundException, ParseException {
|
||||
if (count++ < 5)
|
||||
return "" + count;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.springframework.batch.core.step.item;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobInstance;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.batch.item.support.PassthroughItemProcessor;
|
||||
|
||||
public class SimpleChunkProcessorTests {
|
||||
|
||||
private SimpleChunkProcessor<String, String> processor;
|
||||
|
||||
private StepContribution contribution = new StepContribution(new StepExecution("foo", new JobExecution(
|
||||
new JobInstance(123L, new JobParameters(), "job"))));
|
||||
|
||||
protected List<String> list = new ArrayList<String>();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
processor = new SimpleChunkProcessor<String,String>(new PassthroughItemProcessor<String>(), new ItemWriter<String>() {
|
||||
public void write(List<? extends String> items) throws Exception {
|
||||
list.addAll(items);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcess() throws Exception {
|
||||
Chunk<String> chunk = new Chunk<String>();
|
||||
chunk.add("foo");
|
||||
chunk.add("bar");
|
||||
processor.process(contribution, chunk);
|
||||
assertEquals(2, list.size());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.springframework.batch.core.step.item;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobInstance;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.item.support.ListItemReader;
|
||||
import org.springframework.batch.repeat.support.RepeatTemplate;
|
||||
|
||||
public class SimpleChunkProviderTests {
|
||||
|
||||
private SimpleChunkProvider<String> provider;
|
||||
|
||||
private StepContribution contribution = new StepContribution(new StepExecution("foo", new JobExecution(
|
||||
new JobInstance(123L, new JobParameters(), "job"))));
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
provider = new SimpleChunkProvider<String>(new ListItemReader<String>(Arrays.asList("foo", "bar")),
|
||||
new RepeatTemplate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProvide() throws Exception {
|
||||
Chunk<String> chunk = provider.provide(contribution);
|
||||
assertNotNull(chunk);
|
||||
assertEquals(2, chunk.getItems().size());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,7 +15,8 @@
|
||||
*/
|
||||
package org.springframework.batch.core.step.item;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -23,36 +24,36 @@ import org.junit.Test;
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ItemWrapperTests {
|
||||
public class SkipWrapperTests {
|
||||
|
||||
private Exception exception = new RuntimeException();
|
||||
|
||||
/**
|
||||
* Test method for {@link org.springframework.batch.core.step.item.ItemWrapper#ItemWrapper(java.lang.Object)}.
|
||||
* Test method for {@link SkipWrapper#SkipWrapper(java.lang.Object)}.
|
||||
*/
|
||||
@Test
|
||||
public void testItemWrapperT() {
|
||||
ItemWrapper<String> wrapper = new ItemWrapper<String>("foo");
|
||||
SkipWrapper<String> wrapper = new SkipWrapper<String>("foo");
|
||||
assertEquals("foo", wrapper.getItem());
|
||||
assertEquals(null, wrapper.getException());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link org.springframework.batch.core.step.item.ItemWrapper#ItemWrapper(java.lang.Object, java.lang.Exception)}.
|
||||
* Test method for {@link org.springframework.batch.core.step.item.SkipWrapper#SkipWrapper(java.lang.Object, java.lang.Exception)}.
|
||||
*/
|
||||
@Test
|
||||
public void testItemWrapperTException() {
|
||||
ItemWrapper<String> wrapper = new ItemWrapper<String>("foo",exception);
|
||||
SkipWrapper<String> wrapper = new SkipWrapper<String>("foo",exception);
|
||||
assertEquals("foo", wrapper.getItem());
|
||||
assertEquals(exception, wrapper.getException());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link org.springframework.batch.core.step.item.ItemWrapper#toString()}.
|
||||
* Test method for {@link org.springframework.batch.core.step.item.SkipWrapper#toString()}.
|
||||
*/
|
||||
@Test
|
||||
public void testToString() {
|
||||
ItemWrapper<String> wrapper = new ItemWrapper<String>("foo");
|
||||
SkipWrapper<String> wrapper = new SkipWrapper<String>("foo");
|
||||
assertTrue("foo", wrapper.toString().contains("foo"));
|
||||
}
|
||||
|
||||
@@ -3,8 +3,13 @@
|
||||
*/
|
||||
package org.springframework.batch.core.step.item;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.batch.core.BatchStatus.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.springframework.batch.core.BatchStatus.COMPLETED;
|
||||
import static org.springframework.batch.core.BatchStatus.FAILED;
|
||||
import static org.springframework.batch.core.BatchStatus.STOPPED;
|
||||
import static org.springframework.batch.core.BatchStatus.UNKNOWN;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -47,7 +52,7 @@ public class TaskletStepExceptionTests {
|
||||
|
||||
UpdateCountingJobRepository jobRepository;
|
||||
|
||||
static RuntimeException taskletException = new RuntimeException();
|
||||
static RuntimeException taskletException = new RuntimeException("Static planned test exception.");
|
||||
|
||||
static JobInterruptedException interruptedException = new JobInterruptedException("");
|
||||
|
||||
|
||||
@@ -1,65 +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.aop.framework.ProxyFactory;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.transaction.interceptor.TransactionInterceptor;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class TransactionInterceptorValidatorTests extends TestCase {
|
||||
|
||||
private TransactionInterceptorValidator validator = new TransactionInterceptorValidator(1);
|
||||
|
||||
public void testValidateNull() {
|
||||
try {
|
||||
validator.validate(null);
|
||||
fail("Expected IllegalArgumentException");
|
||||
} catch (IllegalArgumentException e) {
|
||||
String message = e.getMessage();
|
||||
assertTrue("Wrong message: "+message, message.indexOf("JobRepository")>=0);
|
||||
}
|
||||
}
|
||||
|
||||
public void testValidateWithNoInterceptors() {
|
||||
validator.validate(new Object());
|
||||
}
|
||||
|
||||
public void testValidateAdvisedWithOneInterceptor() {
|
||||
validator.validate(ProxyFactory.getProxy(JobRepository.class, new TransactionInterceptor()));
|
||||
}
|
||||
|
||||
public void testValidateAdvisedWithTwoInterceptors() {
|
||||
Object target = ProxyFactory.getProxy(JobRepository.class, new TransactionInterceptor());
|
||||
ProxyFactory factory = new ProxyFactory();
|
||||
factory.setTarget(target);
|
||||
factory.addInterface(JobRepository.class);
|
||||
factory.addAdvice(new TransactionInterceptor());
|
||||
try {
|
||||
validator.validate(factory.getProxy());
|
||||
fail("Expected IllegalStateException");
|
||||
} catch (IllegalStateException e) {
|
||||
String message = e.getMessage();
|
||||
assertTrue("Wrong message: "+message, message.indexOf("JobRepository")>=0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,8 +15,9 @@
|
||||
*/
|
||||
package org.springframework.batch.core.step.tasklet;
|
||||
|
||||
import org.springframework.batch.core.step.item.FaultTolerantChunkOrientedTasklet;
|
||||
import org.springframework.batch.core.step.item.SimpleChunkOrientedTasklet;
|
||||
import org.springframework.batch.core.step.item.ChunkOrientedTasklet;
|
||||
import org.springframework.batch.core.step.item.SimpleChunkProcessor;
|
||||
import org.springframework.batch.core.step.item.SimpleChunkProvider;
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.batch.item.support.PassthroughItemProcessor;
|
||||
@@ -31,13 +32,13 @@ import org.springframework.batch.repeat.support.RepeatTemplate;
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class TestingChunkOrientedTasklet<T> extends SimpleChunkOrientedTasklet<T, T> {
|
||||
public class TestingChunkOrientedTasklet<T> extends ChunkOrientedTasklet<T> {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final RepeatTemplate repeatTemplate = new RepeatTemplate();
|
||||
|
||||
|
||||
static {
|
||||
// It's only for testing, and we don't want any infinite loops...
|
||||
repeatTemplate.setCompletionPolicy(new SimpleCompletionPolicy(6));
|
||||
@@ -45,18 +46,20 @@ public class TestingChunkOrientedTasklet<T> extends SimpleChunkOrientedTasklet<T
|
||||
|
||||
/**
|
||||
* Creates a {@link PassthroughItemProcessor} and uses it to create an
|
||||
* instance of {@link FaultTolerantChunkOrientedTasklet}.
|
||||
* instance of {@link Tasklet}.
|
||||
*/
|
||||
public TestingChunkOrientedTasklet(ItemReader<T> itemReader, ItemWriter<T> itemWriter) {
|
||||
super(itemReader, new PassthroughItemProcessor<T>(), itemWriter, repeatTemplate);
|
||||
this(itemReader, itemWriter, repeatTemplate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link PassthroughItemProcessor} and uses it to create an
|
||||
* instance of {@link FaultTolerantChunkOrientedTasklet}.
|
||||
* instance of {@link Tasklet}.
|
||||
*/
|
||||
public TestingChunkOrientedTasklet(ItemReader<T> itemReader, ItemWriter<T> itemWriter, RepeatOperations repeatOperations) {
|
||||
super(itemReader, new PassthroughItemProcessor<T>(), itemWriter, repeatOperations);
|
||||
public TestingChunkOrientedTasklet(ItemReader<T> itemReader, ItemWriter<T> itemWriter,
|
||||
RepeatOperations repeatOperations) {
|
||||
super(new SimpleChunkProvider<T>(itemReader, repeatOperations), new SimpleChunkProcessor<T, T>(
|
||||
new PassthroughItemProcessor<T>(), itemWriter));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user