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 020package org.crsh.shell.impl.async; 021 022import org.crsh.console.KeyHandler; 023import org.crsh.shell.ShellProcess; 024import org.crsh.shell.ShellProcessContext; 025import org.crsh.shell.ShellResponse; 026import org.crsh.text.Chunk; 027 028import java.io.IOException; 029import java.util.concurrent.Callable; 030 031public 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}