OPEN - issue BATCH-791: Ditch Tasklet (StepHandler is more flexible)

Done.  Use StepHandler instead.
This commit is contained in:
dsyer
2008-08-26 09:15:13 +00:00
parent 0111123bb7
commit 207e882e37
39 changed files with 211 additions and 605 deletions

View File

@@ -13,22 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.step.tasklet;
package org.springframework.batch.core.step.handler;
import java.util.concurrent.Callable;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.AttributeAccessor;
import org.springframework.util.Assert;
/**
* Adapts a {@link Callable}<{@link ExitStatus}> to the
* {@link Tasklet} interface.
* {@link StepHandler} interface.
*
* @author Dave Syer
*
*/
public class CallableTaskletAdapter implements Tasklet, InitializingBean {
public class CallableStepHandlerAdapter implements StepHandler, InitializingBean {
private Callable<ExitStatus> callable;
@@ -50,10 +52,11 @@ public class CallableTaskletAdapter implements Tasklet, InitializingBean {
}
/**
* Execute the provided Callable and return its {@link ExitStatus}.
* @see org.springframework.batch.core.step.tasklet.Tasklet#execute()
* Execute the provided Callable and return its {@link ExitStatus}. Ignores
* the {@link StepContribution} and the attributes.
* @see StepHandler#handle(StepContribution, AttributeAccessor)
*/
public ExitStatus execute() throws Exception {
public ExitStatus handle(StepContribution contribution, AttributeAccessor attributes) throws Exception {
return callable.call();
}

View File

@@ -0,0 +1,40 @@
package org.springframework.batch.core.step.handler;
import java.util.Map;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.util.Assert;
/**
* Maps exit codes to {@link org.springframework.batch.repeat.ExitStatus}
* according to injected map. The injected map is required to contain a value
* for 'else' key, this value will be returned if the injected map
* does not contain value for the exit code returned by the system process.
*
* @author Robert Kasanicky
*/
public class ConfigurableSystemProcessExitCodeMapper implements SystemProcessExitCodeMapper {
public static final String ELSE_KEY = "else";
private Map<Object, ExitStatus> mappings;
public ExitStatus getExitStatus(int exitCode) {
ExitStatus exitStatus = mappings.get(exitCode);
if (exitStatus != null) {
return exitStatus;
} else {
return mappings.get(ELSE_KEY);
}
}
/**
* @param mappings <code>Integer</code> exit code keys to
* {@link org.springframework.batch.repeat.ExitStatus} values.
*/
public void setMappings(Map<Object, ExitStatus> mappings) {
Assert.notNull(mappings.get(ELSE_KEY));
this.mappings = mappings;
}
}

View File

@@ -0,0 +1,22 @@
package org.springframework.batch.core.step.handler;
import org.springframework.batch.repeat.ExitStatus;
/**
* Simple {@link SystemProcessExitCodeMapper} implementation that performs following mapping:
*
* 0 -> ExitStatus.FINISHED
* else -> ExitStatus.FAILED
*
* @author Robert Kasanicky
*/
public class SimpleSystemProcessExitCodeMapper implements SystemProcessExitCodeMapper {
public ExitStatus getExitStatus(int exitCode) {
if (exitCode == 0) {
return ExitStatus.FINISHED;
} else {
return ExitStatus.FAILED;
}
}
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.step.item;
package org.springframework.batch.core.step.handler;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.item.ItemReader;

View File

@@ -13,15 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.step.tasklet;
package org.springframework.batch.core.step.handler;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.item.adapter.AbstractMethodInvokingDelegator;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.core.AttributeAccessor;
/**
* A {@link Tasklet} that wraps a method in a POJO. By default the return value
* is {@link ExitStatus#FINISHED} unless the delegate POJO itself returns an
* {@link ExitStatus}. The POJO method is usually going to have no arguments,
* A {@link StepHandler} that wraps a method in a POJO. By default the return
* value is {@link ExitStatus#FINISHED} unless the delegate POJO itself returns
* an {@link ExitStatus}. The POJO method is usually going to have no arguments,
* but a static argument or array of arguments can be used by setting the
* arguments property.
*
@@ -30,15 +32,16 @@ import org.springframework.batch.repeat.ExitStatus;
* @author Dave Syer
*
*/
public class TaskletAdapter extends AbstractMethodInvokingDelegator<Object> implements Tasklet {
public class StepHandlerAdapter extends AbstractMethodInvokingDelegator<Object> implements StepHandler {
/**
* Delegate execution to the target object and translate the return value to
* an {@link ExitStatus} by invoking a method in the delegate POJO.
* an {@link ExitStatus} by invoking a method in the delegate POJO. Ignores
* the {@link StepContribution} and the attributes.
*
* @see org.springframework.batch.core.step.tasklet.Tasklet#execute()
* @see StepHandler#handle(StepContribution, AttributeAccessor)
*/
public ExitStatus execute() throws Exception {
public ExitStatus handle(StepContribution contribution, AttributeAccessor attributes) throws Exception {
return mapResult(invokeDelegateMethod());
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.step.item;
package org.springframework.batch.core.step.handler;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
@@ -79,7 +79,7 @@ public class StepHandlerStep extends AbstractStep {
private TransactionAttribute transactionAttribute = new DefaultTransactionAttribute();
private StepHandler itemHandler;
private StepHandler stepHandler;
private StepExecutionSynchronizer synchronizer;
@@ -111,10 +111,10 @@ public class StepHandlerStep extends AbstractStep {
/**
* Public setter for the {@link StepHandler}.
*
* @param itemHandler the {@link StepHandler} to set
* @param stepHandler the {@link StepHandler} to set
*/
public void setStepHandler(StepHandler itemHandler) {
this.itemHandler = itemHandler;
public void setStepHandler(StepHandler stepHandler) {
this.stepHandler = stepHandler;
}
/**
@@ -239,7 +239,7 @@ public class StepHandlerStep extends AbstractStep {
try {
try {
exitStatus = itemHandler.handle(contribution, attributes);
exitStatus = stepHandler.handle(contribution, attributes);
}
catch (Error e) {
if (transactionAttribute.rollbackOn(e)) {

View File

@@ -0,0 +1,18 @@
package org.springframework.batch.core.step.handler;
/**
* Exception indicating failed execution of system command.
*/
public class SystemCommandException extends RuntimeException {
// generated
private static final long serialVersionUID = 5139355923336176733L;
public SystemCommandException(String message) {
super(message);
}
public SystemCommandException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,177 @@
package org.springframework.batch.core.step.handler;
import java.io.File;
import java.io.IOException;
import org.apache.commons.lang.time.StopWatch;
import org.springframework.batch.core.JobInterruptedException;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.listener.StepExecutionListenerSupport;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.AttributeAccessor;
import org.springframework.util.Assert;
/**
* {@link StepHandler} that executes a system command.
*
* The system command is executed in a new thread - timeout value is required to
* be set, so that the batch job does not hang forever if the external process
* hangs.
*
* Tasklet periodically checks for termination status (i.e.
* {@link #setCommand(String)} finished its execution or
* {@link #setTimeout(long)} expired or job was interrupted). The check interval
* is given by {@link #setTerminationCheckInterval(long)}.
*
* When job interrupt is detected the thread executing the system command will
* be interrupted and tasklet's execution terminated by throwing
* {@link JobInterruptedException}.
*
* @author Robert Kasanicky
*/
public class SystemCommandStepHandler extends StepExecutionListenerSupport implements StepHandler, InitializingBean {
private String command;
private String[] environmentParams = null;
private File workingDirectory = null;
private SystemProcessExitCodeMapper systemProcessExitCodeMapper = new SimpleSystemProcessExitCodeMapper();
private long timeout = 0;
private long checkInterval = 1000;
private StepExecution execution = null;
/**
* Execute system command and map its exit code to {@link ExitStatus} using
* {@link SystemProcessExitCodeMapper}.
*/
public ExitStatus handle(StepContribution contribution, AttributeAccessor attributes) throws Exception {
ExecutorThread executorThread = new ExecutorThread();
executorThread.start();
StopWatch stopWatch = new StopWatch();
stopWatch.start();
while (stopWatch.getTime() < timeout && executorThread.isAlive() && !execution.isTerminateOnly()) {
Thread.sleep(checkInterval);
}
stopWatch.stop();
if (executorThread.finishedSuccessfully) {
return systemProcessExitCodeMapper.getExitStatus(executorThread.exitCode);
}
else {
executorThread.interrupt();
if (execution.isTerminateOnly()) {
throw new JobInterruptedException("Job interrupted while executing system command '" + command + "'");
}
else {
throw new SystemCommandException(
"Execution of system command failed (did not finish successfully within the timeout)");
}
}
}
/**
* @param command command to be executed in a separate system process
*/
public void setCommand(String command) {
this.command = command;
}
/**
* @param envp environment parameter values, inherited from parent process
* when not set (or set to null).
*/
public void setEnvironmentParams(String[] envp) {
this.environmentParams = envp;
}
/**
* @param dir working directory of the spawned process, inherited from
* parent process when not set (or set to null).
*/
public void setWorkingDirectory(String dir) {
if (dir == null) {
this.workingDirectory = null;
return;
}
this.workingDirectory = new File(dir);
Assert.isTrue(workingDirectory.exists(), "working directory must exist");
Assert.isTrue(workingDirectory.isDirectory(), "working directory value must be a directory");
}
public void afterPropertiesSet() throws Exception {
Assert.hasLength(command, "'command' property value is required");
Assert.notNull(systemProcessExitCodeMapper, "SystemProcessExitCodeMapper must be set");
Assert.isTrue(timeout > 0, "timeout value must be greater than zero");
}
/**
* @param systemProcessExitCodeMapper maps system process return value to
* <code>ExitStatus</code> returned by Tasklet.
* {@link SimpleSystemProcessExitCodeMapper} is used by default.
*/
public void setSystemProcessExitCodeMapper(SystemProcessExitCodeMapper systemProcessExitCodeMapper) {
this.systemProcessExitCodeMapper = systemProcessExitCodeMapper;
}
/**
* @param timeout upper limit for how long the execution of the external
* program is allowed to last.
*/
public void setTimeout(long timeout) {
this.timeout = timeout;
}
/**
* The time interval how often the tasklet will check for termination
* status.
*
* @param checkInterval time interval in milliseconds (1 second by default).
*/
public void setTerminationCheckInterval(long checkInterval) {
this.checkInterval = checkInterval;
}
/**
* Get a reference to {@link StepExecution} for interrupt checks during
* system command execution.
*/
public void beforeStep(StepExecution stepExecution) {
this.execution = stepExecution;
}
/**
* Thread that executes the system command.
*/
private class ExecutorThread extends Thread {
volatile int exitCode = -1;
volatile boolean finishedSuccessfully = false;
public void run() {
try {
Process process = Runtime.getRuntime().exec(command, environmentParams, workingDirectory);
exitCode = process.waitFor();
finishedSuccessfully = true;
}
catch (IOException e) {
throw new SystemCommandException("IO error while executing system command", e);
}
catch (InterruptedException e) {
throw new SystemCommandException("Interrupted while executing system command", e);
}
}
}
}

View File

@@ -0,0 +1,20 @@
package org.springframework.batch.core.step.handler;
import org.springframework.batch.core.step.handler.SystemCommandStepHandler;
import org.springframework.batch.repeat.ExitStatus;
/**
* Maps the exit code of a system process to ExitStatus value
* returned by a system command. Designed for use with the
* {@link SystemCommandStepHandler}.
*
* @author Robert Kasanicky
*/
public interface SystemProcessExitCodeMapper {
/**
* @param exitCode exit code returned by the system process
* @return ExitStatus appropriate for the <code>systemExitCode</code> parameter value
*/
ExitStatus getExitStatus(int exitCode);
}

View File

@@ -20,6 +20,7 @@ import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.step.handler.StepHandler;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
@@ -79,7 +80,7 @@ public class ItemOrientedStepHandler<T, S> implements StepHandler {
* {@link ItemProcessor} returns null, the write is omitted and another item
* taken from the reader.
*
* @see org.springframework.batch.core.step.item.StepHandler#handle(org.springframework.batch.core.StepContribution,
* @see org.springframework.batch.core.step.handler.StepHandler#handle(org.springframework.batch.core.StepContribution,
* AttributeAccessor)
*/
public ExitStatus handle(final StepContribution contribution, AttributeAccessor attributes) throws Exception {

View File

@@ -21,6 +21,8 @@ import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.batch.core.StepListener;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.handler.StepHandler;
import org.springframework.batch.core.step.handler.StepHandlerStep;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemStream;

View File

@@ -9,6 +9,7 @@ import java.util.List;
import org.springframework.batch.core.SkipListener;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.listener.CompositeSkipListener;
import org.springframework.batch.core.step.handler.StepHandlerStep;
import org.springframework.batch.core.step.skip.ItemSkipPolicy;
import org.springframework.batch.core.step.skip.LimitCheckingItemSkipPolicy;
import org.springframework.batch.core.step.skip.SkipLimitExceededException;

View File

@@ -1,57 +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.tasklet;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.repeat.ExitStatus;
/**
* Interface for encapsulating processing logic that is not natural to split
* into read-(transform)-write phases, such as invoking a system command or a
* stored procedure.<br/>
*
* Since the batch framework has no visibility inside the {@link #execute()}
* method, developers should consider implementing {@link StepExecutionListener} and
* check the {@link StepExecution#isTerminateOnly()} value for long lasting
* processes to enable prompt termination of processing on user request.<br/>
*
* It is expected the read-(transform)-write separation will be appropriate for
* most cases and developers should implement {@link ItemReader} and
* {@link ItemWriter} interfaces then (typically extending or composing provided
* implementations).<br/>
*
* @author Lucas Ward
* @author Dave Syer
* @author Robert Kasanicky
*
*/
public interface Tasklet {
/**
* Encapsulates execution logic of {@link Step}, which is unnatural to
* separate into read-(transform)-write phases.
*
* @return ExitStatus indicating success or failure
* @see org.springframework.batch.repeat.ExitStatus
*/
public ExitStatus execute() throws Exception;
}

View File

@@ -1,100 +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.tasklet;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.AbstractStep;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.util.Assert;
/**
* A {@link Step} that executes a {@link Tasklet} directly. This step does not
* manage transactions or any looping functionality. The tasklet should do this
* on its own.
*
* If the {@link Tasklet} itself implements {@link StepExecutionListener} it
* will be registered automatically, but its injected dependencies will not be.
* This is a good way to get access to job parameters and execution context if
* the tasklet is parameterized.
*
* @author Ben Hale
* @author Robert Kasanicky
*/
public class TaskletStep extends AbstractStep {
private Tasklet tasklet;
/**
* Register each of the objects as listeners.
*
* @deprecated use
* {@link #setStepExecutionListeners(StepExecutionListener[])} instead
*/
public void setStepListeners(StepExecutionListener[] listeners) {
setStepExecutionListeners(listeners);
}
/**
* Check mandatory properties.
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
Assert.notNull(tasklet, "Tasklet is mandatory for TaskletStep");
if (tasklet instanceof StepExecutionListener) {
registerStepExecutionListener((StepExecutionListener) tasklet);
}
}
/**
* Default constructor is useful for XML configuration.
*/
public TaskletStep() {
super();
}
/**
* Creates a new <code>Step</code> for executing a <code>Tasklet</code>
*
* @param tasklet The <code>Tasklet</code> to execute
* @param jobRepository The <code>JobRepository</code> to use for
* persistence of incremental state
*/
public TaskletStep(Tasklet tasklet, JobRepository jobRepository) {
this();
this.tasklet = tasklet;
setJobRepository(jobRepository);
}
/**
* Public setter for the {@link Tasklet}.
* @param tasklet the {@link Tasklet} to set
*/
public void setTasklet(Tasklet tasklet) {
this.tasklet = tasklet;
}
/**
* Delegate to tasklet.
*/
protected ExitStatus doExecute(StepExecution stepExecution) throws Exception {
return tasklet.execute();
}
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.step.tasklet;
package org.springframework.batch.core.step.handler;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -22,22 +22,19 @@ import java.util.concurrent.Callable;
import org.junit.Test;
import org.springframework.batch.repeat.ExitStatus;
public class CallableTaskletAdapterTests {
private CallableTaskletAdapter adapter = new CallableTaskletAdapter();
/**
* Test method for {@link org.springframework.batch.core.step.tasklet.CallableTaskletAdapter#execute()}.
* @throws Exception
*/
public class CallableStepHandlerAdapterTests {
private CallableStepHandlerAdapter adapter = new CallableStepHandlerAdapter();
@Test
public void testExecute() throws Exception {
public void testHandle() throws Exception {
adapter.setCallable(new Callable<ExitStatus>() {
public ExitStatus call() throws Exception {
return ExitStatus.FINISHED;
}
});
assertEquals(ExitStatus.FINISHED, adapter.execute());
assertEquals(ExitStatus.FINISHED, adapter.handle(null,null));
}
@Test

View File

@@ -0,0 +1,70 @@
package org.springframework.batch.core.step.handler;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.fail;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.springframework.batch.core.step.handler.ConfigurableSystemProcessExitCodeMapper;
import org.springframework.batch.repeat.ExitStatus;
/**
* Tests for {@link ConfigurableSystemProcessExitCodeMapper}
*/
public class ConfigurableSystemProcessExitCodeMapperTests {
private ConfigurableSystemProcessExitCodeMapper mapper = new ConfigurableSystemProcessExitCodeMapper();
/**
* Regular usage scenario - mapping adheres to injected values
*/
@Test
public void testMapping() {
Map<Object, ExitStatus> mappings = new HashMap<Object, ExitStatus>() {{
put(0, ExitStatus.FINISHED);
put(1, ExitStatus.FAILED);
put(2, ExitStatus.CONTINUABLE);
put(3, ExitStatus.NOOP);
put(4, ExitStatus.UNKNOWN);
put(ConfigurableSystemProcessExitCodeMapper.ELSE_KEY, ExitStatus.UNKNOWN);
}};
mapper.setMappings(mappings);
//check explicitly defined values
for (Map.Entry<Object, ExitStatus> entry : mappings.entrySet()) {
if (entry.getKey().equals(ConfigurableSystemProcessExitCodeMapper.ELSE_KEY)) continue;
int exitCode = (Integer) entry.getKey();
assertSame(entry.getValue(), mapper.getExitStatus(exitCode));
}
//check the else clause
assertSame(mappings.get(ConfigurableSystemProcessExitCodeMapper.ELSE_KEY),
mapper.getExitStatus(5));
}
/**
* Else clause is required in the injected map - setter checks its presence.
*/
@Test
public void testSetMappingsMissingElseClause() {
Map<Object, ExitStatus> missingElse = new HashMap<Object, ExitStatus>();
try {
mapper.setMappings(missingElse);
fail();
}
catch (IllegalArgumentException e) {
// expected
}
Map<Object, ExitStatus> containsElse = new HashMap<Object, ExitStatus>() {{
put(ConfigurableSystemProcessExitCodeMapper.ELSE_KEY, ExitStatus.FAILED);
}};
// no error expected now
mapper.setMappings(containsElse);
}
}

View File

@@ -13,8 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.step.item;
package org.springframework.batch.core.step.handler;
import org.springframework.batch.core.step.handler.StepHandler;
import org.springframework.batch.core.step.item.ItemOrientedStepHandler;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.support.PassthroughItemProcessor;

View File

@@ -0,0 +1,25 @@
package org.springframework.batch.core.step.handler;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.springframework.batch.repeat.ExitStatus;
/**
* Tests for {@link SimpleSystemProcessExitCodeMapper}.
*/
public class SimpleSystemProcessExitCodeMapperTests {
private SimpleSystemProcessExitCodeMapper mapper = new SimpleSystemProcessExitCodeMapper();
/**
* 0 -> ExitStatus.FINISHED
* else -> ExitStatus.FAILED
*/
@Test
public void testMapping() {
assertEquals(ExitStatus.FINISHED, mapper.getExitStatus(0));
assertEquals(ExitStatus.FAILED, mapper.getExitStatus(1));
assertEquals(ExitStatus.FAILED, mapper.getExitStatus(-1));
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.batch.core.step.item;
package org.springframework.batch.core.step.handler;
import java.util.List;
@@ -33,6 +33,7 @@ import org.springframework.batch.core.repository.dao.MapJobInstanceDao;
import org.springframework.batch.core.repository.dao.MapStepExecutionDao;
import org.springframework.batch.core.repository.support.SimpleJobRepository;
import org.springframework.batch.core.step.StepExecutionSynchronizer;
import org.springframework.batch.core.step.handler.StepHandlerStep;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.repeat.policy.SimpleCompletionPolicy;

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.step.tasklet;
package org.springframework.batch.core.step.handler;
import static org.junit.Assert.assertEquals;
@@ -25,9 +25,9 @@ import org.springframework.batch.repeat.ExitStatus;
* @author Dave Syer
*
*/
public class TaskletAdapterTests {
public class StepHandlerAdapterTests {
private TaskletAdapter tasklet = new TaskletAdapter();
private StepHandlerAdapter tasklet = new StepHandlerAdapter();
private Object result = null;
public ExitStatus execute() {
@@ -38,41 +38,28 @@ public class TaskletAdapterTests {
return result ;
}
/* (non-Javadoc)
* @see junit.framework.TestCase#setUp()
*/
@Before
public void setUp() throws Exception {
tasklet.setTargetObject(this);
tasklet.setTargetMethod("execute");
}
/**
* Test method for {@link org.springframework.batch.core.step.tasklet.TaskletAdapter#execute()}.
* @throws Exception
*/
@Test
public void testExecuteWithExitStatus() throws Exception {
assertEquals(ExitStatus.NOOP, tasklet.execute());
tasklet.setTargetMethod("execute");
assertEquals(ExitStatus.NOOP, tasklet.handle(null,null));
}
/**
* Test method for {@link org.springframework.batch.core.step.tasklet.TaskletAdapter#mapResult(java.lang.Object)}.
*/
@Test
public void testMapResultWithNull() throws Exception {
tasklet.setTargetMethod("process");
assertEquals(ExitStatus.FINISHED, tasklet.execute());
assertEquals(ExitStatus.FINISHED, tasklet.handle(null,null));
}
/**
* Test method for {@link org.springframework.batch.core.step.tasklet.TaskletAdapter#mapResult(java.lang.Object)}.
*/
@Test
public void testMapResultWithNonNull() throws Exception {
tasklet.setTargetMethod("process");
this.result = "foo";
assertEquals(ExitStatus.FINISHED, tasklet.execute());
assertEquals(ExitStatus.FINISHED, tasklet.handle(null,null));
}
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.step.item;
package org.springframework.batch.core.step.handler;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -39,6 +39,7 @@ import org.springframework.batch.core.repository.dao.MapJobExecutionDao;
import org.springframework.batch.core.repository.dao.MapJobInstanceDao;
import org.springframework.batch.core.repository.dao.MapStepExecutionDao;
import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
import org.springframework.batch.core.step.handler.StepHandlerStep;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;

View File

@@ -14,15 +14,20 @@
* limitations under the License.
*/
package org.springframework.batch.core.step.item;
package org.springframework.batch.core.step.handler;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import junit.framework.TestCase;
import org.junit.Before;
import org.junit.Test;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
@@ -42,6 +47,7 @@ import org.springframework.batch.core.repository.dao.MapStepExecutionDao;
import org.springframework.batch.core.repository.support.SimpleJobRepository;
import org.springframework.batch.core.step.JobRepositorySupport;
import org.springframework.batch.core.step.StepInterruptionPolicy;
import org.springframework.batch.core.step.handler.StepHandlerStep;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemStream;
@@ -58,7 +64,7 @@ import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.support.DefaultTransactionStatus;
public class StepHandlerStepTests extends TestCase {
public class StepHandlerStepTests {
List<String> processed = new ArrayList<String>();
@@ -99,7 +105,8 @@ public class StepHandlerStepTests extends TestCase {
return step;
}
protected void setUp() throws Exception {
@Before
public void setUp() throws Exception {
MapJobInstanceDao.clear();
MapStepExecutionDao.clear();
MapJobExecutionDao.clear();
@@ -119,6 +126,7 @@ public class StepHandlerStepTests extends TestCase {
}
@Test
public void testStepExecutor() throws Exception {
JobExecution jobExecutionContext = new JobExecution(jobInstance);
@@ -133,6 +141,7 @@ public class StepHandlerStepTests extends TestCase {
/**
* StepExecution should be updated after every chunk commit.
*/
@Test
public void testStepExecutionUpdates() throws Exception {
JobExecution jobExecution = new JobExecution(jobInstance);
@@ -153,6 +162,7 @@ public class StepHandlerStepTests extends TestCase {
/**
* Failure to update StepExecution after chunk commit is fatal.
*/
@Test
public void testStepExecutionUpdateFailure() throws Exception {
JobExecution jobExecution = new JobExecution(jobInstance);
@@ -175,6 +185,7 @@ public class StepHandlerStepTests extends TestCase {
}
@Test
public void testRepository() throws Exception {
SimpleJobRepository repository = new SimpleJobRepository(new MapJobInstanceDao(), new MapJobExecutionDao(),
@@ -188,6 +199,7 @@ public class StepHandlerStepTests extends TestCase {
assertEquals(1, processed.size());
}
@Test
public void testIncrementRollbackCount() {
ItemReader<String> itemReader = new ItemReader<String>() {
@@ -211,6 +223,7 @@ public class StepHandlerStepTests extends TestCase {
}
@Test
public void testExitCodeDefaultClassification() throws Exception {
ItemReader<String> itemReader = new ItemReader<String>() {
@@ -235,6 +248,7 @@ public class StepHandlerStepTests extends TestCase {
}
}
@Test
public void testExitCodeCustomClassification() throws Exception {
ItemReader<String> itemReader = new ItemReader<String>() {
@@ -270,6 +284,7 @@ public class StepHandlerStepTests extends TestCase {
* make sure a job that has never been executed before, but does have
* saveExecutionAttributes = true, doesn't have restoreFrom called on it.
*/
@Test
public void testNonRestartedJob() throws Exception {
MockRestartableItemReader tasklet = new MockRestartableItemReader();
step.setStepHandler(new SimpleStepHandler<String>(tasklet, itemWriter));
@@ -283,6 +298,7 @@ public class StepHandlerStepTests extends TestCase {
assertTrue(tasklet.isGetExecutionAttributesCalled());
}
@Test
public void testSuccessfulExecutionWithExecutionContext() throws Exception {
final JobExecution jobExecution = new JobExecution(jobInstance);
final StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
@@ -299,6 +315,7 @@ public class StepHandlerStepTests extends TestCase {
assertEquals(3, list.size());
}
@Test
public void testSuccessfulExecutionWithFailureOnSaveOfExecutionContext() throws Exception {
final JobExecution jobExecution = new JobExecution(jobInstance);
final StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
@@ -328,6 +345,7 @@ public class StepHandlerStepTests extends TestCase {
* set to false, doesn't have restore or getExecutionAttributes called on
* it.
*/
@Test
public void testNoSaveExecutionAttributesRestartableJob() {
MockRestartableItemReader tasklet = new MockRestartableItemReader();
step.setStepHandler(new SimpleStepHandler<String>(tasklet, itemWriter));
@@ -349,6 +367,7 @@ public class StepHandlerStepTests extends TestCase {
* nothing will be restored because the Tasklet does not implement
* Restartable.
*/
@Test
public void testRestartJobOnNonRestartableTasklet() throws Exception {
step.setStepHandler(new SimpleStepHandler<String>(new ItemReader<String>() {
public String read() throws Exception {
@@ -361,6 +380,7 @@ public class StepHandlerStepTests extends TestCase {
step.execute(stepExecution);
}
@Test
public void testStreamManager() throws Exception {
MockRestartableItemReader reader = new MockRestartableItemReader() {
public String read() throws Exception {
@@ -385,6 +405,7 @@ public class StepHandlerStepTests extends TestCase {
assertEquals("bar", stepExecution.getExecutionContext().getString("foo"));
}
@Test
public void testDirectlyInjectedItemStream() throws Exception {
step.setStreams(new ItemStream[] { new ItemStreamSupport() {
public void update(ExecutionContext executionContext) {
@@ -401,6 +422,7 @@ public class StepHandlerStepTests extends TestCase {
assertEquals("bar", stepExecution.getExecutionContext().getString("foo"));
}
@Test
public void testDirectlyInjectedListener() throws Exception {
step.registerStepExecutionListener(new StepExecutionListenerSupport() {
public void beforeStep(StepExecution stepExecution) {
@@ -418,6 +440,7 @@ public class StepHandlerStepTests extends TestCase {
assertEquals(2, list.size());
}
@Test
public void testListenerCalledBeforeStreamOpened() throws Exception {
MockRestartableItemReader reader = new MockRestartableItemReader() {
public void beforeStep(StepExecution stepExecution) {
@@ -435,6 +458,7 @@ public class StepHandlerStepTests extends TestCase {
assertEquals(1, list.size());
}
@Test
public void testAfterStep() throws Exception {
final ExitStatus customStatus = new ExitStatus(false, "custom code");
@@ -459,6 +483,7 @@ public class StepHandlerStepTests extends TestCase {
assertEquals(customStatus.getExitDescription(), returnedStatus.getExitDescription());
}
@Test
public void testDirectlyInjectedListenerOnError() throws Exception {
step.registerStepExecutionListener(new StepExecutionListenerSupport() {
public ExitStatus onErrorInStep(StepExecution stepExecution, Throwable e) {
@@ -483,6 +508,7 @@ public class StepHandlerStepTests extends TestCase {
assertEquals(1, list.size());
}
@Test
public void testDirectlyInjectedStreamWhichIsAlsoReader() throws Exception {
MockRestartableItemReader reader = new MockRestartableItemReader() {
public String read() throws Exception {
@@ -507,6 +533,7 @@ public class StepHandlerStepTests extends TestCase {
assertEquals("bar", stepExecution.getExecutionContext().getString("foo"));
}
@Test
public void testStatusForInterruptedException() {
StepInterruptionPolicy interruptionPolicy = new StepInterruptionPolicy() {
@@ -546,6 +573,7 @@ public class StepHandlerStepTests extends TestCase {
}
}
@Test
public void testStatusForNormalFailure() throws Exception {
ItemReader<String> itemReader = new ItemReader<String>() {
@@ -573,6 +601,7 @@ public class StepHandlerStepTests extends TestCase {
}
}
@Test
public void testStatusForErrorFailure() throws Exception {
ItemReader<String> itemReader = new ItemReader<String>() {
@@ -600,6 +629,7 @@ public class StepHandlerStepTests extends TestCase {
}
}
@Test
public void testStatusForResetFailedException() throws Exception {
ItemReader<String> itemReader = new ItemReader<String>() {
@@ -635,6 +665,7 @@ public class StepHandlerStepTests extends TestCase {
}
}
@Test
public void testStatusForCommitFailedException() throws Exception {
step.setTransactionManager(new ResourcelessTransactionManager() {
@@ -665,6 +696,7 @@ public class StepHandlerStepTests extends TestCase {
}
}
@Test
public void testStatusForFinalUpdateFailedException() throws Exception {
step.setJobRepository(new JobRepositorySupport());
@@ -693,6 +725,7 @@ public class StepHandlerStepTests extends TestCase {
}
}
@Test
public void testStatusForCloseFailedException() throws Exception {
MockRestartableItemReader itemReader = new MockRestartableItemReader() {
@@ -732,6 +765,7 @@ public class StepHandlerStepTests extends TestCase {
* commiting first chunk - otherwise ItemStreams won't recognize it is
* restart scenario on next run.
*/
@Test
public void testRestartAfterFailureInFirstChunk() throws Exception {
MockRestartableItemReader reader = new MockRestartableItemReader() {
public String read() throws Exception {
@@ -756,6 +790,7 @@ public class StepHandlerStepTests extends TestCase {
}
}
@Test
public void testStepToCompletion() throws Exception {
RepeatTemplate template = new RepeatTemplate();
@@ -777,6 +812,7 @@ public class StepHandlerStepTests extends TestCase {
* causes step to fail.
* @throws JobInterruptedException
*/
@Test
public void testStepFailureInAfterStepCallback() throws JobInterruptedException {
StepExecutionListener listener = new StepExecutionListenerSupport() {
public ExitStatus afterStep(StepExecution stepExecution) {

View File

@@ -0,0 +1,212 @@
package org.springframework.batch.core.step.handler;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
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.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobInterruptedException;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.util.Assert;
/**
* Tests for {@link SystemCommandStepHandler}.
*/
public class SystemCommandStepHandlerIntegrationTests {
private static final Log log = LogFactory.getLog(SystemCommandStepHandlerIntegrationTests.class);
private SystemCommandStepHandler tasklet = new SystemCommandStepHandler();
private StepExecution stepExecution = new StepExecution("systemCommandStep", new JobExecution(new JobInstance(
1L, new JobParameters(), "systemCommandJob")));
@Before
public void setUp() throws Exception {
tasklet.setEnvironmentParams(null); // inherit from parent process
tasklet.setWorkingDirectory(null); // inherit from parent process
tasklet.setSystemProcessExitCodeMapper(new TestExitCodeMapper());
tasklet.setTimeout(5000); // long enough timeout
tasklet.setTerminationCheckInterval(500);
tasklet.setCommand("invalid command, change value for successful execution");
tasklet.afterPropertiesSet();
tasklet.beforeStep(stepExecution);
}
/*
* Regular usage scenario - successful execution of system command.
*/
@Test
public void testExecute() throws Exception {
String command = "java -version";
tasklet.setCommand(command);
tasklet.afterPropertiesSet();
log.info("Executing command: " + command);
ExitStatus exitStatus = tasklet.handle(null,null);
assertEquals(ExitStatus.FINISHED, exitStatus);
}
/*
* Failed execution scenario - error exit code returned by system command.
*/
@Test
public void testExecuteFailure() throws Exception {
String command = "java org.springframework.batch.sample.tasklet.UnknownClass";
tasklet.setCommand(command);
tasklet.afterPropertiesSet();
log.info("Executing command: " + command);
ExitStatus exitStatus = tasklet.handle(null,null);
assertEquals(ExitStatus.FAILED, exitStatus);
}
/*
* Failed execution scenario - execution time exceeds timeout.
*/
@Test
public void testExecuteTimeout() throws Exception {
String command = "sleep 3";
tasklet.setCommand(command);
tasklet.setTimeout(10);
tasklet.afterPropertiesSet();
log.info("Executing command: " + command);
try {
tasklet.handle(null,null);
fail();
}
catch (SystemCommandException e) {
assertTrue(e.getMessage().indexOf("did not finish successfully within the timeout") > 0);
}
}
/*
* Job interrupted scenario.
*/
@Test
public void testInterruption() throws Exception {
String command = "sleep 5";
tasklet.setCommand(command);
tasklet.setTerminationCheckInterval(10);
tasklet.afterPropertiesSet();
stepExecution.setTerminateOnly();
try {
tasklet.handle(null,null);
fail();
}
catch (JobInterruptedException e) {
System.out.println(e.getMessage());
assertTrue(e.getMessage().indexOf("Job interrupted while executing system command") > -1);
assertTrue(e.getMessage().indexOf(command) > -1);
}
}
/*
* Command property value is required to be set.
*/
@Test
public void testCommandNotSet() throws Exception {
tasklet.setCommand(null);
try {
tasklet.afterPropertiesSet();
fail();
}
catch (IllegalArgumentException e) {
// expected
}
tasklet.setCommand("");
try {
tasklet.afterPropertiesSet();
fail();
}
catch (IllegalArgumentException e) {
// expected
}
}
/*
* Timeout must be set to non-zero value.
*/
@Test
public void testTimeoutNotSet() throws Exception {
tasklet.setCommand("not-empty placeholder");
tasklet.setTimeout(0);
try {
tasklet.afterPropertiesSet();
fail();
}
catch (IllegalArgumentException e) {
// expected
}
}
/*
* Working directory property must point to an existing location and it must
* be a directory
*/
@Test
public void testWorkingDirectory() throws Exception {
File notExistingFile = new File("not-existing-path");
Assert.state(!notExistingFile.exists());
try {
tasklet.setWorkingDirectory(notExistingFile.getCanonicalPath());
fail();
}
catch (IllegalArgumentException e) {
// expected
}
File notDirectory = File.createTempFile(this.getClass().getName(), null);
Assert.state(notDirectory.exists());
Assert.state(!notDirectory.isDirectory());
try {
tasklet.setWorkingDirectory(notDirectory.getCanonicalPath());
fail();
}
catch (IllegalArgumentException e) {
// expected
}
File directory = notDirectory.getParentFile();
Assert.state(directory.exists());
Assert.state(directory.isDirectory());
// no error expected now
tasklet.setWorkingDirectory(directory.getCanonicalPath());
}
/**
* Exit code mapper containing mapping logic expected by the tests. 0 means
* finished successfully, other value means failure.
*/
private static class TestExitCodeMapper implements SystemProcessExitCodeMapper {
public ExitStatus getExitStatus(int exitCode) {
if (exitCode == 0) {
return ExitStatus.FINISHED;
}
else {
return ExitStatus.FAILED;
}
}
}
}

View File

@@ -1,260 +0,0 @@
package org.springframework.batch.core.step.tasklet;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import junit.framework.TestCase;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobInterruptedException;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.batch.core.UnexpectedJobExecutionException;
import org.springframework.batch.core.listener.StepExecutionListenerSupport;
import org.springframework.batch.core.step.JobRepositorySupport;
import org.springframework.batch.repeat.ExitStatus;
public class TaskletStepTests extends TestCase {
private StepExecution stepExecution;
private List<Serializable> list = new ArrayList<Serializable>();
protected void setUp() throws Exception {
stepExecution = new StepExecution("stepName", new JobExecution(new JobInstance(new Long(0L),
new JobParameters(), "testJob"), new Long(12)));
}
public void testTaskletMandatory() throws Exception {
TaskletStep step = new TaskletStep();
step.setJobRepository(new JobRepositorySupport());
try {
step.afterPropertiesSet();
}
catch (IllegalArgumentException e) {
String message = e.getMessage();
assertTrue("Message should contain 'tasklet': " + message, contains(message.toLowerCase(), "tasklet"));
}
}
public void testRepositoryMandatory() throws Exception {
TaskletStep step = new TaskletStep();
try {
step.afterPropertiesSet();
fail();
}
catch (IllegalArgumentException e) {
String message = e.getMessage();
assertTrue("Message should contain 'mandatory': " + message, contains(message.toLowerCase(), "mandatory"));
}
}
public void testSuccessfulExecution() throws Exception {
TaskletStep step = new TaskletStep(new StubTasklet(false, false), new JobRepositorySupport());
step.execute(stepExecution);
assertNotNull(stepExecution.getStartTime());
assertEquals(ExitStatus.FINISHED, stepExecution.getExitStatus());
assertNotNull(stepExecution.getEndTime());
}
public void testSuccessfulExecutionWithStepContext() throws Exception {
TaskletStep step = new TaskletStep(new StubTasklet(false, false, true), new JobRepositorySupport());
step.afterPropertiesSet();
step.execute(stepExecution);
assertNotNull(stepExecution.getStartTime());
assertEquals(ExitStatus.FINISHED, stepExecution.getExitStatus());
assertNotNull(stepExecution.getEndTime());
}
public void testSuccessfulExecutionWithExecutionContext() throws Exception {
TaskletStep step = new TaskletStep(new StubTasklet(false, false), new JobRepositorySupport() {
public void updateExecutionContext(StepExecution stepExecution) {
list.add(stepExecution);
}
});
step.execute(stepExecution);
assertEquals(1, list.size());
}
public void testSuccessfulExecutionWithFailureOnSaveOfExecutionContext() throws Exception {
TaskletStep step = new TaskletStep(new StubTasklet(false, false, true), new JobRepositorySupport() {
public void updateExecutionContext(StepExecution stepExecution) {
throw new RuntimeException("foo");
}
});
step.afterPropertiesSet();
try {
step.execute(stepExecution);
fail("Expected BatchCriticalException");
}
catch (UnexpectedJobExecutionException e) {
assertEquals("foo", e.getCause().getMessage());
}
assertEquals(BatchStatus.UNKNOWN, stepExecution.getStatus());
}
public void testFailureExecution() throws Exception {
TaskletStep step = new TaskletStep(new StubTasklet(true, false), new JobRepositorySupport());
step.execute(stepExecution);
assertNotNull(stepExecution.getStartTime());
assertEquals(ExitStatus.FAILED, stepExecution.getExitStatus());
assertNotNull(stepExecution.getEndTime());
}
public void testSuccessfulExecutionWithListener() throws Exception {
TaskletStep step = new TaskletStep(new StubTasklet(false, false), new JobRepositorySupport());
step.setStepExecutionListeners(new StepExecutionListener[] { new StepExecutionListenerSupport() {
public void beforeStep(StepExecution context) {
list.add("open");
}
public ExitStatus afterStep(StepExecution stepExecution) {
list.add("close");
return ExitStatus.CONTINUABLE;
}
} });
step.execute(stepExecution);
assertEquals(2, list.size());
}
public void testExceptionExecution() throws JobInterruptedException, UnexpectedJobExecutionException {
TaskletStep step = new TaskletStep(new StubTasklet(false, true), new JobRepositorySupport());
try {
step.execute(stepExecution);
fail();
}
catch (RuntimeException e) {
assertNotNull(stepExecution.getStartTime());
assertEquals(ExitStatus.FAILED.getExitCode(), stepExecution.getExitStatus().getExitCode());
assertNotNull(stepExecution.getEndTime());
}
}
public void testExceptionError() throws JobInterruptedException, UnexpectedJobExecutionException {
TaskletStep step = new TaskletStep(new StubTasklet(new Error("Foo!")), new JobRepositorySupport());
try {
step.execute(stepExecution);
fail();
}
catch (Error e) {
assertNotNull(stepExecution.getStartTime());
assertEquals(ExitStatus.FAILED.getExitCode(), stepExecution.getExitStatus().getExitCode());
assertNotNull(stepExecution.getEndTime());
}
}
/**
* When job is interrupted the {@link JobInterruptedException} should be
* propagated up.
*/
public void testJobInterrupted() throws Exception {
TaskletStep step = new TaskletStep(new Tasklet() {
public ExitStatus execute() throws Exception {
throw new JobInterruptedException("Job interrupted while executing tasklet");
}
}, new JobRepositorySupport());
try {
step.execute(stepExecution);
fail();
}
catch (JobInterruptedException expected) {
assertEquals("Job interrupted while executing tasklet", expected.getMessage());
}
}
/**
* Exception in {@link StepExecutionListener#afterStep(StepExecution)}
* causes step to fail.
* @throws JobInterruptedException
*/
public void testStepFailureInAfterStepCallback() throws JobInterruptedException {
TaskletStep step = new TaskletStep(new Tasklet() {
public ExitStatus execute() throws Exception {
return ExitStatus.FINISHED;
}
}, new JobRepositorySupport());
StepExecutionListener listener = new StepExecutionListenerSupport() {
public ExitStatus afterStep(StepExecution stepExecution) {
throw new RuntimeException("exception thrown in afterStep to signal failure");
}
};
step.setStepExecutionListeners(new StepExecutionListener[] { listener });
try {
step.execute(stepExecution);
fail();
}
catch (RuntimeException expected) {
assertEquals("exception thrown in afterStep to signal failure", expected.getMessage());
}
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
}
private static class StubTasklet extends StepExecutionListenerSupport implements Tasklet {
private final boolean exitFailure;
private final boolean throwException;
private final boolean assertStepContext;
private StepExecution stepExecution;
private Throwable exception = null;
public StubTasklet(boolean exitFailure, boolean throwException) {
this(exitFailure, throwException, false);
}
public StubTasklet(boolean exitFailure, boolean throwException, boolean assertStepContext) {
this.exitFailure = exitFailure;
this.throwException = throwException;
this.assertStepContext = assertStepContext;
}
/**
* @param error
*/
public StubTasklet(Throwable error) {
this(false, false, false);
this.exception = error;
}
public ExitStatus execute() throws Exception {
if (throwException) {
throw new Exception();
}
if (exception!=null) {
if (exception instanceof Exception) throw (Exception) exception;
if (exception instanceof Error) throw (Error) exception;
}
if (exitFailure) {
return ExitStatus.FAILED;
}
if (assertStepContext) {
assertNotNull(this.stepExecution);
}
return ExitStatus.FINISHED;
}
public void beforeStep(StepExecution stepExecution) {
this.stepExecution = stepExecution;
}
}
private boolean contains(String str, String searchStr) {
return str.indexOf(searchStr) != -1;
}
}