Skip to content

Commit 68046e1

Browse files
author
Philippe Truche
committed
Support command as array of strings
setCommand now supports passing in an array of strings. When a single string is supplied, it is tokenized by the Runtime#exec method. When an array of strings is supplied, the array is supplied as is to the Runtime#exec method in which case no tokenization takes place. Resolves spring-projects#752
1 parent 83579cf commit 68046e1

File tree

2 files changed

+67
-8
lines changed

2 files changed

+67
-8
lines changed

spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/SystemCommandTasklet.java

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
* so that the batch job does not hang forever if the external process hangs.
4747
*
4848
* Tasklet periodically checks for termination status (i.e.
49-
* {@link #setCommand(String)} finished its execution or
49+
* {@link #setCommand(String...)} finished its execution or
5050
* {@link #setTimeout(long)} expired or job was interrupted). The check interval
5151
* is given by {@link #setTerminationCheckInterval(long)}.
5252
*
@@ -64,7 +64,7 @@ public class SystemCommandTasklet extends StepExecutionListenerSupport implement
6464

6565
protected static final Log logger = LogFactory.getLog(SystemCommandTasklet.class);
6666

67-
private String command;
67+
private String[] cmdArray;
6868

6969
private String[] environmentParams = null;
7070

@@ -100,8 +100,15 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon
100100

101101
@Override
102102
public Integer call() throws Exception {
103-
Process process = Runtime.getRuntime().exec(command, environmentParams, workingDirectory);
104-
return process.waitFor();
103+
if (cmdArray.length == 1) {
104+
String command = cmdArray[0];
105+
Process process = Runtime.getRuntime().exec(command, environmentParams, workingDirectory);
106+
return process.waitFor();
107+
} else {
108+
Process process = Runtime.getRuntime().exec(cmdArray, environmentParams, workingDirectory);
109+
return process.waitFor();
110+
}
111+
105112
}
106113

107114
});
@@ -132,6 +139,7 @@ else if (System.currentTimeMillis() - t0 > timeout) {
132139
}
133140
else if (execution.isTerminateOnly()) {
134141
systemCommandTask.cancel(interruptOnCancel);
142+
String command = String.join(" ", cmdArray);
135143
throw new JobInterruptedException("Job interrupted while executing system command '" + command + "'");
136144
}
137145
else if (stopped) {
@@ -143,10 +151,17 @@ else if (stopped) {
143151
}
144152

145153
/**
146-
* @param command command to be executed in a separate system process
154+
* @param command command to be executed in a separate system process. Either a single command can be supplied
155+
* to be tokenized with a space delimiter, or the command and its arguments are supplied as multiple
156+
* strings that are not tokenized.
157+
* <p>
158+
* <p>Possible calls to setCommand:
159+
*
160+
* <pre> {@code setCommand("myCommand myArg1 myArg2");}</pre>
161+
* <pre> {@code setCommand("myCommand", "myArg1", "myArg2 'args for myArg2'");}</pre>
147162
*/
148-
public void setCommand(String command) {
149-
this.command = command;
163+
public void setCommand(String... command) {
164+
this.cmdArray = command ;
150165
}
151166

152167
/**
@@ -174,7 +189,10 @@ public void setWorkingDirectory(String dir) {
174189

175190
@Override
176191
public void afterPropertiesSet() throws Exception {
177-
Assert.hasLength(command, "'command' property value is required");
192+
Assert.notNull(cmdArray, "'cmdArray' property value is required with at least 1 element");
193+
Assert.notEmpty(cmdArray, "'cmdArray' property value is required with at least 1 element");
194+
Assert.noNullElements(cmdArray, "'cmdArray' property value is required with at least 1 element");
195+
Assert.hasLength(cmdArray[0], "'cmdArray' property value is required with at least 1 element");
178196
Assert.notNull(systemProcessExitCodeMapper, "SystemProcessExitCodeMapper must be set");
179197
Assert.isTrue(timeout > 0, "timeout value must be greater than zero");
180198
Assert.notNull(taskExecutor, "taskExecutor is required");

spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/SystemCommandTaskletIntegrationTests.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,20 @@ private void initializeTasklet() {
8181
tasklet.setTaskExecutor(new SimpleAsyncTaskExecutor());
8282
}
8383

84+
/*
85+
* Power usage scenario - successful execution of system command.
86+
*/
87+
@Test
88+
public void testExecuteWithSeparateArgument() throws Exception {
89+
tasklet.setCommand(getJavaCommand(), "--version");
90+
tasklet.afterPropertiesSet();
91+
92+
log.info("Executing command: " + getJavaCommand() + " --version");
93+
RepeatStatus exitStatus = tasklet.execute(stepExecution.createStepContribution(), null);
94+
95+
assertEquals(RepeatStatus.FINISHED, exitStatus);
96+
}
97+
8498
/*
8599
* Regular usage scenario - successful execution of system command.
86100
*/
@@ -199,6 +213,33 @@ public void testCommandNotSet() throws Exception {
199213
catch (IllegalArgumentException e) {
200214
// expected
201215
}
216+
217+
tasklet.setCommand(new String[] {});
218+
try {
219+
tasklet.afterPropertiesSet();
220+
fail();
221+
}
222+
catch (IllegalArgumentException e) {
223+
// expected
224+
}
225+
226+
tasklet.setCommand(new String[] { null });
227+
try {
228+
tasklet.afterPropertiesSet();
229+
fail();
230+
}
231+
catch (IllegalArgumentException e) {
232+
// expected
233+
}
234+
235+
tasklet.setCommand(new String[] { "" });
236+
try {
237+
tasklet.afterPropertiesSet();
238+
fail();
239+
}
240+
catch (IllegalArgumentException e) {
241+
// expected
242+
}
202243
}
203244

204245
/*

0 commit comments

Comments
 (0)