001    package org.crsh.shell.concurrent;
002    
003    import org.crsh.shell.ShellProcess;
004    import org.crsh.shell.ShellProcessContext;
005    import org.crsh.shell.ShellResponse;
006    
007    import java.util.concurrent.Callable;
008    
009    /** @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a> */
010    public class AsyncProcess implements ShellProcess {
011    
012    
013      /** . */
014      private final String request;
015    
016      /** . */
017      private ShellProcessContext caller;
018    
019      /** . */
020      private ShellProcess callee;
021    
022      /** . */
023      private AsyncShell shell;
024    
025      /** . */
026      private Status status;
027    
028      /** . */
029      private final Object lock;
030    
031      AsyncProcess(AsyncShell shell, String request) {
032        this.shell = shell;
033        this.request = request;
034        this.callee = null;
035        this.status = Status.CONSTRUCTED;
036        this.lock = new Object();
037      }
038    
039      public Status getStatus() {
040        return status;
041      }
042    
043      /** . */
044      private final ShellProcessContext context = new ShellProcessContext() {
045        public int getWidth() {
046          return caller.getWidth();
047        }
048    
049        public String getProperty(String name) {
050          return caller.getProperty(name);
051        }
052    
053        public String readLine(String msg, boolean echo) {
054          return caller.readLine(msg, echo);
055        }
056    
057        public void end(ShellResponse response) {
058          // Always leave the status in terminated status if the method succeeds
059          // Cancelled -> Terminated
060          // Evaluating -> Terminated
061          // Terminated -> Terminated
062          synchronized (lock) {
063            switch (status) {
064              case CONSTRUCTED:
065              case QUEUED:
066                throw new AssertionError("Should not happen");
067              case CANCELED:
068                // We substitute the response
069                response = ShellResponse.cancelled();
070                status = Status.TERMINATED;
071                break;
072              case EVALUATING:
073                status = Status.TERMINATED;
074                break;
075              case TERMINATED:
076                throw new IllegalStateException("Cannot end a process already terminated");
077            }
078          }
079    
080          //
081          caller.end(response);
082        }
083      };
084    
085      public void execute(ShellProcessContext processContext) {
086    
087        // Constructed -> Queued
088        synchronized (lock) {
089          if (status != Status.CONSTRUCTED) {
090            throw new IllegalStateException("State was " + status);
091          }
092    
093          // Update state
094          status = Status.QUEUED;
095          callee = shell.shell.createProcess(request);
096          caller = processContext;
097        }
098    
099        // Create the task
100        Callable<AsyncProcess> task = new Callable<AsyncProcess>() {
101          public AsyncProcess call() throws Exception {
102            try {
103              // Cancelled -> Cancelled
104              // Queued -> Evaluating
105              ShellResponse response;
106              synchronized (lock) {
107                switch (status) {
108                  case CANCELED:
109                    // Do nothing it was canceled in the mean time
110                    response = ShellResponse.cancelled();
111                    break;
112                  case QUEUED:
113                    // Ok we are going to run it
114                    status = Status.EVALUATING;
115                    response = null;
116                    break;
117                  default:
118                    // Just in case but this can only be called by the queue
119                    throw new AssertionError();
120                }
121              }
122    
123              // Execute the process if we are in evalating state
124              if (response == null) {
125                // Here the status could already be in status cancelled
126                // it is a race condition, execution still happens
127                // but the callback of the callee to the end method will make the process
128                // terminate and use a cancel response
129                try {
130                  callee.execute(context);
131                  response = ShellResponse.ok();
132                }
133                catch (Throwable t) {
134                  response = ShellResponse.internalError("Unexpected throwable when executing process", t);
135                }
136              }
137    
138              // Make the callback
139              // Calling terminated twice will have no effect
140              try {
141                context.end(response);
142              }
143              catch (Throwable t) {
144                // Log it
145              }
146    
147              // We return this but we don't really care for now
148              return AsyncProcess.this;
149            }
150            finally {
151              synchronized (shell.lock) {
152                shell.processes.remove(AsyncProcess.this);
153              }
154            }
155          }
156        };
157    
158        //
159        synchronized (shell.lock) {
160          if (!shell.closed) {
161            shell.executor.submit(task);
162            shell.processes.add(this);
163          } else {
164            boolean invokeEnd;
165            synchronized (lock) {
166              invokeEnd = status != Status.TERMINATED;
167              status = Status.TERMINATED;
168            }
169            if (invokeEnd) {
170              caller.end(ShellResponse.cancelled());
171            }
172          }
173        }
174      }
175    
176      public void cancel() {
177        // Construcuted -> ISE
178        // Evaluating -> Canceled
179        // Queued -> Canceled
180        // Cancelled -> Cancelled
181        // Terminated -> Terminated
182        boolean cancel;
183        synchronized (lock) {
184          switch (status) {
185            case CONSTRUCTED:
186              throw new IllegalStateException("Cannot call cancel on process that was not scheduled for execution yet");
187            case QUEUED:
188              status = Status.CANCELED;
189              cancel = false;
190              break;
191            case EVALUATING:
192              status = Status.CANCELED;
193              cancel = true;
194              break;
195            default:
196              cancel = false;
197              break;
198          }
199        }
200    
201        //
202        if (cancel) {
203          callee.cancel();
204        }
205      }
206    }