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 package org.crsh.shell.impl.command; 020 021 import groovy.lang.Binding; 022 import groovy.lang.GroovyShell; 023 import groovy.lang.Script; 024 import org.codehaus.groovy.control.CompilerConfiguration; 025 import org.codehaus.groovy.runtime.InvokerHelper; 026 import org.crsh.cli.impl.completion.CompletionMatch; 027 import org.crsh.cli.spi.Completion; 028 import org.crsh.command.CommandContext; 029 import org.crsh.cli.impl.Delimiter; 030 import org.crsh.command.BaseCommandContext; 031 import org.crsh.command.CommandInvoker; 032 import org.crsh.command.NoSuchCommandException; 033 import org.crsh.command.GroovyScriptCommand; 034 import org.crsh.command.ScriptException; 035 import org.crsh.command.ShellCommand; 036 import org.crsh.plugin.ResourceKind; 037 import org.crsh.shell.ErrorType; 038 import org.crsh.shell.Shell; 039 import org.crsh.shell.ShellProcess; 040 import org.crsh.shell.ShellProcessContext; 041 import org.crsh.shell.ShellResponse; 042 import org.crsh.text.Chunk; 043 import org.crsh.util.Safe; 044 import org.crsh.util.Utils; 045 046 import java.io.Closeable; 047 import java.security.Principal; 048 import java.util.HashMap; 049 import java.util.Map; 050 import java.util.logging.Level; 051 import java.util.logging.Logger; 052 053 public class CRaSHSession extends HashMap<String, Object> implements Shell, Closeable, CommandContext { 054 055 /** . */ 056 static final Logger log = Logger.getLogger(CRaSHSession.class.getName()); 057 058 /** . */ 059 static final Logger accessLog = Logger.getLogger("org.crsh.shell.access"); 060 061 /** . */ 062 private GroovyShell groovyShell; 063 064 /** . */ 065 final CRaSH crash; 066 067 /** . */ 068 final Principal user; 069 070 /** 071 * Used for testing purposes. 072 * 073 * @return a groovy shell operating on the session attributes 074 */ 075 public GroovyShell getGroovyShell() { 076 if (groovyShell == null) { 077 CompilerConfiguration config = new CompilerConfiguration(); 078 config.setRecompileGroovySource(true); 079 config.setScriptBaseClass(GroovyScriptCommand.class.getName()); 080 groovyShell = new GroovyShell(crash.context.getLoader(), new Binding(this), config); 081 } 082 return groovyShell; 083 } 084 085 public Script getLifeCycle(String name) throws NoSuchCommandException, NullPointerException { 086 Class<? extends Script> scriptClass = crash.lifecycles.getClass(name); 087 if (scriptClass != null) { 088 Script script = InvokerHelper.createScript(scriptClass, new Binding(this)); 089 script.setBinding(new Binding(this)); 090 return script; 091 } else { 092 return null; 093 } 094 } 095 096 CRaSHSession(final CRaSH crash, Principal user) { 097 // Set variable available to all scripts 098 put("crash", crash); 099 100 // 101 this.groovyShell = null; 102 this.crash = crash; 103 this.user = user; 104 105 // 106 try { 107 Script login = getLifeCycle("login"); 108 if (login instanceof CommandInvoker) { 109 ((CommandInvoker)login).setSession(this); 110 } 111 if (login != null) { 112 login.run(); 113 } 114 } 115 catch (NoSuchCommandException e) { 116 e.printStackTrace(); 117 } 118 119 } 120 121 public Map<String, Object> getSession() { 122 return this; 123 } 124 125 public Map<String, Object> getAttributes() { 126 return crash.context.getAttributes(); 127 } 128 129 public void close() { 130 ClassLoader previous = setCRaSHLoader(); 131 try { 132 Script logout = getLifeCycle("logout"); 133 if (logout instanceof CommandInvoker) { 134 ((CommandInvoker)logout).setSession(this); 135 } 136 if (logout != null) { 137 logout.run(); 138 } 139 } 140 catch (NoSuchCommandException e) { 141 e.printStackTrace(); 142 } 143 finally { 144 setPreviousLoader(previous); 145 } 146 } 147 148 // Shell implementation ********************************************************************************************** 149 150 public String getWelcome() { 151 ClassLoader previous = setCRaSHLoader(); 152 try { 153 GroovyShell shell = getGroovyShell(); 154 Object ret = shell.evaluate("welcome();"); 155 return String.valueOf(ret); 156 } 157 finally { 158 setPreviousLoader(previous); 159 } 160 } 161 162 public String getPrompt() { 163 ClassLoader previous = setCRaSHLoader(); 164 try { 165 GroovyShell shell = getGroovyShell(); 166 Object ret = shell.evaluate("prompt();"); 167 return String.valueOf(ret); 168 } 169 finally { 170 setPreviousLoader(previous); 171 } 172 } 173 174 public ShellProcess createProcess(String request) { 175 log.log(Level.FINE, "Invoking request " + request); 176 final ShellResponse response; 177 if ("bye".equals(request) || "exit".equals(request)) { 178 response = ShellResponse.close(); 179 } else { 180 // Create pipeline from request 181 PipeLineParser parser = new PipeLineParser(request); 182 final PipeLineFactory factory = parser.parse(); 183 if (factory != null) { 184 try { 185 final CommandInvoker<Void, Chunk> pipeLine = factory.create(this); 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.open(invocationContext); 193 pipeLine.flush(); 194 return ShellResponse.ok(); 195 } 196 catch (ScriptException e) { 197 return build(e); 198 } catch (Throwable t) { 199 return build(t); 200 } finally { 201 Safe.close(pipeLine); 202 Safe.close(invocationContext); 203 } 204 } 205 206 private ShellResponse.Error build(Throwable throwable) { 207 ErrorType errorType; 208 if (throwable instanceof ScriptException) { 209 errorType = ErrorType.EVALUATION; 210 Throwable cause = throwable.getCause(); 211 if (cause != null) { 212 throwable = cause; 213 } 214 } else { 215 errorType = ErrorType.INTERNAL; 216 } 217 String result; 218 String msg = throwable.getMessage(); 219 if (throwable instanceof ScriptException) { 220 if (msg == null) { 221 result = request + ": failed"; 222 } else { 223 result = request + ": " + msg; 224 } 225 return ShellResponse.error(errorType, result, throwable); 226 } else { 227 if (msg == null) { 228 msg = throwable.getClass().getSimpleName(); 229 } 230 if (throwable instanceof RuntimeException) { 231 result = request + ": exception: " + msg; 232 } else if (throwable instanceof Exception) { 233 result = request + ": exception: " + msg; 234 } else if (throwable instanceof java.lang.Error) { 235 result = request + ": error: " + msg; 236 } else { 237 result = request + ": unexpected throwable: " + msg; 238 } 239 return ShellResponse.error(errorType, result, throwable); 240 } 241 } 242 }; 243 } 244 catch (NoSuchCommandException e) { 245 response = ShellResponse.unknownCommand(e.getCommandName()); 246 } 247 } else { 248 response = ShellResponse.noCommand(); 249 } 250 } 251 252 // 253 return new CRaSHProcess(this, request) { 254 @Override 255 ShellResponse doInvoke(ShellProcessContext context) throws InterruptedException { 256 return response; 257 } 258 }; 259 } 260 261 /** 262 * For now basic implementation 263 */ 264 public CompletionMatch complete(final String prefix) { 265 ClassLoader previous = setCRaSHLoader(); 266 try { 267 log.log(Level.FINE, "Want prefix of " + prefix); 268 PipeLineFactory ast = new PipeLineParser(prefix).parse(); 269 String termPrefix; 270 if (ast != null) { 271 PipeLineFactory last = ast.getLast(); 272 termPrefix = Utils.trimLeft(last.getLine()); 273 } else { 274 termPrefix = ""; 275 } 276 277 // 278 log.log(Level.FINE, "Retained term prefix is " + prefix); 279 CompletionMatch completion; 280 int pos = termPrefix.indexOf(' '); 281 if (pos == -1) { 282 Completion.Builder builder = Completion.builder(prefix); 283 for (String resourceId : crash.context.listResourceId(ResourceKind.COMMAND)) { 284 if (resourceId.startsWith(termPrefix)) { 285 builder.add(resourceId.substring(termPrefix.length()), true); 286 } 287 } 288 completion = new CompletionMatch(Delimiter.EMPTY, builder.build()); 289 } else { 290 String commandName = termPrefix.substring(0, pos); 291 termPrefix = termPrefix.substring(pos); 292 try { 293 ShellCommand command = crash.getCommand(commandName); 294 if (command != null) { 295 completion = command.complete(new BaseCommandContext(this, crash.context.getAttributes()), termPrefix); 296 } else { 297 completion = new CompletionMatch(Delimiter.EMPTY, Completion.create()); 298 } 299 } 300 catch (NoSuchCommandException e) { 301 log.log(Level.FINE, "Could not create command for completion of " + prefix, e); 302 completion = new CompletionMatch(Delimiter.EMPTY, Completion.create()); 303 } 304 } 305 306 // 307 log.log(Level.FINE, "Found completions for " + prefix + ": " + completion); 308 return completion; 309 } 310 finally { 311 setPreviousLoader(previous); 312 } 313 } 314 315 ClassLoader setCRaSHLoader() { 316 Thread thread = Thread.currentThread(); 317 ClassLoader previous = thread.getContextClassLoader(); 318 thread.setContextClassLoader(crash.context.getLoader()); 319 return previous; 320 } 321 322 void setPreviousLoader(ClassLoader previous) { 323 Thread.currentThread().setContextClassLoader(previous); 324 } 325 }