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

@@ -1,40 +0,0 @@
package org.springframework.batch.sample.common;
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

@@ -1,22 +0,0 @@
package org.springframework.batch.sample.common;
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

@@ -1,18 +0,0 @@
package org.springframework.batch.sample.common;
/**
* 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

@@ -1,176 +0,0 @@
package org.springframework.batch.sample.common;
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.StepExecution;
import org.springframework.batch.core.listener.StepExecutionListenerSupport;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
/**
* {@link Tasklet} 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 SystemCommandTasklet extends StepExecutionListenerSupport implements Tasklet, 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 execute() 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

@@ -1,19 +0,0 @@
package org.springframework.batch.sample.common;
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 SystemCommandTasklet}.
*
* @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

@@ -2,28 +2,30 @@ package org.springframework.batch.sample.tasklet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.listener.StepExecutionListenerSupport;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.core.step.handler.StepHandler;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.core.AttributeAccessor;
/**
* Dummy tasklet that retrieves message from the job execution context.
*/
public class DummyMessageReceivingTasklet extends StepExecutionListenerSupport implements Tasklet {
public class DummyMessageReceivingStepHandler extends StepExecutionListenerSupport implements StepHandler {
private static final Log logger = LogFactory.getLog(DummyMessageReceivingTasklet.class);
private static final Log logger = LogFactory.getLog(DummyMessageReceivingStepHandler.class);
private String receivedMessage = null;
public void beforeStep(StepExecution stepExecution) {
ExecutionContext ctx = stepExecution.getJobExecution().getExecutionContext();
receivedMessage = ctx.getString(DummyMessageSendingTasklet.MESSAGE_KEY);
receivedMessage = ctx.getString(DummyMessageSendingStepHandler.MESSAGE_KEY);
logger.info("Got message from context: " + receivedMessage);
}
public ExitStatus execute() throws Exception {
public ExitStatus handle(StepContribution contribution, AttributeAccessor attributes) throws Exception {
return ExitStatus.FINISHED;
}

View File

@@ -2,20 +2,22 @@ package org.springframework.batch.sample.tasklet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.listener.StepExecutionListenerSupport;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.core.step.handler.StepHandler;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.core.AttributeAccessor;
/**
* Dummy tasklet that stores a message in the job execution context.
*/
public class DummyMessageSendingTasklet extends StepExecutionListenerSupport implements Tasklet {
public class DummyMessageSendingStepHandler extends StepExecutionListenerSupport implements StepHandler {
private static final Log logger = LogFactory.getLog(DummyMessageSendingTasklet.class);
private static final Log logger = LogFactory.getLog(DummyMessageSendingStepHandler.class);
public static final String MESSAGE_KEY = "DummyMessageSendingTasklet.MESSAGE";
public static final String MESSAGE_KEY = "DummyMessageSendingStepHandler.MESSAGE";
private String message = "Hello!";
@@ -25,8 +27,8 @@ public class DummyMessageSendingTasklet extends StepExecutionListenerSupport imp
logger.info("Put message into context: " + message);
return null;
}
public ExitStatus execute() throws Exception {
public ExitStatus handle(StepContribution contribution, AttributeAccessor attributes) throws Exception {
return ExitStatus.FINISHED;
}

View File

@@ -0,0 +1,40 @@
package org.springframework.batch.sample.tasklet;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.UnexpectedJobExecutionException;
import org.springframework.batch.core.step.handler.StepHandler;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.AttributeAccessor;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
/**
* Deletes files from an array of resources, so a pattern can be used in
* configuration, e.g. <code>resources="/home/batch/job/**"</code>
*
* @author Robert Kasanicky
*/
public class FileDeletingStepHandler implements StepHandler, InitializingBean {
private Resource[] resources;
public ExitStatus handle(StepContribution contribution, AttributeAccessor attributes) throws Exception {
for (Resource resource : resources) {
boolean deleted = resource.getFile().delete();
if (!deleted) {
throw new UnexpectedJobExecutionException("Could not delete file " + resource);
}
}
return ExitStatus.FINISHED;
}
public void setResources(Resource[] resources) {
this.resources = resources;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(resources, "Resources must be set");
}
}

View File

@@ -1,44 +0,0 @@
package org.springframework.batch.sample.tasklet;
import java.io.File;
import org.springframework.batch.core.UnexpectedJobExecutionException;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
/**
* Deletes files in given directory. Ignores subdirectories. Fails (by throwing
* exception) if any of the files could not be deleted.
*
* @author Robert Kasanicky
*/
public class FileDeletingTasklet implements Tasklet, InitializingBean {
private Resource directory;
public ExitStatus execute() throws Exception {
File dir = directory.getFile();
Assert.state(dir.isDirectory());
File[] files = dir.listFiles();
for (File file : files) {
boolean deleted = file.delete();
if (!deleted) {
throw new UnexpectedJobExecutionException("Could not delete file " + file.getPath());
}
}
return ExitStatus.FINISHED;
}
public void setDirectoryResource(Resource directory) {
this.directory = directory;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(directory, "directory must be set");
}
}

View File

@@ -1,14 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<description>
Deletes files in given directory - TaskletStep is used as this
is the kind of task that is not natural to split into read and
@@ -17,23 +13,19 @@
The second step illustrates executing a system command.
</description>
<bean id="taskletJob" parent="simpleJob">
<bean id="handlerJob" parent="simpleJob">
<property name="steps">
<list>
<bean id="deleteFilesInDir" parent="taskletStep">
<bean id="deleteFilesInDir" parent="handlerStep">
<property name="tasklet">
<bean
class="org.springframework.batch.sample.tasklet.FileDeletingTasklet">
<property name="directoryResource"
ref="directory" />
<bean class="org.springframework.batch.sample.tasklet.FileDeletingStepHandler">
<property name="resources" value="target/test-outputs/test-dir/*" />
</bean>
</property>
</bean>
<bean id="executeSystemCommand" parent="taskletStep">
<bean id="executeSystemCommand" parent="handlerStep">
<property name="tasklet">
<bean
class="org.springframework.batch.sample.common.SystemCommandTasklet">
<bean class="org.springframework.batch.core.step.handler.SystemCommandStepHandler">
<property name="command" value="java -version" />
<!-- 5 second timeout for the command to complete -->
<property name="timeout" value="5000" />
@@ -42,12 +34,5 @@
</bean>
</list>
</property>
</bean>
<bean id="directory"
class="org.springframework.core.io.FileSystemResource">
<constructor-arg value="target/test-outputs/test-dir" />
</bean>
</beans>

View File

@@ -11,10 +11,10 @@
<bean id="jobExecutionContextSample" parent="simpleJob">
<property name="steps">
<list>
<bean id="step1" parent="taskletStep">
<bean id="step1" parent="handlerStep">
<property name="tasklet" ref="sender" />
</bean>
<bean id="step2" parent="taskletStep">
<bean id="step2" parent="handlerStep">
<property name="tasklet" ref="receiver" />
</bean>
</list>
@@ -22,11 +22,11 @@
</bean>
<bean id="sender"
class="org.springframework.batch.sample.tasklet.DummyMessageSendingTasklet">
class="org.springframework.batch.sample.tasklet.DummyMessageSendingStepHandler">
<property name="message" value="Hey!" />
</bean>
<bean id="receiver"
class="org.springframework.batch.sample.tasklet.DummyMessageReceivingTasklet" />
class="org.springframework.batch.sample.tasklet.DummyMessageReceivingStepHandler" />
</beans>

View File

@@ -12,7 +12,8 @@
<property name="restartable" value="true" />
</bean>
<bean id="taskletStep" class="org.springframework.batch.core.step.tasklet.TaskletStep" abstract="true">
<bean id="handlerStep" class="org.springframework.batch.core.step.handler.StepHandlerStep" abstract="true">
<property name="transactionManager" ref="transactionManager" />
<property name="jobRepository" ref="jobRepository" />
<property name="allowStartIfComplete" value="true" />
</bean>

View File

@@ -4,7 +4,7 @@ import java.io.File;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -17,10 +17,9 @@ import org.springframework.util.Assert;
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration()
public class TaskletJobFunctionalTests extends AbstractValidatingBatchLauncherTests {
public class HandlerJobFunctionalTests extends AbstractValidatingBatchLauncherTests {
@Autowired
private Resource directory;
private Resource directory = new FileSystemResource("target/test-outputs/test-dir");
/*
* Create the directory and some files in it.

View File

@@ -3,8 +3,8 @@ package org.springframework.batch.sample;
import static org.junit.Assert.assertEquals;
import org.junit.runner.RunWith;
import org.springframework.batch.sample.tasklet.DummyMessageReceivingTasklet;
import org.springframework.batch.sample.tasklet.DummyMessageSendingTasklet;
import org.springframework.batch.sample.tasklet.DummyMessageReceivingStepHandler;
import org.springframework.batch.sample.tasklet.DummyMessageSendingStepHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -14,10 +14,10 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
public class JobExecutionContextSampleFunctionalTests extends AbstractValidatingBatchLauncherTests {
@Autowired
private DummyMessageSendingTasklet sender;
private DummyMessageSendingStepHandler sender;
@Autowired
private DummyMessageReceivingTasklet receiver;
private DummyMessageReceivingStepHandler receiver;
protected void validatePostConditions() throws Exception {
assertEquals(sender.getMessage(), receiver.getReceivedMessage());

View File

@@ -1,69 +0,0 @@
package org.springframework.batch.sample.common;
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.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

@@ -1,25 +0,0 @@
package org.springframework.batch.sample.common;
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

@@ -1,212 +0,0 @@
package org.springframework.batch.sample.common;
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 SystemCommandTasklet}.
*/
public class SystemCommandTaskletIntegrationTests {
private static final Log log = LogFactory.getLog(SystemCommandTaskletIntegrationTests.class);
private SystemCommandTasklet tasklet = new SystemCommandTasklet();
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.execute();
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.execute();
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.execute();
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.execute();
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

@@ -5,6 +5,6 @@
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<import resource="classpath:/simple-job-launcher-context.xml" />
<import resource="classpath:/jobs/taskletJob.xml" />
<import resource="classpath:/jobs/handlerJob.xml" />
</beans>