]> git.argeo.org Git - gpl/argeo-slc.git/blob - org.argeo.slc.core/src/org/argeo/slc/core/execution/tasks/SystemCall.java
b2d8723a658c1b0eceff3288230bcf4009fbf674
[gpl/argeo-slc.git] / org.argeo.slc.core / src / org / argeo / slc / core / execution / tasks / SystemCall.java
1 /*
2 * Copyright (C) 2007-2012 Argeo GmbH
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.argeo.slc.core.execution.tasks;
17
18 import java.io.File;
19 import java.io.FileOutputStream;
20 import java.io.FileWriter;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.OutputStream;
24 import java.io.PipedInputStream;
25 import java.io.PipedOutputStream;
26 import java.io.Writer;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.UUID;
33
34 import javax.security.auth.callback.CallbackHandler;
35
36 import org.apache.commons.exec.CommandLine;
37 import org.apache.commons.exec.DefaultExecutor;
38 import org.apache.commons.exec.ExecuteException;
39 import org.apache.commons.exec.ExecuteResultHandler;
40 import org.apache.commons.exec.ExecuteStreamHandler;
41 import org.apache.commons.exec.ExecuteWatchdog;
42 import org.apache.commons.exec.Executor;
43 import org.apache.commons.exec.LogOutputStream;
44 import org.apache.commons.exec.PumpStreamHandler;
45 import org.apache.commons.exec.ShutdownHookProcessDestroyer;
46 import org.apache.commons.io.FileUtils;
47 import org.apache.commons.io.IOUtils;
48 import org.apache.commons.logging.Log;
49 import org.apache.commons.logging.LogFactory;
50 import org.argeo.slc.SlcException;
51 import org.argeo.slc.UnsupportedException;
52 import org.argeo.slc.core.execution.ExecutionResources;
53 import org.argeo.slc.core.test.SimpleResultPart;
54 import org.argeo.slc.test.TestResult;
55 import org.argeo.slc.test.TestStatus;
56 import org.springframework.core.io.Resource;
57 import org.springframework.security.core.Authentication;
58 import org.springframework.security.core.context.SecurityContextHolder;
59
60 /** Execute an OS specific system call. */
61 public class SystemCall implements Runnable {
62 public final static String LOG_STDOUT = "System.out";
63
64 private final Log log = LogFactory.getLog(getClass());
65
66 private String execDir;
67
68 private String cmd = null;
69 private List<Object> command = null;
70
71 private Executor executor = new DefaultExecutor();
72 private Boolean synchronous = true;
73
74 private String stdErrLogLevel = "ERROR";
75 private String stdOutLogLevel = "INFO";
76
77 private Resource stdOutFile = null;
78 private Resource stdErrFile = null;
79
80 private Resource stdInFile = null;
81 /**
82 * If no {@link #stdInFile} provided, writing to this stream will write to
83 * the stdin of the process.
84 */
85 private OutputStream stdInSink = null;
86
87 private Boolean redirectStdOut = false;
88
89 private List<SystemCallOutputListener> outputListeners = Collections
90 .synchronizedList(new ArrayList<SystemCallOutputListener>());
91
92 private Map<String, List<Object>> osCommands = new HashMap<String, List<Object>>();
93 private Map<String, String> osCmds = new HashMap<String, String>();
94 private Map<String, String> environmentVariables = new HashMap<String, String>();
95
96 private Boolean logCommand = false;
97 private Boolean redirectStreams = true;
98 private Boolean exceptionOnFailed = true;
99 private Boolean mergeEnvironmentVariables = true;
100
101 private Authentication authentication;
102
103 private String osConsole = null;
104 private String generateScript = null;
105
106 /** 24 hours */
107 private Long watchdogTimeout = 24 * 60 * 60 * 1000l;
108
109 private TestResult testResult;
110
111 private ExecutionResources executionResources;
112
113 /** Sudo the command, as root if empty or as user if not. */
114 private String sudo = null;
115 // TODO make it more secure and robust, test only once
116 private final String sudoPrompt = UUID.randomUUID().toString();
117 private String askPassProgram = "/usr/libexec/openssh/ssh-askpass";
118 @SuppressWarnings("unused")
119 private boolean firstLine = true;
120 @SuppressWarnings("unused")
121 private CallbackHandler callbackHandler;
122 /** Chroot to the this path (must not be empty) */
123 private String chroot = null;
124
125 // Current
126 /** Current watchdog, null if process is completed */
127 ExecuteWatchdog currentWatchdog = null;
128
129 /** Empty constructor */
130 public SystemCall() {
131
132 }
133
134 /**
135 * Constructor based on the provided command list.
136 *
137 * @param command
138 * the command list
139 */
140 public SystemCall(List<Object> command) {
141 this.command = command;
142 }
143
144 /**
145 * Constructor based on the provided command.
146 *
147 * @param cmd
148 * the command. If the provided string contains no space a
149 * command list is initialized with the argument as first
150 * component (useful for chained construction)
151 */
152 public SystemCall(String cmd) {
153 if (cmd.indexOf(' ') < 0) {
154 command = new ArrayList<Object>();
155 command.add(cmd);
156 } else {
157 this.cmd = cmd;
158 }
159 }
160
161 /** Executes the system call. */
162 public void run() {
163 authentication = SecurityContextHolder.getContext().getAuthentication();
164
165 // Manage streams
166 Writer stdOutWriter = null;
167 OutputStream stdOutputStream = null;
168 Writer stdErrWriter = null;
169 InputStream stdInStream = null;
170 if (stdOutFile != null)
171 if (redirectStdOut)
172 stdOutputStream = createOutputStream(stdOutFile);
173 else
174 stdOutWriter = createWriter(stdOutFile, true);
175
176 if (stdErrFile != null) {
177 stdErrWriter = createWriter(stdErrFile, true);
178 } else {
179 if (stdOutFile != null && !redirectStdOut)
180 stdErrWriter = createWriter(stdOutFile, true);
181 }
182
183 try {
184 if (stdInFile != null)
185 stdInStream = stdInFile.getInputStream();
186 else {
187 stdInStream = new PipedInputStream();
188 stdInSink = new PipedOutputStream(
189 (PipedInputStream) stdInStream);
190 }
191 } catch (IOException e2) {
192 throw new SlcException("Cannot open a stream for " + stdInFile, e2);
193 }
194
195 if (log.isTraceEnabled()) {
196 log.debug("os.name=" + System.getProperty("os.name"));
197 log.debug("os.arch=" + System.getProperty("os.arch"));
198 log.debug("os.version=" + System.getProperty("os.version"));
199 }
200
201 // Execution directory
202 File dir = new File(getExecDirToUse());
203 // if (!dir.exists())
204 // dir.mkdirs();
205
206 // Watchdog to check for lost processes
207 Executor executorToUse;
208 if (executor != null)
209 executorToUse = executor;
210 else
211 executorToUse = new DefaultExecutor();
212 executorToUse.setWatchdog(createWatchdog());
213
214 if (redirectStreams) {
215 // Redirect standard streams
216 executorToUse.setStreamHandler(createExecuteStreamHandler(
217 stdOutWriter, stdOutputStream, stdErrWriter, stdInStream));
218 } else {
219 // Dummy stream handler (otherwise pump is used)
220 executorToUse.setStreamHandler(new DummyexecuteStreamHandler());
221 }
222
223 executorToUse.setProcessDestroyer(new ShutdownHookProcessDestroyer());
224 executorToUse.setWorkingDirectory(dir);
225
226 // Command line to use
227 final CommandLine commandLine = createCommandLine();
228 if (logCommand)
229 log.info("Execute command:\n" + commandLine
230 + "\n in working directory: \n" + dir + "\n");
231
232 // Env variables
233 Map<String, String> environmentVariablesToUse = null;
234 environmentVariablesToUse = new HashMap<String, String>();
235 if (mergeEnvironmentVariables)
236 environmentVariablesToUse.putAll(System.getenv());
237 if (environmentVariables.size() > 0)
238 environmentVariablesToUse.putAll(environmentVariables);
239
240 // Execute
241 ExecuteResultHandler executeResultHandler = createExecuteResultHandler(commandLine);
242
243 //
244 // THE EXECUTION PROPER
245 //
246 try {
247 if (synchronous)
248 try {
249 int exitValue = executorToUse.execute(commandLine,
250 environmentVariablesToUse);
251 executeResultHandler.onProcessComplete(exitValue);
252 } catch (ExecuteException e1) {
253 if (e1.getExitValue() == Executor.INVALID_EXITVALUE) {
254 Thread.currentThread().interrupt();
255 return;
256 }
257 // Sleep 1s in order to make sure error logs are flushed
258 Thread.sleep(1000);
259 executeResultHandler.onProcessFailed(e1);
260 }
261 else
262 executorToUse.execute(commandLine, environmentVariablesToUse,
263 executeResultHandler);
264 } catch (SlcException e) {
265 throw e;
266 } catch (Exception e) {
267 throw new SlcException("Could not execute command " + commandLine,
268 e);
269 } finally {
270 IOUtils.closeQuietly(stdOutWriter);
271 IOUtils.closeQuietly(stdErrWriter);
272 IOUtils.closeQuietly(stdInStream);
273 IOUtils.closeQuietly(stdInSink);
274 }
275
276 }
277
278 public synchronized String function() {
279 final StringBuffer buf = new StringBuffer("");
280 SystemCallOutputListener tempOutputListener = new SystemCallOutputListener() {
281 private Long lineCount = 0l;
282
283 public void newLine(SystemCall systemCall, String line,
284 Boolean isError) {
285 if (!isError) {
286 if (lineCount != 0l)
287 buf.append('\n');
288 buf.append(line);
289 lineCount++;
290 }
291 }
292 };
293 addOutputListener(tempOutputListener);
294 run();
295 removeOutputListener(tempOutputListener);
296 return buf.toString();
297 }
298
299 public String asCommand() {
300 return createCommandLine().toString();
301 }
302
303 @Override
304 public String toString() {
305 return asCommand();
306 }
307
308 /**
309 * Build a command line based on the properties. Can be overridden by
310 * specific command wrappers.
311 */
312 protected CommandLine createCommandLine() {
313 // Check if an OS specific command overrides
314 String osName = System.getProperty("os.name");
315 List<Object> commandToUse = null;
316 if (osCommands.containsKey(osName))
317 commandToUse = osCommands.get(osName);
318 else
319 commandToUse = command;
320 String cmdToUse = null;
321 if (osCmds.containsKey(osName))
322 cmdToUse = osCmds.get(osName);
323 else
324 cmdToUse = cmd;
325
326 CommandLine commandLine = null;
327
328 // Which command definition to use
329 if (commandToUse == null && cmdToUse == null)
330 throw new SlcException("Please specify a command.");
331 else if (commandToUse != null && cmdToUse != null)
332 throw new SlcException(
333 "Specify the command either as a line or as a list.");
334 else if (cmdToUse != null) {
335 if (chroot != null && !chroot.trim().equals(""))
336 cmdToUse = "chroot \"" + chroot + "\" " + cmdToUse;
337 if (sudo != null) {
338 environmentVariables.put("SUDO_ASKPASS", askPassProgram);
339 if (!sudo.trim().equals(""))
340 cmdToUse = "sudo -p " + sudoPrompt + " -u " + sudo + " "
341 + cmdToUse;
342 else
343 cmdToUse = "sudo -p " + sudoPrompt + " " + cmdToUse;
344 }
345
346 // GENERATE COMMAND LINE
347 commandLine = CommandLine.parse(cmdToUse);
348 } else if (commandToUse != null) {
349 if (commandToUse.size() == 0)
350 throw new SlcException("Command line is empty.");
351
352 if (chroot != null && sudo != null) {
353 commandToUse.add(0, "chroot");
354 commandToUse.add(1, chroot);
355 }
356
357 if (sudo != null) {
358 environmentVariables.put("SUDO_ASKPASS", askPassProgram);
359 commandToUse.add(0, "sudo");
360 commandToUse.add(1, "-p");
361 commandToUse.add(2, sudoPrompt);
362 if (!sudo.trim().equals("")) {
363 commandToUse.add(3, "-u");
364 commandToUse.add(4, sudo);
365 }
366 }
367
368 // GENERATE COMMAND LINE
369 commandLine = new CommandLine(commandToUse.get(0).toString());
370
371 for (int i = 1; i < commandToUse.size(); i++) {
372 if (log.isTraceEnabled())
373 log.debug(commandToUse.get(i));
374 commandLine.addArgument(commandToUse.get(i).toString());
375 }
376 } else {
377 // all cases covered previously
378 throw new UnsupportedException();
379 }
380
381 if (generateScript != null) {
382 File scriptFile = new File(getExecDirToUse() + File.separator
383 + generateScript);
384 try {
385 FileUtils.writeStringToFile(scriptFile,
386 (osConsole != null ? osConsole + " " : "")
387 + commandLine.toString());
388 } catch (IOException e) {
389 throw new SlcException("Could not generate script "
390 + scriptFile, e);
391 }
392 commandLine = new CommandLine(scriptFile);
393 } else {
394 if (osConsole != null)
395 commandLine = CommandLine.parse(osConsole + " "
396 + commandLine.toString());
397 }
398
399 return commandLine;
400 }
401
402 /**
403 * Creates a {@link PumpStreamHandler} which redirects streams to the custom
404 * logging mechanism.
405 */
406 protected ExecuteStreamHandler createExecuteStreamHandler(
407 final Writer stdOutWriter, final OutputStream stdOutputStream,
408 final Writer stdErrWriter, final InputStream stdInStream) {
409
410 // Log writers
411 OutputStream stdout = stdOutputStream != null ? stdOutputStream
412 : new LogOutputStream() {
413 protected void processLine(String line, int level) {
414 // if (firstLine) {
415 // if (sudo != null && callbackHandler != null
416 // && line.startsWith(sudoPrompt)) {
417 // try {
418 // PasswordCallback pc = new PasswordCallback(
419 // "sudo password", false);
420 // Callback[] cbs = { pc };
421 // callbackHandler.handle(cbs);
422 // char[] pwd = pc.getPassword();
423 // char[] arr = Arrays.copyOf(pwd,
424 // pwd.length + 1);
425 // arr[arr.length - 1] = '\n';
426 // IOUtils.write(arr, stdInSink);
427 // stdInSink.flush();
428 // } catch (Exception e) {
429 // throw new SlcException(
430 // "Cannot retrieve sudo password", e);
431 // }
432 // }
433 // firstLine = false;
434 // }
435
436 if (line != null && !line.trim().equals(""))
437 logStdOut(line);
438
439 if (stdOutWriter != null)
440 appendLineToFile(stdOutWriter, line);
441 }
442 };
443
444 OutputStream stderr = new LogOutputStream() {
445 protected void processLine(String line, int level) {
446 if (line != null && !line.trim().equals(""))
447 logStdErr(line);
448 if (stdErrWriter != null)
449 appendLineToFile(stdErrWriter, line);
450 }
451 };
452
453 PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(stdout,
454 stderr, stdInStream) {
455
456 @Override
457 public void stop() throws IOException {
458 // prevents the method to block when joining stdin
459 if (stdInSink != null)
460 IOUtils.closeQuietly(stdInSink);
461
462 super.stop();
463 }
464 };
465 return pumpStreamHandler;
466 }
467
468 /** Creates the default {@link ExecuteResultHandler}. */
469 protected ExecuteResultHandler createExecuteResultHandler(
470 final CommandLine commandLine) {
471 return new ExecuteResultHandler() {
472
473 public void onProcessComplete(int exitValue) {
474 String msg = "System call '" + commandLine
475 + "' properly completed.";
476 if (log.isTraceEnabled())
477 log.trace(msg);
478 if (testResult != null) {
479 forwardPath(testResult);
480 testResult.addResultPart(new SimpleResultPart(
481 TestStatus.PASSED, msg));
482 }
483 releaseWatchdog();
484 }
485
486 public void onProcessFailed(ExecuteException e) {
487
488 String msg = "System call '" + commandLine + "' failed.";
489 if (testResult != null) {
490 forwardPath(testResult);
491 testResult.addResultPart(new SimpleResultPart(
492 TestStatus.ERROR, msg, e));
493 } else {
494 if (exceptionOnFailed)
495 throw new SlcException(msg, e);
496 else
497 log.error(msg, e);
498 }
499 releaseWatchdog();
500 }
501 };
502 }
503
504 @Deprecated
505 protected void forwardPath(TestResult testResult) {
506 // TODO: allocate a TreeSPath
507 }
508
509 /**
510 * Shortcut method getting the execDir to use
511 */
512 protected String getExecDirToUse() {
513 try {
514 if (execDir != null) {
515 return execDir;
516 }
517 return System.getProperty("user.dir");
518 } catch (Exception e) {
519 throw new SlcException("Cannot find exec dir", e);
520 }
521 }
522
523 protected void logStdOut(String line) {
524 for (SystemCallOutputListener outputListener : outputListeners)
525 outputListener.newLine(this, line, false);
526 log(stdOutLogLevel, line);
527 }
528
529 protected void logStdErr(String line) {
530 for (SystemCallOutputListener outputListener : outputListeners)
531 outputListener.newLine(this, line, true);
532 log(stdErrLogLevel, line);
533 }
534
535 /** Log from the underlying streams. */
536 protected void log(String logLevel, String line) {
537 // TODO optimize
538 if (SecurityContextHolder.getContext().getAuthentication() == null) {
539 SecurityContextHolder.getContext()
540 .setAuthentication(authentication);
541 }
542
543 if ("ERROR".equals(logLevel))
544 log.error(line);
545 else if ("WARN".equals(logLevel))
546 log.warn(line);
547 else if ("INFO".equals(logLevel))
548 log.info(line);
549 else if ("DEBUG".equals(logLevel))
550 log.debug(line);
551 else if ("TRACE".equals(logLevel))
552 log.trace(line);
553 else if (LOG_STDOUT.equals(logLevel))
554 System.out.println(line);
555 else if ("System.err".equals(logLevel))
556 System.err.println(line);
557 else
558 throw new SlcException("Unknown log level " + logLevel);
559 }
560
561 /** Append line to a log file. */
562 protected void appendLineToFile(Writer writer, String line) {
563 try {
564 writer.append(line).append('\n');
565 } catch (IOException e) {
566 log.error("Cannot write to log file", e);
567 }
568 }
569
570 /** Creates the writer for the output/err files. */
571 protected Writer createWriter(Resource target, Boolean append) {
572 FileWriter writer = null;
573 try {
574
575 final File file;
576 if (executionResources != null)
577 file = new File(executionResources.getAsOsPath(target, true));
578 else
579 file = target.getFile();
580 writer = new FileWriter(file, append);
581 } catch (IOException e) {
582 log.error("Cannot get file for " + target, e);
583 IOUtils.closeQuietly(writer);
584 }
585 return writer;
586 }
587
588 /** Creates an outputstream for the output/err files. */
589 protected OutputStream createOutputStream(Resource target) {
590 FileOutputStream out = null;
591 try {
592
593 final File file;
594 if (executionResources != null)
595 file = new File(executionResources.getAsOsPath(target, true));
596 else
597 file = target.getFile();
598 out = new FileOutputStream(file, false);
599 } catch (IOException e) {
600 log.error("Cannot get file for " + target, e);
601 IOUtils.closeQuietly(out);
602 }
603 return out;
604 }
605
606 /** Append the argument (for chaining) */
607 public SystemCall arg(String arg) {
608 if (command == null)
609 command = new ArrayList<Object>();
610 command.add(arg);
611 return this;
612 }
613
614 /** Append the argument (for chaining) */
615 public SystemCall arg(String arg, String value) {
616 if (command == null)
617 command = new ArrayList<Object>();
618 command.add(arg);
619 command.add(value);
620 return this;
621 }
622
623 // CONTROL
624 public synchronized Boolean isRunning() {
625 return currentWatchdog != null;
626 }
627
628 private synchronized ExecuteWatchdog createWatchdog() {
629 // if (currentWatchdog != null)
630 // throw new SlcException("A process is already being monitored");
631 currentWatchdog = new ExecuteWatchdog(watchdogTimeout);
632 return currentWatchdog;
633 }
634
635 private synchronized void releaseWatchdog() {
636 currentWatchdog = null;
637 }
638
639 public synchronized void kill() {
640 if (currentWatchdog != null)
641 currentWatchdog.destroyProcess();
642 }
643
644 /** */
645 public void setCmd(String command) {
646 this.cmd = command;
647 }
648
649 public void setCommand(List<Object> command) {
650 this.command = command;
651 }
652
653 public void setExecDir(String execdir) {
654 this.execDir = execdir;
655 }
656
657 public void setStdErrLogLevel(String stdErrLogLevel) {
658 this.stdErrLogLevel = stdErrLogLevel;
659 }
660
661 public void setStdOutLogLevel(String stdOutLogLevel) {
662 this.stdOutLogLevel = stdOutLogLevel;
663 }
664
665 public void setSynchronous(Boolean synchronous) {
666 this.synchronous = synchronous;
667 }
668
669 public void setOsCommands(Map<String, List<Object>> osCommands) {
670 this.osCommands = osCommands;
671 }
672
673 public void setOsCmds(Map<String, String> osCmds) {
674 this.osCmds = osCmds;
675 }
676
677 public void setEnvironmentVariables(Map<String, String> environmentVariables) {
678 this.environmentVariables = environmentVariables;
679 }
680
681 public Map<String, String> getEnvironmentVariables() {
682 return environmentVariables;
683 }
684
685 public void setWatchdogTimeout(Long watchdogTimeout) {
686 this.watchdogTimeout = watchdogTimeout;
687 }
688
689 public void setStdOutFile(Resource stdOutFile) {
690 this.stdOutFile = stdOutFile;
691 }
692
693 public void setStdErrFile(Resource stdErrFile) {
694 this.stdErrFile = stdErrFile;
695 }
696
697 public void setStdInFile(Resource stdInFile) {
698 this.stdInFile = stdInFile;
699 }
700
701 public void setTestResult(TestResult testResult) {
702 this.testResult = testResult;
703 }
704
705 public void setLogCommand(Boolean logCommand) {
706 this.logCommand = logCommand;
707 }
708
709 public void setRedirectStreams(Boolean redirectStreams) {
710 this.redirectStreams = redirectStreams;
711 }
712
713 public void setExceptionOnFailed(Boolean exceptionOnFailed) {
714 this.exceptionOnFailed = exceptionOnFailed;
715 }
716
717 public void setMergeEnvironmentVariables(Boolean mergeEnvironmentVariables) {
718 this.mergeEnvironmentVariables = mergeEnvironmentVariables;
719 }
720
721 public void setOsConsole(String osConsole) {
722 this.osConsole = osConsole;
723 }
724
725 public void setGenerateScript(String generateScript) {
726 this.generateScript = generateScript;
727 }
728
729 public void setExecutionResources(ExecutionResources executionResources) {
730 this.executionResources = executionResources;
731 }
732
733 public void setRedirectStdOut(Boolean redirectStdOut) {
734 this.redirectStdOut = redirectStdOut;
735 }
736
737 public void addOutputListener(SystemCallOutputListener outputListener) {
738 outputListeners.add(outputListener);
739 }
740
741 public void removeOutputListener(SystemCallOutputListener outputListener) {
742 outputListeners.remove(outputListener);
743 }
744
745 public void setOutputListeners(
746 List<SystemCallOutputListener> outputListeners) {
747 this.outputListeners = outputListeners;
748 }
749
750 public void setExecutor(Executor executor) {
751 this.executor = executor;
752 }
753
754 public void setSudo(String sudo) {
755 this.sudo = sudo;
756 }
757
758 public void setCallbackHandler(CallbackHandler callbackHandler) {
759 this.callbackHandler = callbackHandler;
760 }
761
762 public void setChroot(String chroot) {
763 this.chroot = chroot;
764 }
765
766 private class DummyexecuteStreamHandler implements ExecuteStreamHandler {
767
768 public void setProcessErrorStream(InputStream is) throws IOException {
769 }
770
771 public void setProcessInputStream(OutputStream os) throws IOException {
772 }
773
774 public void setProcessOutputStream(InputStream is) throws IOException {
775 }
776
777 public void start() throws IOException {
778 }
779
780 public void stop() {
781 }
782
783 }
784 }