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