001 /*
002 * Copyright (C) 2012 eXo Platform SAS.
003 *
004 * This is free software; you can redistribute it and/or modify it
005 * under the terms of the GNU Lesser General Public License as
006 * published by the Free Software Foundation; either version 2.1 of
007 * the License, or (at your option) any later version.
008 *
009 * This software is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * You should have received a copy of the GNU Lesser General Public
015 * License along with this software; if not, write to the Free
016 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018 */
019
020 package org.crsh.shell.impl.async;
021
022 import org.crsh.console.KeyHandler;
023 import org.crsh.shell.ShellProcess;
024 import org.crsh.shell.ShellProcessContext;
025 import org.crsh.shell.ShellResponse;
026 import org.crsh.text.Chunk;
027
028 import java.io.IOException;
029 import java.util.concurrent.Callable;
030
031 public class AsyncProcess implements ShellProcess {
032
033
034 /** . */
035 private final String request;
036
037 /** . */
038 private ShellProcessContext caller;
039
040 /** . */
041 private ShellProcess callee;
042
043 /** . */
044 private AsyncShell shell;
045
046 /** . */
047 private Status status;
048
049 /** . */
050 private final Object lock;
051
052 AsyncProcess(AsyncShell shell, String request) {
053 this.shell = shell;
054 this.request = request;
055 this.callee = null;
056 this.status = Status.CONSTRUCTED;
057 this.lock = new Object();
058 }
059
060 public Status getStatus() {
061 return status;
062 }
063
064 /** . */
065 private final ShellProcessContext context = new ShellProcessContext() {
066 public int getWidth() {
067 return caller.getWidth();
068 }
069
070 public int getHeight() {
071 return caller.getHeight();
072 }
073
074 public String getProperty(String name) {
075 return caller.getProperty(name);
076 }
077
078 public boolean takeAlternateBuffer() throws IOException {
079 return caller.takeAlternateBuffer();
080 }
081
082 public boolean releaseAlternateBuffer() throws IOException {
083 return caller.releaseAlternateBuffer();
084 }
085
086 public String readLine(String msg, boolean echo) throws IOException, InterruptedException {
087 return caller.readLine(msg, echo);
088 }
089
090 public Class<Chunk> getConsumedType() {
091 return Chunk.class;
092 }
093
094 public void write(Chunk chunk) throws IOException {
095 caller.write(chunk);
096 }
097
098 public void flush() throws IOException {
099 caller.flush();
100 }
101
102 public void end(ShellResponse response) {
103 // Always leave the status in terminated status if the method succeeds
104 // Cancelled -> Terminated
105 // Evaluating -> Terminated
106 // Terminated -> Terminated
107 synchronized (lock) {
108 switch (status) {
109 case CONSTRUCTED:
110 case QUEUED:
111 throw new AssertionError("Should not happen");
112 case CANCELED:
113 // We substitute the response
114 response = ShellResponse.cancelled();
115 status = Status.TERMINATED;
116 break;
117 case EVALUATING:
118 status = Status.TERMINATED;
119 break;
120 case TERMINATED:
121 throw new IllegalStateException("Cannot end a process already terminated");
122 }
123 }
124
125 //
126 caller.end(response);
127 }
128 };
129
130 @Override
131 public KeyHandler getKeyHandler() {
132 synchronized (lock) {
133 if (status != Status.EVALUATING) {
134 throw new IllegalStateException();
135 }
136 }
137 // Should it be synchronous or not ????
138 // no clue for now :-)
139 return callee.getKeyHandler();
140 }
141
142 public void execute(ShellProcessContext processContext) {
143
144 // Constructed -> Queued
145 synchronized (lock) {
146 if (status != Status.CONSTRUCTED) {
147 throw new IllegalStateException("State was " + status);
148 }
149
150 // Update state
151 status = Status.QUEUED;
152 callee = shell.shell.createProcess(request);
153 caller = processContext;
154 }
155
156 // Create the task
157 Callable<AsyncProcess> task = new Callable<AsyncProcess>() {
158 public AsyncProcess call() throws Exception {
159 try {
160 // Cancelled -> Cancelled
161 // Queued -> Evaluating
162 ShellResponse response;
163 synchronized (lock) {
164 switch (status) {
165 case CANCELED:
166 // Do nothing it was canceled in the mean time
167 response = ShellResponse.cancelled();
168 break;
169 case QUEUED:
170 // Ok we are going to run it
171 status = Status.EVALUATING;
172 response = null;
173 break;
174 default:
175 // Just in case but this can only be called by the queue
176 throw new AssertionError();
177 }
178 }
179
180 // Execute the process if we are in evalating state
181 if (response == null) {
182 // Here the status could already be in status cancelled
183 // it is a race condition, execution still happens
184 // but the callback of the callee to the end method will make the process
185 // terminate and use a cancel response
186 try {
187 callee.execute(context);
188 response = ShellResponse.ok();
189 }
190 catch (Throwable t) {
191 response = ShellResponse.internalError("Unexpected throwable when executing process", t);
192 }
193 }
194
195 // Make the callback
196 // Calling terminated twice will have no effect
197 try {
198 context.end(response);
199 }
200 catch (Throwable t) {
201 // Log it
202 }
203
204 // We return this but we don't really care for now
205 return AsyncProcess.this;
206 }
207 finally {
208 synchronized (shell.lock) {
209 shell.processes.remove(AsyncProcess.this);
210 }
211 }
212 }
213 };
214
215 //
216 synchronized (shell.lock) {
217 if (!shell.closed) {
218 shell.executor.submit(task);
219 shell.processes.add(this);
220 } else {
221 boolean invokeEnd;
222 synchronized (lock) {
223 invokeEnd = status != Status.TERMINATED;
224 status = Status.TERMINATED;
225 }
226 if (invokeEnd) {
227 caller.end(ShellResponse.cancelled());
228 }
229 }
230 }
231 }
232
233 public void cancel() {
234 // Construcuted -> ISE
235 // Evaluating -> Canceled
236 // Queued -> Canceled
237 // Cancelled -> Cancelled
238 // Terminated -> Terminated
239 boolean cancel;
240 synchronized (lock) {
241 switch (status) {
242 case CONSTRUCTED:
243 throw new IllegalStateException("Cannot call cancel on process that was not scheduled for execution yet");
244 case QUEUED:
245 status = Status.CANCELED;
246 cancel = false;
247 break;
248 case EVALUATING:
249 status = Status.CANCELED;
250 cancel = true;
251 break;
252 default:
253 cancel = false;
254 break;
255 }
256 }
257
258 //
259 if (cancel) {
260 callee.cancel();
261 }
262 }
263 }