/*
* Copyright 2008-2014 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.mockito.Mockito.mock;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import java.util.Arrays;
import java.util.Collection;
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.BatchStatus;
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.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.step.JobRepositorySupport;
import org.springframework.batch.core.step.factory.FaultTolerantStepFactoryBean;
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.util.StringUtils;
public class FaultTolerantStepFactoryBeanNonBufferingTests {
protected final Log logger = LogFactory.getLog(getClass());
private FaultTolerantStepFactoryBean<String, String> factory = new FaultTolerantStepFactoryBean<String, String>();
private List<String> items = Arrays.asList(new String[] { "1", "2", "3", "4", "5" });
private ListItemReader<String> reader = new ListItemReader<String>(TransactionAwareProxyFactory
.createTransactionalList(items));
private SkipWriterStub writer = new SkipWriterStub();
private JobExecution jobExecution;
private static final SkippableRuntimeException exception = new SkippableRuntimeException("exception in writer");
int count = 0;
@Before
public void setUp() throws Exception {
factory.setBeanName("stepName");
factory.setJobRepository(new JobRepositorySupport());
factory.setTransactionManager(new ResourcelessTransactionManager());
factory.setCommitInterval(2);
factory.setItemReader(reader);
factory.setItemWriter(writer);
Map<Class<? extends Throwable>, Boolean> skippableExceptions = new HashMap<Class<? extends Throwable>, Boolean>();
skippableExceptions.put(SkippableException.class, true);
skippableExceptions.put(SkippableRuntimeException.class, true);
factory.setSkippableExceptionClasses(skippableExceptions);
factory.setSkipLimit(2);
factory.setIsReaderTransactionalQueue(true);
JobInstance jobInstance = new JobInstance(new Long(1), "skipJob");
jobExecution = new JobExecution(jobInstance, new JobParameters());
}
/**
* Check items causing errors are skipped as expected.
*/
@Test
public void testSkip() throws Exception {
@SuppressWarnings("unchecked")
SkipListener<Integer, String> skipListener = mock(SkipListener.class);
skipListener.onSkipInWrite("3", exception);
skipListener.onSkipInWrite("4", exception);
factory.setListeners(new SkipListener[] { skipListener });
Step step = factory.getObject();
StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
step.execute(stepExecution);
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
assertEquals(2, stepExecution.getSkipCount());
assertEquals(0, stepExecution.getReadSkipCount());
assertEquals(2, stepExecution.getWriteSkipCount());
// 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);
// 5 items + 1 rollbacks reading 2 items each time
assertEquals(7, stepExecution.getReadCount());
}
/**
* Simple item writer that supports skip functionality.
*/
private static class SkipWriterStub implements ItemWriter<String> {
protected final Log logger = LogFactory.getLog(getClass());
// simulate transactional output
private List<Object> written = TransactionAwareProxyFactory.createTransactionalList();
private final Collection<String> failures;
public SkipWriterStub() {
this(Arrays.asList("4"));
}
/**
* @param failures commaDelimitedListToSet
*/
public SkipWriterStub(Collection<String> failures) {
this.failures = failures;
}
@Override
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 + "]");
throw exception;
}
written.add(item);
}
}
}
}