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 }