001 /* 002 * Copyright (C) 2003-2009 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; 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.cmdline.CommandCompletion; 027 import org.crsh.cmdline.Delimiter; 028 import org.crsh.cmdline.spi.ValueCompletion; 029 import org.crsh.command.NoSuchCommandException; 030 import org.crsh.command.impl.BaseCommandContext; 031 import org.crsh.command.GroovyScriptCommand; 032 import org.crsh.command.ShellCommand; 033 import org.crsh.plugin.ResourceKind; 034 import org.crsh.shell.Shell; 035 import org.crsh.shell.ShellProcess; 036 import org.crsh.shell.ShellProcessContext; 037 import org.crsh.shell.ShellResponse; 038 import org.crsh.util.Utils; 039 import org.slf4j.Logger; 040 import org.slf4j.LoggerFactory; 041 042 import java.io.Closeable; 043 import java.security.Principal; 044 import java.util.HashMap; 045 import java.util.Map; 046 047 /** 048 * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a> 049 * @version $Revision$ 050 */ 051 public class CRaSHSession implements Shell, Closeable { 052 053 /** . */ 054 static final Logger log = LoggerFactory.getLogger(CRaSHSession.class); 055 056 /** . */ 057 static final Logger accessLog = LoggerFactory.getLogger("org.crsh.shell.access"); 058 059 /** . */ 060 private GroovyShell groovyShell; 061 062 /** . */ 063 final CRaSH crash; 064 065 /** . */ 066 final Map<String, Object> attributes; 067 068 /** . */ 069 final Principal user; 070 071 /** 072 * Used for testing purposes. 073 * 074 * @return a groovy shell operating on the session attributes 075 */ 076 public GroovyShell getGroovyShell() { 077 if (groovyShell == null) { 078 CompilerConfiguration config = new CompilerConfiguration(); 079 config.setRecompileGroovySource(true); 080 config.setScriptBaseClass(GroovyScriptCommand.class.getName()); 081 groovyShell = new GroovyShell(crash.context.getLoader(), new Binding(attributes), config); 082 } 083 return groovyShell; 084 } 085 086 public Script getLifeCycle(String name) throws NoSuchCommandException, NullPointerException { 087 Class<? extends Script> scriptClass = crash.lifecycles.getClass(name); 088 if (scriptClass != null) { 089 Script script = InvokerHelper.createScript(scriptClass, new Binding(attributes)); 090 script.setBinding(new Binding(attributes)); 091 return script; 092 } else { 093 return null; 094 } 095 } 096 097 public Object getAttribute(String name) { 098 return attributes.get(name); 099 } 100 101 public void setAttribute(String name, Object value) { 102 attributes.put(name, value); 103 } 104 105 CRaSHSession(final CRaSH crash, Principal user) { 106 HashMap<String, Object> attributes = new HashMap<String, Object>(); 107 108 // Set variable available to all scripts 109 attributes.put("crash", crash); 110 111 // 112 this.attributes = attributes; 113 this.groovyShell = null; 114 this.crash = crash; 115 this.user = user; 116 117 // 118 try { 119 Script login = getLifeCycle("login"); 120 if (login != null) { 121 login.run(); 122 } 123 } 124 catch (NoSuchCommandException e) { 125 e.printStackTrace(); 126 } 127 128 } 129 130 public void close() { 131 ClassLoader previous = setCRaSHLoader(); 132 try { 133 Script login = getLifeCycle("logout"); 134 if (login != null) { 135 login.run(); 136 } 137 } 138 catch (NoSuchCommandException e) { 139 e.printStackTrace(); 140 } 141 finally { 142 setPreviousLoader(previous); 143 } 144 } 145 146 // Shell implementation ********************************************************************************************** 147 148 public String getWelcome() { 149 ClassLoader previous = setCRaSHLoader(); 150 try { 151 GroovyShell shell = getGroovyShell(); 152 Object ret = shell.evaluate("welcome();"); 153 return String.valueOf(ret); 154 } 155 finally { 156 setPreviousLoader(previous); 157 } 158 } 159 160 public String getPrompt() { 161 ClassLoader previous = setCRaSHLoader(); 162 try { 163 GroovyShell shell = getGroovyShell(); 164 Object ret = shell.evaluate("prompt();"); 165 return String.valueOf(ret); 166 } 167 finally { 168 setPreviousLoader(previous); 169 } 170 } 171 172 public ShellProcess createProcess(String request) { 173 log.debug("Invoking request " + request); 174 final ShellResponse response; 175 if ("bye".equals(request) || "exit".equals(request)) { 176 response = ShellResponse.close(); 177 } else { 178 179 // Create AST 180 Parser parser = new Parser(request); 181 AST ast = parser.parse(); 182 183 // 184 if (ast instanceof AST.Expr) { 185 AST.Expr expr = (AST.Expr)ast; 186 187 // Create commands first 188 try { 189 return expr.create(this, request); 190 } catch (NoSuchCommandException e) { 191 log.error("Could not create shell process", e); 192 response = ShellResponse.unknownCommand(e.getCommandName()); 193 } 194 } else { 195 response = ShellResponse.noCommand(); 196 } 197 } 198 199 // 200 return new CRaSHProcess(this, request) { 201 @Override 202 ShellResponse doInvoke(ShellProcessContext context) throws InterruptedException { 203 return response; 204 } 205 }; 206 } 207 208 /** 209 * For now basic implementation 210 */ 211 public CommandCompletion complete(final String prefix) { 212 ClassLoader previous = setCRaSHLoader(); 213 try { 214 log.debug("Want prefix of " + prefix); 215 AST ast = new Parser(prefix).parse(); 216 String termPrefix; 217 if (ast != null) { 218 AST.Term last = ast.lastTerm(); 219 termPrefix = Utils.trimLeft(last.getLine()); 220 } else { 221 termPrefix = ""; 222 } 223 224 // 225 log.debug("Retained term prefix is " + prefix); 226 CommandCompletion completion; 227 int pos = termPrefix.indexOf(' '); 228 if (pos == -1) { 229 ValueCompletion completions = ValueCompletion.create(); 230 for (String resourceId : crash.context.listResourceId(ResourceKind.COMMAND)) { 231 if (resourceId.startsWith(termPrefix)) { 232 completions.put(resourceId.substring(termPrefix.length()), true); 233 } 234 } 235 completion = new CommandCompletion(Delimiter.EMPTY, completions); 236 } else { 237 String commandName = termPrefix.substring(0, pos); 238 termPrefix = termPrefix.substring(pos); 239 try { 240 ShellCommand command = crash.getCommand(commandName); 241 if (command != null) { 242 completion = command.complete(new BaseCommandContext(attributes), termPrefix); 243 } else { 244 completion = new CommandCompletion(Delimiter.EMPTY, ValueCompletion.create()); 245 } 246 } 247 catch (NoSuchCommandException e) { 248 log.debug("Could not create command for completion of " + prefix, e); 249 completion = new CommandCompletion(Delimiter.EMPTY, ValueCompletion.create()); 250 } 251 } 252 253 // 254 log.debug("Found completions for " + prefix + ": " + completion); 255 return completion; 256 } 257 finally { 258 setPreviousLoader(previous); 259 } 260 } 261 262 ClassLoader setCRaSHLoader() { 263 Thread thread = Thread.currentThread(); 264 ClassLoader previous = thread.getContextClassLoader(); 265 thread.setContextClassLoader(crash.context.getLoader()); 266 return previous; 267 } 268 269 void setPreviousLoader(ClassLoader previous) { 270 Thread.currentThread().setContextClassLoader(previous); 271 } 272 }