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