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