4545 * {@link #setTaskExecutor(TaskExecutor)} - timeout value is required to be set, so that
4646 * the batch job does not hang forever if the external process hangs.
4747 *
48- * Tasklet periodically checks for termination status (i.e. {@link #setCommand(String)}
48+ * Tasklet periodically checks for termination status (i.e. {@link #setCommand(String... )}
4949 * finished its execution or {@link #setTimeout(long)} expired or job was interrupted).
5050 * The check interval is given by {@link #setTerminationCheckInterval(long)}.
5151 *
@@ -66,7 +66,7 @@ public class SystemCommandTasklet implements StepExecutionListener, StoppableTas
6666
6767 private CommandRunner commandRunner = new JvmCommandRunner ();
6868
69- private String command ;
69+ private String [] cmdArray ;
7070
7171 private String [] environmentParams = null ;
7272
@@ -102,8 +102,14 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon
102102
103103 @ Override
104104 public Integer call () throws Exception {
105- Process process = commandRunner .exec (command , environmentParams , workingDirectory );
106- return process .waitFor ();
105+ if (cmdArray .length == 1 ) {
106+ String command = cmdArray [0 ];
107+ Process process = commandRunner .exec (command , environmentParams , workingDirectory );
108+ return process .waitFor ();
109+ } else {
110+ Process process = Runtime .getRuntime ().exec (cmdArray , environmentParams , workingDirectory );
111+ return process .waitFor ();
112+ }
107113 }
108114
109115 });
@@ -134,6 +140,7 @@ else if (System.currentTimeMillis() - t0 > timeout) {
134140 }
135141 else if (execution .isTerminateOnly ()) {
136142 systemCommandTask .cancel (interruptOnCancel );
143+ String command = String .join (" " , cmdArray );
137144 throw new JobInterruptedException ("Job interrupted while executing system command '" + command + "'" );
138145 }
139146 else if (stopped ) {
@@ -155,10 +162,17 @@ public void setCommandRunner(CommandRunner commandRunner) {
155162 }
156163
157164 /**
158- * @param command command to be executed in a separate system process
165+ * @param command command to be executed in a separate system process. Either a single command can be supplied
166+ * to be tokenized with a space delimiter, or the command and its arguments are supplied as multiple
167+ * strings that are not tokenized.
168+ * <p>
169+ * <p>Possible calls to setCommand:
170+ *
171+ * <pre> {@code setCommand("myCommand myArg1 myArg2");}</pre>
172+ * <pre> {@code setCommand("myCommand", "myArg1", "myArg2 'args for myArg2'");}</pre>
159173 */
160- public void setCommand (String command ) {
161- this .command = command ;
174+ public void setCommand (String ... command ) {
175+ this .cmdArray = command ;
162176 }
163177
164178 /**
@@ -187,7 +201,10 @@ public void setWorkingDirectory(String dir) {
187201 @ Override
188202 public void afterPropertiesSet () throws Exception {
189203 Assert .notNull (commandRunner , "CommandRunner must be set" );
190- Assert .hasLength (command , "'command' property value is required" );
204+ Assert .notNull (cmdArray , "'cmdArray' property value is required with at least 1 element" );
205+ Assert .notEmpty (cmdArray , "'cmdArray' property value is required with at least 1 element" );
206+ Assert .noNullElements (cmdArray , "'cmdArray' property value is required with at least 1 element" );
207+ Assert .hasLength (cmdArray [0 ], "'cmdArray' property value is required with at least 1 element" );
191208 Assert .notNull (systemProcessExitCodeMapper , "SystemProcessExitCodeMapper must be set" );
192209 Assert .isTrue (timeout > 0 , "timeout value must be greater than zero" );
193210 Assert .notNull (taskExecutor , "taskExecutor is required" );
0 commit comments