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 }