[88] | 1 | package com.framsticks.running; |
---|
| 2 | |
---|
| 3 | import java.io.BufferedReader; |
---|
| 4 | import java.io.File; |
---|
| 5 | import java.io.IOException; |
---|
| 6 | import java.io.InputStreamReader; |
---|
| 7 | import java.io.OutputStreamWriter; |
---|
| 8 | import java.io.PrintWriter; |
---|
| 9 | import java.util.ArrayList; |
---|
| 10 | import java.util.List; |
---|
| 11 | |
---|
| 12 | |
---|
[100] | 13 | import org.apache.logging.log4j.Logger; |
---|
| 14 | import org.apache.logging.log4j.LogManager; |
---|
[88] | 15 | |
---|
[101] | 16 | import com.framsticks.core.ValueChange; |
---|
| 17 | import com.framsticks.params.EventListener; |
---|
| 18 | import com.framsticks.params.EventListeners; |
---|
| 19 | import com.framsticks.params.ParamFlags; |
---|
[90] | 20 | import com.framsticks.params.annotations.AutoAppendAnnotation; |
---|
[88] | 21 | import com.framsticks.params.annotations.FramsClassAnnotation; |
---|
| 22 | import com.framsticks.params.annotations.ParamAnnotation; |
---|
| 23 | import com.framsticks.util.FramsticksException; |
---|
[90] | 24 | import com.framsticks.util.dispatching.AbstractJoinable; |
---|
| 25 | import com.framsticks.util.dispatching.Dispatching; |
---|
| 26 | import com.framsticks.util.dispatching.Joinable; |
---|
| 27 | import com.framsticks.util.dispatching.JoinableParent; |
---|
| 28 | import com.framsticks.util.dispatching.JoinableState; |
---|
| 29 | import com.framsticks.util.dispatching.RunAt; |
---|
[88] | 30 | import com.framsticks.util.dispatching.Thread; |
---|
[97] | 31 | import com.framsticks.util.dispatching.ThrowExceptionHandler; |
---|
[88] | 32 | import com.framsticks.util.io.Encoding; |
---|
| 33 | |
---|
| 34 | @FramsClassAnnotation |
---|
[96] | 35 | public class ExternalProcess extends AbstractJoinable implements JoinableParent { |
---|
[100] | 36 | private static final Logger log = LogManager.getLogger(ExternalProcess.class); |
---|
[88] | 37 | |
---|
| 38 | protected List<String> arguments = new ArrayList<>(); |
---|
| 39 | protected Process process; |
---|
[90] | 40 | protected final ProcessBuilder builder = new ProcessBuilder(); |
---|
| 41 | protected Thread<ExternalProcess> readerThread = new Thread<ExternalProcess>(); |
---|
[88] | 42 | |
---|
| 43 | protected PrintWriter input; |
---|
| 44 | protected BufferedReader output; |
---|
| 45 | protected Integer exitCode; |
---|
[101] | 46 | protected String echoInput; |
---|
[88] | 47 | |
---|
[101] | 48 | protected final EventListeners<ValueChange> listeners = new EventListeners<>(); |
---|
[88] | 49 | |
---|
[90] | 50 | @AutoAppendAnnotation |
---|
[101] | 51 | @ParamAnnotation(id = "line_output") |
---|
| 52 | public void addOutputListener(EventListener<ValueChange> listener) { |
---|
[88] | 53 | synchronized (listeners) { |
---|
| 54 | listeners.add(listener); |
---|
| 55 | } |
---|
| 56 | } |
---|
| 57 | |
---|
[101] | 58 | @ParamAnnotation(id = "line_output") |
---|
| 59 | public void removeOutputListener(EventListener<ValueChange> listener) { |
---|
| 60 | synchronized (listeners) { |
---|
| 61 | listeners.remove(listener); |
---|
| 62 | } |
---|
| 63 | } |
---|
| 64 | |
---|
[88] | 65 | /** |
---|
| 66 | * |
---|
| 67 | */ |
---|
| 68 | public ExternalProcess() { |
---|
| 69 | super(); |
---|
| 70 | setName("process"); |
---|
| 71 | arguments.add(null); |
---|
[90] | 72 | builder.redirectErrorStream(true); |
---|
[88] | 73 | } |
---|
| 74 | |
---|
| 75 | /** |
---|
| 76 | * @return the command |
---|
| 77 | */ |
---|
[101] | 78 | @ParamAnnotation(flags = ParamFlags.USERREADONLY) |
---|
[88] | 79 | public String getCommand() { |
---|
| 80 | return arguments.get(0); |
---|
| 81 | } |
---|
| 82 | |
---|
| 83 | /** |
---|
| 84 | * @param command the command to set |
---|
| 85 | */ |
---|
| 86 | @ParamAnnotation |
---|
| 87 | public void setCommand(String command) { |
---|
| 88 | arguments.set(0, command); |
---|
| 89 | } |
---|
| 90 | |
---|
[90] | 91 | protected void readerTask() { |
---|
[88] | 92 | |
---|
[101] | 93 | log.debug("reading output from " + this); |
---|
[88] | 94 | String line; |
---|
| 95 | try { |
---|
| 96 | try { |
---|
| 97 | while ((line = output.readLine()) != null) { |
---|
[100] | 98 | log.trace("read line: {}", line); |
---|
[88] | 99 | synchronized (listeners) { |
---|
[101] | 100 | listeners.actionForAll(new ValueChange(line)); |
---|
[88] | 101 | } |
---|
| 102 | } |
---|
| 103 | } catch (IOException e) { |
---|
| 104 | throw new FramsticksException().msg("failed to read line from output of process").cause(e); |
---|
| 105 | } |
---|
| 106 | try { |
---|
| 107 | exitCode = process.waitFor(); |
---|
| 108 | } catch (InterruptedException e) { |
---|
| 109 | throw new FramsticksException().msg("failed to wait for process").cause(e); |
---|
| 110 | } |
---|
[102] | 111 | log.info("process ended {}", this); |
---|
[90] | 112 | // process = null; |
---|
[88] | 113 | } catch (FramsticksException e) { |
---|
[100] | 114 | log.error("exception caught in process {}", this, e); |
---|
[88] | 115 | } |
---|
[101] | 116 | interruptJoinable(); |
---|
[90] | 117 | // finish(); |
---|
[88] | 118 | } |
---|
| 119 | |
---|
[101] | 120 | @ParamAnnotation(flags = ParamFlags.USERREADONLY) |
---|
[88] | 121 | public void setDirectory(String directory) { |
---|
| 122 | builder.directory(new File(directory)); |
---|
| 123 | } |
---|
| 124 | |
---|
| 125 | @ParamAnnotation |
---|
| 126 | public String getDirectory() { |
---|
[101] | 127 | return builder.directory() != null ? builder.directory().getName() : "."; |
---|
[88] | 128 | } |
---|
| 129 | |
---|
| 130 | @Override |
---|
[90] | 131 | protected void joinableStart() { |
---|
[103] | 132 | log.info("running process {}", this); |
---|
[88] | 133 | builder.command(arguments); |
---|
| 134 | try { |
---|
| 135 | process = builder.start(); |
---|
| 136 | input = new PrintWriter(new OutputStreamWriter(process.getOutputStream(), Encoding.getDefaultCharset())); |
---|
| 137 | output = new BufferedReader(new InputStreamReader(process.getInputStream(), Encoding.getDefaultCharset())); |
---|
[90] | 138 | |
---|
[88] | 139 | } catch (IOException e) { |
---|
| 140 | throw new FramsticksException().msg("failed to start process").cause(e); |
---|
| 141 | } |
---|
| 142 | |
---|
[97] | 143 | readerThread.dispatch(new RunAt<ExternalProcess>(ThrowExceptionHandler.getInstance()) { |
---|
[90] | 144 | |
---|
| 145 | @Override |
---|
[97] | 146 | protected void runAt() { |
---|
[90] | 147 | readerTask(); |
---|
| 148 | } |
---|
| 149 | |
---|
| 150 | }); |
---|
| 151 | Dispatching.use(readerThread, this); |
---|
[101] | 152 | |
---|
| 153 | if (echoInput != null) { |
---|
| 154 | input.println(echoInput); |
---|
| 155 | input.flush(); |
---|
| 156 | } |
---|
[88] | 157 | } |
---|
| 158 | |
---|
| 159 | @Override |
---|
| 160 | public String toString() { |
---|
[102] | 161 | return getName() + arguments; |
---|
[88] | 162 | } |
---|
| 163 | |
---|
| 164 | /** |
---|
| 165 | * @return the input |
---|
| 166 | */ |
---|
| 167 | public PrintWriter getInput() { |
---|
| 168 | return input; |
---|
| 169 | } |
---|
| 170 | |
---|
[101] | 171 | /** |
---|
| 172 | * @return the echoInput |
---|
| 173 | */ |
---|
| 174 | @ParamAnnotation(flags = ParamFlags.USERREADONLY) |
---|
| 175 | public String getEchoInput() { |
---|
| 176 | return echoInput; |
---|
| 177 | } |
---|
| 178 | |
---|
| 179 | /** |
---|
| 180 | * @param echoInput the echoInput to set |
---|
| 181 | */ |
---|
| 182 | @ParamAnnotation |
---|
| 183 | public void setEchoInput(String echoInput) { |
---|
| 184 | this.echoInput = echoInput; |
---|
| 185 | } |
---|
| 186 | |
---|
[88] | 187 | @Override |
---|
| 188 | protected void joinableInterrupt() { |
---|
[90] | 189 | process.destroy(); |
---|
| 190 | Dispatching.drop(readerThread, this); |
---|
| 191 | // finish(); |
---|
[88] | 192 | } |
---|
| 193 | |
---|
[90] | 194 | @Override |
---|
[101] | 195 | @ParamAnnotation(flags = ParamFlags.USERREADONLY) |
---|
[90] | 196 | public String getName() { |
---|
| 197 | return readerThread.getName(); |
---|
| 198 | } |
---|
| 199 | |
---|
| 200 | /** |
---|
| 201 | * @param name the name to set |
---|
| 202 | */ |
---|
| 203 | @ParamAnnotation |
---|
| 204 | public void setName(String name) { |
---|
| 205 | readerThread.setName(name); |
---|
| 206 | } |
---|
| 207 | |
---|
| 208 | @Override |
---|
| 209 | protected void joinableFinish() { |
---|
| 210 | |
---|
| 211 | } |
---|
| 212 | |
---|
| 213 | @Override |
---|
| 214 | protected void joinableJoin() throws InterruptedException { |
---|
| 215 | Dispatching.join(readerThread); |
---|
| 216 | } |
---|
| 217 | |
---|
| 218 | @Override |
---|
| 219 | public void childChangedState(Joinable joinable, JoinableState state) { |
---|
| 220 | proceedToState(state); |
---|
| 221 | } |
---|
| 222 | |
---|
| 223 | |
---|
| 224 | |
---|
[88] | 225 | } |
---|