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 = new ScriptREPL(); 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 for (REPL repl : crash.getContext().getPlugins(REPL.class)) { 162 if (repl.getName().equals(name)) { 163 found = repl; 164 break; 165 } 166 } 167 if (found != null) { 168 repl = found; 169 msg.append("Using repl ").append(found.getName()); 170 response = ShellResponse.ok(); 171 } else { 172 response = ShellResponse.error(ErrorType.EVALUATION, "Repl " + name + " not found"); 173 } 174 } 175 } else { 176 EvalResponse r = repl.eval(this, request); 177 if (r instanceof EvalResponse.Response) { 178 EvalResponse.Response rr = (EvalResponse.Response)r; 179 response = rr.response; 180 } else { 181 final CommandInvoker<Void, ?> pipeLine = ((EvalResponse.Invoke)r).invoker; 182 return new CRaSHProcess(this, request) { 183 184 @Override 185 ShellResponse doInvoke(final ShellProcessContext context) throws InterruptedException { 186 CRaSHProcessContext invocationContext = new CRaSHProcessContext(CRaSHSession.this, context); 187 try { 188 pipeLine.invoke(invocationContext); 189 return ShellResponse.ok(); 190 } 191 catch (ScriptException e) { 192 return build(e); 193 } catch (Throwable t) { 194 return build(t); 195 } finally { 196 Safe.close(invocationContext); 197 } 198 } 199 200 private ShellResponse.Error build(Throwable throwable) { 201 ErrorType errorType; 202 if (throwable instanceof ScriptException || throwable instanceof UndeclaredThrowableException) { 203 errorType = ErrorType.EVALUATION; 204 Throwable cause = throwable.getCause(); 205 if (cause != null) { 206 throwable = cause; 207 } 208 } else { 209 errorType = ErrorType.INTERNAL; 210 } 211 String result; 212 String msg = throwable.getMessage(); 213 if (throwable instanceof ScriptException) { 214 if (msg == null) { 215 result = request + ": failed"; 216 } else { 217 result = request + ": " + msg; 218 } 219 return ShellResponse.error(errorType, result, throwable); 220 } else { 221 if (msg == null) { 222 msg = throwable.getClass().getSimpleName(); 223 } 224 if (throwable instanceof RuntimeException) { 225 result = request + ": exception: " + msg; 226 } else if (throwable instanceof Exception) { 227 result = request + ": exception: " + msg; 228 } else if (throwable instanceof java.lang.Error) { 229 result = request + ": error: " + msg; 230 } else { 231 result = request + ": unexpected throwable: " + msg; 232 } 233 return ShellResponse.error(errorType, result, throwable); 234 } 235 } 236 }; 237 } 238 } 239 return new CRaSHProcess(this, request) { 240 @Override 241 ShellResponse doInvoke(ShellProcessContext context) throws InterruptedException { 242 if (msg.length() > 0) { 243 try { 244 context.write(Text.create(msg)); 245 } 246 catch (IOException ignore) { 247 } 248 } 249 return response; 250 } 251 }; 252 } 253 254 /** 255 * For now basic implementation 256 */ 257 public CompletionMatch complete(final String prefix) { 258 ClassLoader previous = setCRaSHLoader(); 259 try { 260 return repl.complete(this, prefix); 261 } 262 finally { 263 setPreviousLoader(previous); 264 } 265 } 266 267 ClassLoader setCRaSHLoader() { 268 Thread thread = Thread.currentThread(); 269 ClassLoader previous = thread.getContextClassLoader(); 270 thread.setContextClassLoader(crash.context.getLoader()); 271 return previous; 272 } 273 274 void setPreviousLoader(ClassLoader previous) { 275 Thread.currentThread().setContextClassLoader(previous); 276 } 277}