OPEN - issue BATCH-791: Ditch Tasklet (StepHandler is more flexible)
Done. Use StepHandler instead.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user