/*
* Copyright 2009-2012 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.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.job.SimpleJob;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.UnexpectedRollbackException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* @author Dan Garrette
* @since 2.0.2
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@FixMethodOrder(MethodSorters.JVM)
public class FaultTolerantExceptionClassesTests implements ApplicationContextAware {
@Autowired
private JobRepository jobRepository;
@Autowired
private JobLauncher jobLauncher;
@Autowired
private SkipReaderStub<String> reader;
@Autowired
private SkipWriterStub<String> writer;
@Autowired
private ExceptionThrowingTaskletStub tasklet;
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Before
public void setup() {
reader.clear();
writer.clear();
}
@Test
public void testNonSkippable() throws Exception {
writer.setExceptionType(RuntimeException.class);
StepExecution stepExecution = launchStep("nonSkippableStep");
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
assertEquals("[1, 2, 3]", writer.getWritten().toString());
assertEquals("[]", writer.getCommitted().toString());
}
@Test
public void testNonSkippableChecked() throws Exception {
writer.setExceptionType(Exception.class);
StepExecution stepExecution = launchStep("nonSkippableStep");
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
assertEquals("[1, 2, 3]", writer.getWritten().toString());
assertEquals("[]", writer.getCommitted().toString());
}
@Test
public void testSkippable() throws Exception {
writer.setExceptionType(SkippableRuntimeException.class);
StepExecution stepExecution = launchStep("skippableStep");
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
assertEquals("[1, 2, 3, 1, 2, 3, 4]", writer.getWritten().toString());
assertEquals("[1, 2, 4]", writer.getCommitted().toString());
}
@Test
public void testRegularRuntimeExceptionNotSkipped() throws Exception {
writer.setExceptionType(RuntimeException.class);
StepExecution stepExecution = launchStep("skippableStep");
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
// BATCH-1327:
assertEquals("[1, 2, 3]", writer.getWritten().toString());
// BATCH-1327:
assertEquals("[]", writer.getCommitted().toString());
}
@Test
public void testFatalOverridesSkippable() throws Exception {
writer.setExceptionType(FatalRuntimeException.class);
StepExecution stepExecution = launchStep("skippableFatalStep");
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
assertEquals("[1, 2, 3]", writer.getWritten().toString());
assertEquals("[]", writer.getCommitted().toString());
}
@Test
public void testDefaultFatalChecked() throws Exception {
writer.setExceptionType(Exception.class);
StepExecution stepExecution = launchStep("skippableFatalStep");
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
// BATCH-1327:
assertEquals("[1, 2, 3]", writer.getWritten().toString());
// BATCH-1327:
assertEquals("[]", writer.getCommitted().toString());
}
@Test
public void testSkippableChecked() throws Exception {
writer.setExceptionType(SkippableException.class);
StepExecution stepExecution = launchStep("skippableStep");
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
assertEquals("[1, 2, 3, 1, 2, 3, 4]", writer.getWritten().toString());
assertEquals("[1, 2, 4]", writer.getCommitted().toString());
}
@Test
public void testNonSkippableUnchecked() throws Exception {
writer.setExceptionType(UnexpectedRollbackException.class);
StepExecution stepExecution = launchStep("skippableStep");
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
assertEquals("[1, 2, 3]", writer.getWritten().toString());
assertEquals("[]", writer.getCommitted().toString());
}
@Test
public void testFatalChecked() throws Exception {
writer.setExceptionType(FatalSkippableException.class);
StepExecution stepExecution = launchStep("skippableFatalStep");
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
assertEquals("[1, 2, 3]", writer.getWritten().toString());
assertEquals("[]", writer.getCommitted().toString());
}
@Test
public void testRetryableButNotSkippable() throws Exception {
writer.setExceptionType(RuntimeException.class);
StepExecution stepExecution = launchStep("retryable");
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
assertEquals("[1, 2, 3, 1, 2, 3]", writer.getWritten().toString());
// BATCH-1327:
assertEquals("[]", writer.getCommitted().toString());
}
@Test
public void testRetryableSkippable() throws Exception {
writer.setExceptionType(SkippableRuntimeException.class);
StepExecution stepExecution = launchStep("retryable");
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
assertEquals("[1, 2, 3, 1, 2, 3, 1, 2, 3, 4]", writer.getWritten().toString());
assertEquals("[1, 2, 4]", writer.getCommitted().toString());
}
@Test
public void testRetryableFatal() throws Exception {
// User wants all exceptions to be retried, but only some are skippable
// FatalRuntimeException is not skippable because it is fatal, but is a
// subclass of another skippable
writer.setExceptionType(FatalRuntimeException.class);
StepExecution stepExecution = launchStep("retryable");
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
// BATCH-1333:
assertEquals("[1, 2, 3, 1, 2, 3]", writer.getWritten().toString());
assertEquals("[]", writer.getCommitted().toString());
}
@Test
public void testRetryableButNotSkippableChecked() throws Exception {
writer.setExceptionType(Exception.class);
StepExecution stepExecution = launchStep("retryable");
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
assertEquals("[1, 2, 3, 1, 2, 3]", writer.getWritten().toString());
// BATCH-1327:
assertEquals("[]", writer.getCommitted().toString());
}
@Test
public void testRetryableSkippableChecked() throws Exception {
writer.setExceptionType(SkippableException.class);
StepExecution stepExecution = launchStep("retryable");
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
assertEquals("[1, 2, 3, 1, 2, 3, 1, 2, 3, 4]", writer.getWritten().toString());
assertEquals("[1, 2, 4]", writer.getCommitted().toString());
}
@Test
public void testRetryableFatalChecked() throws Exception {
writer.setExceptionType(FatalSkippableException.class);
StepExecution stepExecution = launchStep("retryable");
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
// BATCH-1333:
assertEquals("[1, 2, 3, 1, 2, 3]", writer.getWritten().toString());
assertEquals("[]", writer.getCommitted().toString());
assertEquals(0, stepExecution.getWriteSkipCount());
}
@Test
public void testNoRollbackDefaultRollbackException() throws Exception {
// Exception is neither no-rollback nor skippable
writer.setExceptionType(Exception.class);
StepExecution stepExecution = launchStep("noRollbackDefault");
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
// BATCH-1318:
assertEquals("[1, 2, 3]", writer.getWritten().toString());
// BATCH-1318:
assertEquals("[]", writer.getCommitted().toString());
assertEquals(0, stepExecution.getWriteSkipCount());
}
@Test
public void testNoRollbackDefaultNoRollbackException() throws Exception {
// Exception is no-rollback and not skippable
writer.setExceptionType(IllegalStateException.class);
StepExecution stepExecution = launchStep("noRollbackDefault");
assertNotNull(stepExecution);
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
// BATCH-1334:
assertEquals("[1, 2, 3, 1, 2, 3, 4]", writer.getWritten().toString());
// BATCH-1334:
assertEquals("[1, 2, 3, 4]", writer.getCommitted().toString());
// BATCH-1334:
assertEquals(0, stepExecution.getWriteSkipCount());
}
@Test
public void testNoRollbackPathology() throws Exception {
// Exception is neither no-rollback nor skippable and no-rollback is
// RuntimeException (potentially pathological because other obviously
// rollback signalling Exceptions also extend RuntimeException)
writer.setExceptionType(Exception.class);
StepExecution stepExecution = launchStep("noRollbackPathology");
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
// BATCH-1335:
assertEquals("[1, 2, 3]", writer.getWritten().toString());
// BATCH-1335:
assertEquals("[]", writer.getCommitted().toString());
}
@Test
public void testNoRollbackSkippableRollbackException() throws Exception {
writer.setExceptionType(SkippableRuntimeException.class);
StepExecution stepExecution = launchStep("noRollbackSkippable");
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
assertEquals("[1, 2, 3, 1, 2, 3, 4]", writer.getWritten().toString());
assertEquals("[1, 2, 4]", writer.getCommitted().toString());
}
@Test
public void testNoRollbackSkippableNoRollbackException() throws Exception {
writer.setExceptionType(FatalRuntimeException.class);
StepExecution stepExecution = launchStep("noRollbackSkippable");
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
// BATCH-1332:
assertEquals("[1, 2, 3, 1, 2, 3, 4]", writer.getWritten().toString());
// BATCH-1334:
// Skipped but also committed (because it was marked as no-rollback)
assertEquals("[1, 2, 3, 4]", writer.getCommitted().toString());
assertEquals(1, stepExecution.getWriteSkipCount());
}
@Test
public void testNoRollbackFatalRollbackException() throws Exception {
writer.setExceptionType(SkippableRuntimeException.class);
StepExecution stepExecution = launchStep("noRollbackFatal");
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
assertEquals("[1, 2, 3]", writer.getWritten().toString());
assertEquals("[]", writer.getCommitted().toString());
}
@Test
public void testNoRollbackFatalNoRollbackException() throws Exception {
// User has asked for no rollback on a fatal exception. What should the
// outcome be? As per BATCH-1333 it is interpreted as not skippable, but
// retryable if requested. Here it was not requested to be retried, but
// it was marked as no-rollback. As per BATCH-1334 this has to be ignored
// so that the failed item can be isolated.
writer.setExceptionType(FatalRuntimeException.class);
StepExecution stepExecution = launchStep("noRollbackFatal");
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
// BATCH-1331:
assertEquals("[1, 2, 3, 1, 2, 3, 4]", writer.getWritten().toString());
// BATCH-1331:
assertEquals("[1, 2, 3, 4]", writer.getCommitted().toString());
}
@Test
public void testNoRollbackTaskletRollbackException() throws Exception {
tasklet.setExceptionType(RuntimeException.class);
StepExecution stepExecution = launchStep("noRollbackTasklet");
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
assertEquals("[]", tasklet.getCommitted().toString());
}
@Test
public void testNoRollbackTaskletNoRollbackException() throws Exception {
tasklet.setExceptionType(SkippableRuntimeException.class);
StepExecution stepExecution = launchStep("noRollbackTasklet");
// assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
// BATCH-1298:
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
assertEquals("[1, 1, 1, 1]", tasklet.getCommitted().toString());
}
private StepExecution launchStep(String stepName) throws Exception {
SimpleJob job = new SimpleJob();
job.setName("job");
job.setJobRepository(jobRepository);
List<Step> stepsToExecute = new ArrayList<Step>();
stepsToExecute.add((Step) applicationContext.getBean(stepName));
job.setSteps(stepsToExecute);
JobExecution jobExecution = jobLauncher.run(job, new JobParametersBuilder().addLong("timestamp",
new Date().getTime()).toJobParameters());
return jobExecution.getStepExecutions().iterator().next();
}
}