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