OPEN - issue BATCH-364: StepScope responsibilities can be assumed by Step (not ApplicationContext)
http://jira.springframework.org/browse/BATCH-364 Instead of lazy open, throw exception is stream is used before open().
This commit is contained in:
@@ -60,16 +60,20 @@ import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Simple implementation of executing the step as a set of chunks, each chunk surrounded by a transaction. The structure
|
||||
* is therefore that of two nested loops, with transaction boundary around the whole inner loop. The outer loop is
|
||||
* controlled by the step operations ({@link #setStepOperations(RepeatOperations)}), and the inner loop by the chunk
|
||||
* operations ({@link #setChunkOperations(RepeatOperations)}). The inner loop should always be executed in a single
|
||||
* thread, so the chunk operations should not do any concurrent execution. N.B. usually that means that the chunk
|
||||
* operations should be a {@link RepeatTemplate} (which is the default).<br/>
|
||||
* Simple implementation of executing the step as a set of chunks, each chunk
|
||||
* surrounded by a transaction. The structure is therefore that of two nested
|
||||
* loops, with transaction boundary around the whole inner loop. The outer loop
|
||||
* is controlled by the step operations ({@link #setStepOperations(RepeatOperations)}),
|
||||
* and the inner loop by the chunk operations ({@link #setChunkOperations(RepeatOperations)}).
|
||||
* The inner loop should always be executed in a single thread, so the chunk
|
||||
* operations should not do any concurrent execution. N.B. usually that means
|
||||
* that the chunk operations should be a {@link RepeatTemplate} (which is the
|
||||
* default).<br/>
|
||||
*
|
||||
* Clients can use interceptors in the step operations to intercept or listen to the iteration on a step-wide basis, for
|
||||
* instance to get a callback when the step is complete. Those that want callbacks at the level of an individual tasks,
|
||||
* can specify interceptors for the chunk operations.
|
||||
* Clients can use interceptors in the step operations to intercept or listen to
|
||||
* the iteration on a step-wide basis, for instance to get a callback when the
|
||||
* step is complete. Those that want callbacks at the level of an individual
|
||||
* tasks, can specify interceptors for the chunk operations.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Lucas Ward
|
||||
@@ -114,18 +118,22 @@ public class SimpleStepExecutor implements InitializingBean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link StreamManager}. This will be used to create the {@link StepContext}, and hence any
|
||||
* component that is a {@link ItemStream} and in step scope will be registered with the service. The
|
||||
* {@link StepContext} is then a source of aggregate statistics for the step.
|
||||
* Public setter for the {@link StreamManager}. This will be used to create
|
||||
* the {@link StepContext}, and hence any component that is a
|
||||
* {@link ItemStream} and in step scope will be registered with the service.
|
||||
* The {@link StepContext} is then a source of aggregate statistics for the
|
||||
* step.
|
||||
*
|
||||
* @param streamManager the {@link StreamManager} to set. Default is a {@link SimpleStreamManager}.
|
||||
* @param streamManager the {@link StreamManager} to set. Default is a
|
||||
* {@link SimpleStreamManager}.
|
||||
*/
|
||||
public void setStreamManager(StreamManager streamManager) {
|
||||
this.streamManager = streamManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injected strategy for storage and retrieval of persistent step information. Mandatory property.
|
||||
* Injected strategy for storage and retrieval of persistent step
|
||||
* information. Mandatory property.
|
||||
*
|
||||
* @param jobRepository
|
||||
*/
|
||||
@@ -134,8 +142,9 @@ public class SimpleStepExecutor implements InitializingBean {
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link RepeatOperations} to use for the outer loop of the batch processing. Should be set up by the caller
|
||||
* through a factory. Defaults to a plain {@link RepeatTemplate}.
|
||||
* The {@link RepeatOperations} to use for the outer loop of the batch
|
||||
* processing. Should be set up by the caller through a factory. Defaults to
|
||||
* a plain {@link RepeatTemplate}.
|
||||
*
|
||||
* @param stepOperations a {@link RepeatOperations} instance.
|
||||
*/
|
||||
@@ -144,8 +153,9 @@ public class SimpleStepExecutor implements InitializingBean {
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link RepeatOperations} to use for the inner loop of the batch processing. Should be set up by the caller
|
||||
* through a factory. Defaults to a plain {@link RepeatTemplate}.
|
||||
* The {@link RepeatOperations} to use for the inner loop of the batch
|
||||
* processing. Should be set up by the caller through a factory. Defaults to
|
||||
* a plain {@link RepeatTemplate}.
|
||||
*
|
||||
* @param chunkOperations a {@link RepeatOperations} instance.
|
||||
*/
|
||||
@@ -154,8 +164,9 @@ public class SimpleStepExecutor implements InitializingBean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the {@link StepInterruptionPolicy}. The policy is used to check whether an external request has been
|
||||
* made to interrupt the job execution.
|
||||
* Setter for the {@link StepInterruptionPolicy}. The policy is used to
|
||||
* check whether an external request has been made to interrupt the job
|
||||
* execution.
|
||||
*
|
||||
* @param interruptionPolicy a {@link StepInterruptionPolicy}
|
||||
*/
|
||||
@@ -164,8 +175,8 @@ public class SimpleStepExecutor implements InitializingBean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the {@link ExitStatusExceptionClassifier} that will be used to classify any exception that causes a job
|
||||
* to fail.
|
||||
* Setter for the {@link ExitStatusExceptionClassifier} that will be used to
|
||||
* classify any exception that causes a job to fail.
|
||||
*
|
||||
* @param exceptionClassifier
|
||||
*/
|
||||
@@ -186,7 +197,7 @@ public class SimpleStepExecutor implements InitializingBean {
|
||||
public void setItemWriter(ItemWriter itemWriter) {
|
||||
this.itemWriter = itemWriter;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Setter for injecting optional recovery handler.
|
||||
*
|
||||
@@ -223,7 +234,7 @@ public class SimpleStepExecutor implements InitializingBean {
|
||||
|
||||
if (retryPolicy != null) {
|
||||
Assert.state(itemReader instanceof KeyedItemReader,
|
||||
"ItemReader must be instance of KeyedItemReader to use the retry policy");
|
||||
"ItemReader must be instance of KeyedItemReader to use the retry policy");
|
||||
retryCallback = new ItemReaderRetryCallback((KeyedItemReader) itemReader, itemWriter);
|
||||
retryCallback.setRecoverer(itemRecoverer);
|
||||
}
|
||||
@@ -231,7 +242,8 @@ public class SimpleStepExecutor implements InitializingBean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the configuration by inspecting it to see if it has any relevant policy information.
|
||||
* Apply the configuration by inspecting it to see if it has any relevant
|
||||
* policy information.
|
||||
*
|
||||
* @param step a step
|
||||
*/
|
||||
@@ -261,14 +273,18 @@ public class SimpleStepExecutor implements InitializingBean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the step and update its context so that progress can be monitored by the caller. The step is broken down
|
||||
* into chunks, each one executing in a transaction. The step and its execution and execution context are all given
|
||||
* an up to date {@link BatchStatus}, and the {@link JobRepository} is used to store the result. Various reporting
|
||||
* information are also added to the current context (the {@link RepeatContext} governing the step execution, which
|
||||
* would normally be available to the caller somehow through the step's {@link JobExecutionContext}.<br/>
|
||||
* Process the step and update its context so that progress can be monitored
|
||||
* by the caller. The step is broken down into chunks, each one executing in
|
||||
* a transaction. The step and its execution and execution context are all
|
||||
* given an up to date {@link BatchStatus}, and the {@link JobRepository}
|
||||
* is used to store the result. Various reporting information are also added
|
||||
* to the current context (the {@link RepeatContext} governing the step
|
||||
* execution, which would normally be available to the caller somehow
|
||||
* through the step's {@link JobExecutionContext}.<br/>
|
||||
*
|
||||
* @throws JobInterruptedException if the step or a chunk is interrupted
|
||||
* @throws RuntimeException if there is an exception during a chunk execution
|
||||
* @throws RuntimeException if there is an exception during a chunk
|
||||
* execution
|
||||
* @see StepExecutor#execute(StepExecution)
|
||||
*/
|
||||
public void execute(final StepExecution stepExecution) throws BatchCriticalException, JobInterruptedException {
|
||||
@@ -342,31 +358,36 @@ public class SimpleStepExecutor implements InitializingBean {
|
||||
|
||||
streamManager.commit(transaction);
|
||||
|
||||
} catch (Throwable t) {
|
||||
}
|
||||
catch (Throwable t) {
|
||||
/*
|
||||
* Any exception thrown within the transaction template will automatically cause the transaction
|
||||
* to rollback. We need to include exceptions during an attempted commit (e.g. Hibernate flush)
|
||||
* so this catch block comes outside the transaction.
|
||||
* Any exception thrown within the transaction template
|
||||
* will automatically cause the transaction to rollback.
|
||||
* We need to include exceptions during an attempted
|
||||
* commit (e.g. Hibernate flush) so this catch block
|
||||
* comes outside the transaction.
|
||||
*/
|
||||
synchronized (stepExecution) {
|
||||
stepExecution.rollback();
|
||||
}
|
||||
try {
|
||||
streamManager.rollback(transaction);
|
||||
} catch (ResetFailedException e) {
|
||||
}
|
||||
catch (ResetFailedException e) {
|
||||
// The original Throwable cause is in danger of
|
||||
// being lost here, so we log the reset
|
||||
// failure and re-throw with cause of the rollback.
|
||||
logger.error("Encountered reset error on rollback: "
|
||||
+ "one of the streams may be in an inconsistent state, "
|
||||
+ "so this step should not proceed", e);
|
||||
+ "one of the streams may be in an inconsistent state, "
|
||||
+ "so this step should not proceed", e);
|
||||
throw new ResetFailedException("Encountered reset error on rollback. "
|
||||
+ "Consult logs for the cause of the reet failure. "
|
||||
+ "The cause of the original rollback is incuded here.", t);
|
||||
+ "Consult logs for the cause of the reet failure. "
|
||||
+ "The cause of the original rollback is incuded here.", t);
|
||||
}
|
||||
if (t instanceof RuntimeException) {
|
||||
throw (RuntimeException) t;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
@@ -382,27 +403,32 @@ public class SimpleStepExecutor implements InitializingBean {
|
||||
});
|
||||
|
||||
updateStatus(stepExecution, BatchStatus.COMPLETED);
|
||||
} catch (RuntimeException e) {
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
|
||||
// classify exception so an exit code can be stored.
|
||||
status = exceptionClassifier.classifyForExitCode(e);
|
||||
if (e.getCause() instanceof JobInterruptedException) {
|
||||
updateStatus(stepExecution, BatchStatus.STOPPED);
|
||||
throw (JobInterruptedException) e.getCause();
|
||||
} else if (e instanceof ResetFailedException) {
|
||||
}
|
||||
else if (e instanceof ResetFailedException) {
|
||||
updateStatus(stepExecution, BatchStatus.UNKNOWN);
|
||||
throw (ResetFailedException) e;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
updateStatus(stepExecution, BatchStatus.FAILED);
|
||||
throw e;
|
||||
}
|
||||
|
||||
} finally {
|
||||
}
|
||||
finally {
|
||||
stepExecution.setExitStatus(status);
|
||||
stepExecution.setEndTime(new Date(System.currentTimeMillis()));
|
||||
try {
|
||||
jobRepository.saveOrUpdate(stepExecution);
|
||||
} finally {
|
||||
}
|
||||
finally {
|
||||
// clear any registered synchronizations
|
||||
StepSynchronizationManager.close();
|
||||
}
|
||||
@@ -411,11 +437,13 @@ public class SimpleStepExecutor implements InitializingBean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a bunch of identical business logic operations all within a transaction. The transaction is
|
||||
* programmatically started and stopped outside this method, so subclasses that override do not need to create a
|
||||
* Execute a bunch of identical business logic operations all within a
|
||||
* transaction. The transaction is programmatically started and stopped
|
||||
* outside this method, so subclasses that override do not need to create a
|
||||
* transaction.
|
||||
*
|
||||
* @param step the current step containing the {@link Tasklet} with the business logic.
|
||||
* @param step the current step containing the {@link Tasklet} with the
|
||||
* business logic.
|
||||
* @return true if there is more data to process.
|
||||
*/
|
||||
ExitStatus processChunk(final Step step, final StepContribution contribution) {
|
||||
@@ -437,10 +465,12 @@ public class SimpleStepExecutor implements InitializingBean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the business logic, delegating to the given {@link Tasklet}. Subclasses could extend the behaviour as
|
||||
* long as they always return the value of this method call in their superclass.<br/>
|
||||
* Execute the business logic, delegating to the given {@link Tasklet}.
|
||||
* Subclasses could extend the behaviour as long as they always return the
|
||||
* value of this method call in their superclass.<br/>
|
||||
*
|
||||
* If there is an exception and the {@link Tasklet} implements {@link Skippable} then the skip method is called.
|
||||
* If there is an exception and the {@link Tasklet} implements
|
||||
* {@link Skippable} then the skip method is called.
|
||||
*
|
||||
* @param tasklet the unit of business logic to execute
|
||||
* @param contribution the current step
|
||||
@@ -454,7 +484,8 @@ public class SimpleStepExecutor implements InitializingBean {
|
||||
|
||||
exitStatus = execute();
|
||||
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
skip();
|
||||
// Rethrow so that outer transaction is rolled back properly
|
||||
throw e;
|
||||
@@ -465,13 +496,16 @@ public class SimpleStepExecutor implements InitializingBean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Read from the {@link ItemReader} and process (if not null) with the {@link ItemWriter}. The call to
|
||||
* {@link ItemWriter} is wrapped in a stateful retry, if a {@link RetryPolicy} is provided. The
|
||||
* {@link ItemRecoverer} is used (if provided) in the case of an exception to apply alternate processing to the
|
||||
* item. If the stateful retry is in place then the recovery will happen in the next transaction automatically,
|
||||
* otherwise it might be necessary for clients to make the recover method transactional with appropriate propagation
|
||||
* behaviour (probably REQUIRES_NEW because the call will happen in the context of a transaction that is about to
|
||||
* rollback).
|
||||
* Read from the {@link ItemReader} and process (if not null) with the
|
||||
* {@link ItemWriter}. The call to {@link ItemWriter} is wrapped in a
|
||||
* stateful retry, if a {@link RetryPolicy} is provided. The
|
||||
* {@link ItemRecoverer} is used (if provided) in the case of an exception
|
||||
* to apply alternate processing to the item. If the stateful retry is in
|
||||
* place then the recovery will happen in the next transaction
|
||||
* automatically, otherwise it might be necessary for clients to make the
|
||||
* recover method transactional with appropriate propagation behaviour
|
||||
* (probably REQUIRES_NEW because the call will happen in the context of a
|
||||
* transaction that is about to rollback).
|
||||
*
|
||||
* @see org.springframework.batch.core.tasklet.Tasklet#execute()
|
||||
*/
|
||||
@@ -484,7 +518,8 @@ public class SimpleStepExecutor implements InitializingBean {
|
||||
}
|
||||
try {
|
||||
itemWriter.write(item);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (itemRecoverer != null) {
|
||||
itemRecoverer.recover(item, e);
|
||||
}
|
||||
@@ -500,9 +535,10 @@ public class SimpleStepExecutor implements InitializingBean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the current item as skipped if possible. If there is a retry policy in action there is no need to take any
|
||||
* action now because it will be covered by the retry in the next transaction. Otherwise if the reader and / or
|
||||
* writer are {@link Skippable} then delegate to them in that order.
|
||||
* Mark the current item as skipped if possible. If there is a retry policy
|
||||
* in action there is no need to take any action now because it will be
|
||||
* covered by the retry in the next transaction. Otherwise if the reader and /
|
||||
* or writer are {@link Skippable} then delegate to them in that order.
|
||||
*
|
||||
* @see org.springframework.batch.io.Skippable#skip()
|
||||
*/
|
||||
|
||||
@@ -1,566 +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.execution.step.simple;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.easymock.MockControl;
|
||||
import org.springframework.batch.core.domain.BatchStatus;
|
||||
import org.springframework.batch.core.domain.JobExecution;
|
||||
import org.springframework.batch.core.domain.JobInstance;
|
||||
import org.springframework.batch.core.domain.JobParameters;
|
||||
import org.springframework.batch.core.domain.JobSupport;
|
||||
import org.springframework.batch.core.domain.StepExecution;
|
||||
import org.springframework.batch.core.domain.StepInstance;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.execution.scope.StepScope;
|
||||
import org.springframework.batch.execution.scope.StepSynchronizationManager;
|
||||
import org.springframework.batch.item.ExecutionAttributes;
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.batch.item.exception.StreamException;
|
||||
import org.springframework.batch.item.reader.ListItemReader;
|
||||
import org.springframework.batch.item.stream.ItemStreamAdapter;
|
||||
import org.springframework.batch.item.stream.SimpleStreamManager;
|
||||
import org.springframework.batch.item.writer.AbstractItemWriter;
|
||||
import org.springframework.batch.repeat.ExitStatus;
|
||||
import org.springframework.batch.repeat.RepeatContext;
|
||||
import org.springframework.batch.repeat.interceptor.RepeatListenerAdapter;
|
||||
import org.springframework.batch.repeat.policy.SimpleCompletionPolicy;
|
||||
import org.springframework.batch.repeat.support.RepeatTemplate;
|
||||
import org.springframework.batch.support.PropertiesConverter;
|
||||
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Lucas Ward
|
||||
*
|
||||
*/
|
||||
public class ChunkedStepTests extends TestCase {
|
||||
|
||||
ArrayList processed = new ArrayList();
|
||||
|
||||
ItemWriter processor = new AbstractItemWriter() {
|
||||
public void write(Object data) throws Exception {
|
||||
processed.add((String) data);
|
||||
}
|
||||
};
|
||||
|
||||
private ChunkedStep chunkedStep;
|
||||
|
||||
private JobInstance jobInstance;
|
||||
|
||||
private ResourcelessTransactionManager transactionManager;
|
||||
|
||||
private StepInstance stepInstance;
|
||||
private JobExecution jobExecutionContext;
|
||||
private StepExecution stepExecution;
|
||||
|
||||
private ItemReader getReader(String[] args) {
|
||||
return new ListItemReader(Arrays.asList(args));
|
||||
}
|
||||
|
||||
|
||||
|
||||
private ChunkedStep getStep(String[] strings) throws Exception {
|
||||
ChunkedStep step = new ChunkedStep();
|
||||
step.setItemWriter(processor);
|
||||
step.setItemReader(getReader(strings));
|
||||
step.setJobRepository(new JobRepositorySupport());
|
||||
step.setStreamManager(new SimpleStreamManager(transactionManager));
|
||||
step.afterPropertiesSet();
|
||||
return step;
|
||||
}
|
||||
|
||||
protected void setUp() throws Exception {
|
||||
transactionManager = new ResourcelessTransactionManager();
|
||||
chunkedStep = getStep(new String[] { "foo", "bar", "spam" });
|
||||
|
||||
// Only process one item:
|
||||
chunkedStep.setChunkSize(1);
|
||||
|
||||
jobInstance = new JobInstance(new Long(0), new JobParameters());
|
||||
jobInstance.setJob(new JobSupport("FOO"));
|
||||
|
||||
SimpleStreamManager streamManager = new SimpleStreamManager(transactionManager);
|
||||
streamManager.setUseClassNameAsPrefix(false);
|
||||
chunkedStep.setStreamManager(streamManager);
|
||||
chunkedStep.setJobRepository(new JobRepositorySupport());
|
||||
|
||||
stepInstance = new StepInstance(new Long(9));
|
||||
jobExecutionContext = new JobExecution(jobInstance);
|
||||
stepExecution = new StepExecution(stepInstance, jobExecutionContext);
|
||||
}
|
||||
|
||||
public void testStepExecutor() throws Exception {
|
||||
|
||||
chunkedStep.execute(stepExecution);
|
||||
assertEquals(3, processed.size());
|
||||
assertEquals(stepExecution.getStatus(), BatchStatus.COMPLETED);
|
||||
}
|
||||
|
||||
public void testStepContextInitialized() throws Exception {
|
||||
|
||||
final StepInstance step = new StepInstance(new Long(1));
|
||||
final JobExecution jobExecution = new JobExecution(jobInstance);
|
||||
final StepExecution stepExecution = new StepExecution(step, jobExecution);
|
||||
|
||||
chunkedStep.setChunker(new ItemChunker(new ItemReader() {
|
||||
int counter = 0;
|
||||
public Object read() throws Exception {
|
||||
assertEquals(step, stepExecution.getStep());
|
||||
assertNotNull(StepSynchronizationManager.getContext().getStepExecution());
|
||||
if(counter++ < 2){
|
||||
return "foo";
|
||||
}
|
||||
else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
chunkedStep.execute(stepExecution);
|
||||
assertEquals(2, processed.size());
|
||||
}
|
||||
|
||||
public void testStepContextInitializedBeforeTasklet() throws Exception {
|
||||
|
||||
RepeatTemplate template = new RepeatTemplate();
|
||||
|
||||
// Only process one chunk:
|
||||
template.setCompletionPolicy(new SimpleCompletionPolicy(1));
|
||||
chunkedStep.setStepOperations(template);
|
||||
|
||||
final StepInstance step = new StepInstance(new Long(1));
|
||||
final JobExecution jobExecution = new JobExecution(jobInstance);
|
||||
jobExecution.setId(new Long(1));
|
||||
final StepExecution stepExecution = new StepExecution(step, jobExecution);
|
||||
|
||||
template.setListener(new RepeatListenerAdapter() {
|
||||
public void open(RepeatContext context) {
|
||||
assertNotNull(StepSynchronizationManager.getContext().getStepExecution());
|
||||
assertEquals(stepExecution, StepSynchronizationManager.getContext().getStepExecution());
|
||||
// StepScope can obtain id information....
|
||||
assertNotNull(StepSynchronizationManager.getContext().getAttribute(StepScope.ID_KEY));
|
||||
}
|
||||
});
|
||||
|
||||
chunkedStep.execute(stepExecution);
|
||||
assertEquals(1, processed.size());
|
||||
|
||||
}
|
||||
|
||||
public void testRepository() throws Exception {
|
||||
|
||||
MockControl repoControl = MockControl.createControl(JobRepository.class);
|
||||
JobRepository repository = (JobRepository)repoControl.getMock();
|
||||
chunkedStep.setJobRepository(repository);
|
||||
|
||||
// StepInstance step = new StepInstance(new Long(1));
|
||||
// JobExecution jobExecutionContext = new JobExecution(jobInstance);
|
||||
// StepExecution stepExecution = new StepExecution(step, jobExecutionContext);
|
||||
|
||||
repository.saveOrUpdate(stepExecution);
|
||||
repository.saveOrUpdate(stepExecution);
|
||||
repository.saveOrUpdate(stepExecution);
|
||||
repository.saveOrUpdate(stepExecution);
|
||||
repository.saveOrUpdate(stepExecution);
|
||||
repository.saveOrUpdate(stepExecution);
|
||||
repoControl.replay();
|
||||
chunkedStep.execute(stepExecution);
|
||||
assertEquals(3, processed.size());
|
||||
repoControl.verify();
|
||||
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
|
||||
}
|
||||
|
||||
//ReadFailures (meaning an item couldn't be skipped) should cause the job to
|
||||
//fail.
|
||||
public void testReadFailure() {
|
||||
|
||||
ItemReader itemReader = new ItemReader() {
|
||||
int counter = 0;
|
||||
public Object read() throws Exception {
|
||||
|
||||
counter++;
|
||||
|
||||
if (counter > 1) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
return "foo";
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
chunkedStep.setChunker(new ItemChunker(itemReader));
|
||||
|
||||
try {
|
||||
chunkedStep.execute(stepExecution);
|
||||
fail();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
assertEquals( 1, processed.size());
|
||||
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void testWriterFailure(){
|
||||
|
||||
ItemWriter itemWriter = new ItemWriter(){
|
||||
public void write(Object item) throws Exception {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
};
|
||||
|
||||
chunkedStep.setDechunker(new ItemDechunker(itemWriter));
|
||||
|
||||
try{
|
||||
chunkedStep.execute(stepExecution);
|
||||
fail();
|
||||
}
|
||||
catch(Exception ex){
|
||||
//it should rollback three times since that's default behavior for a retry template.
|
||||
assertEquals(new Integer(3), stepExecution.getRollbackCount());
|
||||
}
|
||||
}
|
||||
|
||||
public void testExitCodeDefaultClassification() throws Exception {
|
||||
|
||||
ItemReader itemReader = new ItemReader() {
|
||||
int counter = 0;
|
||||
public Object read() throws Exception {
|
||||
counter++;
|
||||
|
||||
if (counter == 1) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
return ExitStatus.CONTINUABLE;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
chunkedStep.setItemReader(itemReader);
|
||||
|
||||
try {
|
||||
chunkedStep.execute(stepExecution);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ExitStatus status = stepExecution.getExitStatus();
|
||||
assertEquals("FATAL_EXCEPTION", status.getExitCode());
|
||||
assertFalse(status.isContinuable());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* make sure a job that has never been executed before, but does have
|
||||
* saveExecutionAttributes = true, doesn't have restoreFrom called on it.
|
||||
*/
|
||||
// I can't get this test to pass, I think there is something wrong with the code I
|
||||
// pulled from SimpleStepExecutor
|
||||
// public void testNonRestartedJob() throws Exception {
|
||||
// MockRestartableItemReader reader = new MockRestartableItemReader();
|
||||
// chunkedStep.setItemReader(reader);
|
||||
// chunkedStep.setSaveExecutionAttributes(true);
|
||||
//
|
||||
// chunkedStep.execute(stepExecution);
|
||||
//
|
||||
// assertFalse(reader.isRestoreFromCalled());
|
||||
// assertTrue(reader.isGetExecutionAttributesCalled());
|
||||
// }
|
||||
//
|
||||
// /*
|
||||
// * make sure a job that has been executed before, and is therefore being
|
||||
// * restarted, is restored.
|
||||
// */
|
||||
// public void testRestartedJob() throws Exception {
|
||||
// StepInstance step = new StepInstance(new Long(1));
|
||||
// step.setStepExecutionCount(1);
|
||||
// MockRestartableTasklet tasklet = new MockRestartableTasklet();
|
||||
// chunkedStep.setItemReader(tasklet);
|
||||
// stepConfiguration.setSaveExecutionAttributes(true);
|
||||
// JobExecution jobExecutionContext = new JobExecution(jobInstance);
|
||||
// StepExecution stepExecution = new StepExecution(step, jobExecutionContext);
|
||||
//
|
||||
// stepExecution
|
||||
// .setExecutionAttributes(new ExecutionAttributes(PropertiesConverter.stringToProperties("foo=bar")));
|
||||
// step.setLastExecution(stepExecution);
|
||||
// chunkedStep.execute(stepExecution);
|
||||
//
|
||||
// assertTrue(tasklet.isRestoreFromCalled());
|
||||
// assertTrue(tasklet.isRestoreFromCalledWithSomeContext());
|
||||
// assertTrue(tasklet.isGetExecutionAttributesCalled());
|
||||
// }
|
||||
//
|
||||
// /*
|
||||
// * Test that a job that is being restarted, but has saveExecutionAttributes
|
||||
// * set to false, doesn't have restore or getExecutionAttributes called on
|
||||
// * it.
|
||||
// */
|
||||
// public void testNoSaveExecutionAttributesRestartableJob() {
|
||||
// StepInstance step = new StepInstance(new Long(1));
|
||||
// step.setStepExecutionCount(1);
|
||||
// MockRestartableTasklet tasklet = new MockRestartableTasklet();
|
||||
// stepConfiguration.setItemReader(tasklet);
|
||||
// stepConfiguration.setSaveExecutionAttributes(false);
|
||||
// JobExecution jobExecutionContext = new JobExecution(jobInstance);
|
||||
// StepExecution stepExecution = new StepExecution(step, jobExecutionContext);
|
||||
//
|
||||
// try {
|
||||
// chunkedStep.execute(stepExecution);
|
||||
// }
|
||||
// catch (Throwable t) {
|
||||
// fail();
|
||||
// }
|
||||
//
|
||||
// assertFalse(tasklet.isRestoreFromCalled());
|
||||
// assertFalse(tasklet.isGetExecutionAttributesCalled());
|
||||
// }
|
||||
//
|
||||
// /*
|
||||
// * Even though the job is restarted, and saveExecutionAttributes is true,
|
||||
// * nothing will be restored because the Tasklet does not implement
|
||||
// * Restartable.
|
||||
// */
|
||||
// public void testRestartJobOnNonRestartableTasklet() throws Exception {
|
||||
// StepInstance step = new StepInstance(new Long(1));
|
||||
// step.setStepExecutionCount(1);
|
||||
// stepConfiguration.setItemReader(new ItemReader() {
|
||||
// public Object read() throws Exception {
|
||||
// return ExitStatus.FINISHED;
|
||||
// }
|
||||
// });
|
||||
// stepConfiguration.setSaveExecutionAttributes(true);
|
||||
// JobExecution jobExecution = new JobExecution(jobInstance);
|
||||
// StepExecution stepExecution = new StepExecution(step, jobExecution);
|
||||
//
|
||||
// chunkedStep.execute(stepExecution);
|
||||
// }
|
||||
//
|
||||
//// public void testApplyConfigurationWithExceptionHandler() throws Exception {
|
||||
//// AbstractStep stepConfiguration = new SimpleStep("foo");
|
||||
//// final List list = new ArrayList();
|
||||
//// chunkedStep.setStepOperations(new RepeatTemplate() {
|
||||
//// public void setExceptionHandler(ExceptionHandler exceptionHandler) {
|
||||
//// list.add(exceptionHandler);
|
||||
//// }
|
||||
//// });
|
||||
//// stepConfiguration.setExceptionHandler(new DefaultExceptionHandler());
|
||||
//// chunkedStep.applyConfiguration(stepConfiguration);
|
||||
//// assertEquals(1, list.size());
|
||||
//// }
|
||||
////
|
||||
//// public void testApplyConfigurationWithZeroSkipLimit() throws Exception {
|
||||
//// AbstractStep stepConfiguration = new SimpleStep("foo");
|
||||
//// stepConfiguration.setSkipLimit(0);
|
||||
//// final List list = new ArrayList();
|
||||
//// chunkedStep.setStepOperations(new RepeatTemplate() {
|
||||
//// public void setExceptionHandler(ExceptionHandler exceptionHandler) {
|
||||
//// list.add(exceptionHandler);
|
||||
//// }
|
||||
//// });
|
||||
//// chunkedStep.applyConfiguration(stepConfiguration);
|
||||
//// assertEquals(0, list.size());
|
||||
//// }
|
||||
////
|
||||
//// public void testApplyConfigurationWithNonZeroSkipLimit() throws Exception {
|
||||
//// AbstractStep stepConfiguration = new SimpleStep("foo");
|
||||
//// stepConfiguration.setSkipLimit(1);
|
||||
//// final List list = new ArrayList();
|
||||
//// chunkedStep.setStepOperations(new RepeatTemplate() {
|
||||
//// public void setExceptionHandler(ExceptionHandler exceptionHandler) {
|
||||
//// list.add(exceptionHandler);
|
||||
//// }
|
||||
//// });
|
||||
//// chunkedStep.applyConfiguration(stepConfiguration);
|
||||
//// assertEquals(1, list.size());
|
||||
//// }
|
||||
//
|
||||
// public void testStreamManager() throws Exception {
|
||||
// StepInstance step = new StepInstance(new Long(1));
|
||||
// step.setStepExecutionCount(1);
|
||||
// stepConfiguration.setItemReader(new ItemReader() {
|
||||
// public Object read() throws Exception {
|
||||
// return ExitStatus.FINISHED;
|
||||
// }
|
||||
// });
|
||||
// stepConfiguration.setSaveExecutionAttributes(true);
|
||||
// JobExecution jobExecution = new JobExecution(jobInstance);
|
||||
// StepExecution stepExecution = new StepExecution(step, jobExecution);
|
||||
//
|
||||
// assertEquals(false, stepExecution.getExecutionAttributes().containsKey("foo"));
|
||||
//
|
||||
// final Map map = new HashMap();
|
||||
// chunkedStep.setStreamManager(new SimpleStreamManager(new ResourcelessTransactionManager()) {
|
||||
// public ExecutionAttributes getExecutionAttributes(Object key) {
|
||||
// // TODO Auto-generated method stub
|
||||
// return new ExecutionAttributes(PropertiesConverter.stringToProperties("foo=bar"));
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// chunkedStep.execute(stepExecution);
|
||||
//
|
||||
// // At least once in that process the statistics service was asked for
|
||||
// // statistics...
|
||||
// assertEquals("bar", stepExecution.getExecutionAttributes().getString("foo"));
|
||||
// // ...but nothing was registered because nothing with step scoped.
|
||||
// assertEquals(0, map.size());
|
||||
// }
|
||||
//
|
||||
// public void testStatusForInterruptedException() {
|
||||
//
|
||||
// StepInterruptionPolicy interruptionPolicy = new StepInterruptionPolicy() {
|
||||
//
|
||||
// public void checkInterrupted(RepeatContext context) throws JobInterruptedException {
|
||||
// throw new JobInterruptedException("");
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// chunkedStep.setInterruptionPolicy(interruptionPolicy);
|
||||
//
|
||||
// ItemReader itemReader = new ItemReader() {
|
||||
//
|
||||
// public Object read() throws Exception {
|
||||
// int counter = 0;
|
||||
// counter++;
|
||||
//
|
||||
// if (counter == 1) {
|
||||
// throw new RuntimeException();
|
||||
// }
|
||||
//
|
||||
// return ExitStatus.CONTINUABLE;
|
||||
// }
|
||||
//
|
||||
// };
|
||||
//
|
||||
// chunkedStep.setItemReader(itemReader);
|
||||
//
|
||||
// StepInstance step = new StepInstance(new Long(1));
|
||||
// JobExecution jobExecutionContext = new JobExecution(jobInstance);
|
||||
// StepExecution stepExecution = new StepExecution(step, jobExecutionContext);
|
||||
//
|
||||
// stepExecution
|
||||
// .setExecutionAttributes(new ExecutionAttributes(PropertiesConverter.stringToProperties("foo=bar")));
|
||||
// step.setLastExecution(stepExecution);
|
||||
//
|
||||
// try {
|
||||
// chunkedStep.execute(stepExecution);
|
||||
// fail("Expected StepInterruptedException");
|
||||
// }
|
||||
// catch (JobInterruptedException ex) {
|
||||
// assertEquals(BatchStatus.STOPPED, stepExecution.getStatus());
|
||||
// String msg = stepExecution.getExitStatus().getExitDescription();
|
||||
// assertTrue("Message does not contain JobInterruptedException: " + msg, msg
|
||||
// .contains("JobInterruptedException"));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public void testStatusForResetFailedException() throws Exception {
|
||||
//
|
||||
// ItemReader itemReader = new ItemReader() {
|
||||
// public Object read() throws Exception {
|
||||
// // Trigger a rollback
|
||||
// throw new RuntimeException("Foo");
|
||||
// }
|
||||
// };
|
||||
// chunkedStep.setItemReader(itemReader);
|
||||
// chunkedStep.setStreamManager(new SimpleStreamManager(transactionManager) {
|
||||
// public void rollback(TransactionStatus status) {
|
||||
// super.rollback(status);
|
||||
// // Simulate failure on rollback when stream resets
|
||||
// throw new ResetFailedException("Bar");
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// StepInstance step = new StepInstance(new Long(1));
|
||||
// JobExecution jobExecutionContext = jobInstance.createJobExecution();
|
||||
// StepExecution stepExecution = new StepExecution(step, jobExecutionContext);
|
||||
//
|
||||
// stepExecution
|
||||
// .setExecutionAttributes(new ExecutionAttributes(PropertiesConverter.stringToProperties("foo=bar")));
|
||||
// step.setLastExecution(stepExecution);
|
||||
//
|
||||
// try {
|
||||
// chunkedStep.execute(stepExecution);
|
||||
// fail("Expected ResetFailedException");
|
||||
// }
|
||||
// catch (ResetFailedException ex) {
|
||||
// assertEquals(BatchStatus.UNKNOWN, stepExecution.getStatus());
|
||||
// String msg = stepExecution.getExitStatus().getExitDescription();
|
||||
// assertTrue("Message does not contain ResetFailedException: " + msg, msg.contains("ResetFailedException"));
|
||||
// // The original rollback was caused by this one:
|
||||
// assertEquals("Foo", ex.getCause().getMessage());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
private class MockRestartableItemReader extends ItemStreamAdapter implements ItemReader {
|
||||
|
||||
private boolean getExecutionAttributesCalled = false;
|
||||
|
||||
private boolean restoreFromCalled = false;
|
||||
|
||||
private boolean restoreFromCalledWithSomeContext = false;
|
||||
|
||||
private int counter = 0;
|
||||
|
||||
public Object read() throws Exception {
|
||||
StepSynchronizationManager.getContext().setAttribute("TASKLET_TEST", this);
|
||||
counter++;
|
||||
if(counter > 4){
|
||||
return "item";
|
||||
}
|
||||
else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRestoreFromCalledWithSomeContext() {
|
||||
return restoreFromCalledWithSomeContext;
|
||||
}
|
||||
|
||||
public ExecutionAttributes getExecutionAttributes() {
|
||||
getExecutionAttributesCalled = true;
|
||||
return new ExecutionAttributes(PropertiesConverter.stringToProperties("spam=bucket"));
|
||||
}
|
||||
|
||||
public void restoreFrom(ExecutionAttributes data) {
|
||||
restoreFromCalled = true;
|
||||
restoreFromCalledWithSomeContext = data.getProperties().size() > 0;
|
||||
}
|
||||
|
||||
public boolean isGetExecutionAttributesCalled() {
|
||||
return getExecutionAttributesCalled;
|
||||
}
|
||||
|
||||
public boolean isRestoreFromCalled() {
|
||||
return restoreFromCalled;
|
||||
}
|
||||
|
||||
public void open() throws StreamException {
|
||||
}
|
||||
|
||||
public void close() throws StreamException {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user