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 */ 019package org.crsh.shell.impl.command; 020 021import org.crsh.cli.impl.completion.CompletionMatch; 022import org.crsh.command.CommandCreationException; 023import org.crsh.command.RuntimeContext; 024import org.crsh.command.CommandInvoker; 025import org.crsh.command.ScriptException; 026import org.crsh.command.ShellCommand; 027import org.crsh.plugin.PluginContext; 028import org.crsh.repl.REPL; 029import org.crsh.shell.ErrorType; 030import org.crsh.shell.Shell; 031import org.crsh.shell.ShellProcess; 032import org.crsh.shell.ShellProcessContext; 033import org.crsh.shell.ShellResponse; 034import org.crsh.repl.EvalResponse; 035import org.crsh.lang.script.ScriptREPL; 036import org.crsh.repl.REPLSession; 037import org.crsh.text.Text; 038import org.crsh.util.Safe; 039 040import java.io.Closeable; 041import java.io.IOException; 042import java.lang.reflect.UndeclaredThrowableException; 043import java.security.Principal; 044import java.util.HashMap; 045import java.util.Map; 046import java.util.ServiceLoader; 047import java.util.logging.Level; 048import java.util.logging.Logger; 049 050public class CRaSHSession extends HashMap<String, Object> implements Shell, Closeable, RuntimeContext, REPLSession { 051 052 /** . */ 053 static final Logger log = Logger.getLogger(CRaSHSession.class.getName()); 054 055 /** . */ 056 static final Logger accessLog = Logger.getLogger("org.crsh.shell.access"); 057 058 /** . */ 059 public final CRaSH crash; 060 061 /** . */ 062 final Principal user; 063 064 public CommandManager getCommandManager() { 065 return crash.managers.get("groovy"); 066 } 067 068 CRaSHSession(final CRaSH crash, Principal user) { 069 // Set variable available to all scripts 070 put("crash", crash); 071 072 // 073 this.crash = crash; 074 this.user = user; 075 076 // 077 ClassLoader previous = setCRaSHLoader(); 078 try { 079 for (CommandManager manager : crash.managers.values()) { 080 manager.init(this); 081 } 082 } 083 finally { 084 setPreviousLoader(previous); 085 } 086 } 087 088 public Iterable<String> getCommandNames() { 089 return crash.getCommandNames(); 090 } 091 092 public ShellCommand getCommand(String name) throws CommandCreationException { 093 return crash.getCommand(name); 094 } 095 096 public PluginContext getContext() { 097 return crash.context; 098 } 099 100 public Map<String, Object> getSession() { 101 return this; 102 } 103 104 public Map<String, Object> getAttributes() { 105 return crash.context.getAttributes(); 106 } 107 108 public void close() { 109 ClassLoader previous = setCRaSHLoader(); 110 try { 111 for (CommandManager manager : crash.managers.values()) { 112 manager.destroy(this); 113 } 114 } 115 finally { 116 setPreviousLoader(previous); 117 } 118 } 119 120 // Shell implementation ********************************************************************************************** 121 122 public String getWelcome() { 123 ClassLoader previous = setCRaSHLoader(); 124 try { 125 return crash.managers.get("groovy").doCallBack(this, "welcome", ""); 126 } 127 finally { 128 setPreviousLoader(previous); 129 } 130 } 131 132 public String getPrompt() { 133 ClassLoader previous = setCRaSHLoader(); 134 try { 135 return crash.managers.get("groovy").doCallBack(this, "prompt", "% "); 136 } 137 finally { 138 setPreviousLoader(previous); 139 } 140 } 141 142 /** . */ 143 private REPL repl = ScriptREPL.getInstance(); 144 145 public ShellProcess createProcess(String request) { 146 log.log(Level.FINE, "Invoking request " + request); 147 String trimmedRequest = request.trim(); 148 final StringBuilder msg = new StringBuilder(); 149 final ShellResponse response; 150 if ("bye".equals(trimmedRequest) || "exit".equals(trimmedRequest)) { 151 response = ShellResponse.close(); 152 } else if (trimmedRequest.equals("repl")) { 153 msg.append("Current repl ").append(repl.getName()); 154 response = ShellResponse.ok(); 155 } else if (trimmedRequest.startsWith("repl ")) { 156 String name = trimmedRequest.substring("repl ".length()).trim(); 157 if (name.equals(repl.getName())) { 158 response = ShellResponse.ok(); 159 } else { 160 REPL found = null; 161 if ("script".equals(name)) { 162 found = ScriptREPL.getInstance(); 163 } else { 164 for (REPL repl : crash.getContext().getPlugins(REPL.class)) { 165 if (repl.getName().equals(name)) { 166 found = repl; 167 break; 168 } 169 } 170 } 171 if (found != null) { 172 repl = found; 173 msg.append("Using repl ").append(found.getName()); 174 response = ShellResponse.ok(); 175 } else { 176 response = ShellResponse.error(ErrorType.EVALUATION, "Repl " + name + " not found"); 177 } 178 } 179 } else { 180 EvalResponse r = repl.eval(this, request); 181 if (r instanceof EvalResponse.Response) { 182 EvalResponse.Response rr = (EvalResponse.Response)r; 183 response = rr.response; 184 } else { 185 final CommandInvoker<Void, ?> pipeLine = ((EvalResponse.Invoke)r).invoker; 186 return new CRaSHProcess(this, request) { 187 188 @Override 189 ShellResponse doInvoke(final ShellProcessContext context) throws InterruptedException { 190 CRaSHProcessContext invocationContext = new CRaSHProcessContext(CRaSHSession.this, context); 191 try { 192 pipeLine.invoke(invocationContext); 193 return ShellResponse.ok(); 194 } 195 catch (ScriptException e) { 196 return build(e); 197 } catch (Throwable t) { 198 return build(t); 199 } finally { 200 Safe.close(invocationContext); 201 } 202 } 203 204 private ShellResponse.Error build(Throwable throwable) { 205 ErrorType errorType; 206 if (throwable instanceof ScriptException || throwable instanceof UndeclaredThrowableException) { 207 errorType = ErrorType.EVALUATION; 208 Throwable cause = throwable.getCause(); 209 if (cause != null) { 210 throwable = cause; 211 } 212 } else { 213 errorType = ErrorType.INTERNAL; 214 } 215 String result; 216 String msg = throwable.getMessage(); 217 if (throwable instanceof ScriptException) { 218 if (msg == null) { 219 result = request + ": failed"; 220 } else { 221 result = request + ": " + msg; 222 } 223 return ShellResponse.error(errorType, result, throwable); 224 } else { 225 if (msg == null) { 226 msg = throwable.getClass().getSimpleName(); 227 } 228 if (throwable instanceof RuntimeException) { 229 result = request + ": exception: " + msg; 230 } else if (throwable instanceof Exception) { 231 result = request + ": exception: " + msg; 232 } else if (throwable instanceof java.lang.Error) { 233 result = request + ": error: " + msg; 234 } else { 235 result = request + ": unexpected throwable: " + msg; 236 } 237 return ShellResponse.error(errorType, result, throwable); 238 } 239 } 240 }; 241 } 242 } 243 return new CRaSHProcess(this, request) { 244 @Override 245 ShellResponse doInvoke(ShellProcessContext context) throws InterruptedException { 246 if (msg.length() > 0) { 247 try { 248 context.write(Text.create(msg)); 249 } 250 catch (IOException ignore) { 251 } 252 } 253 return response; 254 } 255 }; 256 } 257 258 /** 259 * For now basic implementation 260 */ 261 public CompletionMatch complete(final String prefix) { 262 ClassLoader previous = setCRaSHLoader(); 263 try { 264 return repl.complete(this, prefix); 265 } 266 finally { 267 setPreviousLoader(previous); 268 } 269 } 270 271 ClassLoader setCRaSHLoader() { 272 Thread thread = Thread.currentThread(); 273 ClassLoader previous = thread.getContextClassLoader(); 274 thread.setContextClassLoader(crash.context.getLoader()); 275 return previous; 276 } 277 278 void setPreviousLoader(ClassLoader previous) { 279 Thread.currentThread().setContextClassLoader(previous); 280 } 281}