001 /*
002 * Copyright (C) 2012 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.command;
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.BaseCommandContext;
030 import org.crsh.command.NoSuchCommandException;
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
046 public class CRaSHSession extends HashMap<String, Object> implements Shell, Closeable {
047
048 /** . */
049 static final Logger log = LoggerFactory.getLogger(CRaSHSession.class);
050
051 /** . */
052 static final Logger accessLog = LoggerFactory.getLogger("org.crsh.shell.access");
053
054 /** . */
055 private GroovyShell groovyShell;
056
057 /** . */
058 final CRaSH crash;
059
060 /** . */
061 final Principal user;
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(this), config);
074 }
075 return groovyShell;
076 }
077
078 public Script getLifeCycle(String name) throws NoSuchCommandException, NullPointerException {
079 Class<? extends Script> scriptClass = crash.lifecycles.getClass(name);
080 if (scriptClass != null) {
081 Script script = InvokerHelper.createScript(scriptClass, new Binding(this));
082 script.setBinding(new Binding(this));
083 return script;
084 } else {
085 return null;
086 }
087 }
088
089 CRaSHSession(final CRaSH crash, Principal user) {
090 // Set variable available to all scripts
091 put("crash", crash);
092
093 //
094 this.groovyShell = null;
095 this.crash = crash;
096 this.user = user;
097
098 //
099 try {
100 Script login = getLifeCycle("login");
101 if (login != null) {
102 login.run();
103 }
104 }
105 catch (NoSuchCommandException e) {
106 e.printStackTrace();
107 }
108
109 }
110
111 public void close() {
112 ClassLoader previous = setCRaSHLoader();
113 try {
114 Script login = getLifeCycle("logout");
115 if (login != null) {
116 login.run();
117 }
118 }
119 catch (NoSuchCommandException e) {
120 e.printStackTrace();
121 }
122 finally {
123 setPreviousLoader(previous);
124 }
125 }
126
127 // Shell implementation **********************************************************************************************
128
129 public String getWelcome() {
130 ClassLoader previous = setCRaSHLoader();
131 try {
132 GroovyShell shell = getGroovyShell();
133 Object ret = shell.evaluate("welcome();");
134 return String.valueOf(ret);
135 }
136 finally {
137 setPreviousLoader(previous);
138 }
139 }
140
141 public String getPrompt() {
142 ClassLoader previous = setCRaSHLoader();
143 try {
144 GroovyShell shell = getGroovyShell();
145 Object ret = shell.evaluate("prompt();");
146 return String.valueOf(ret);
147 }
148 finally {
149 setPreviousLoader(previous);
150 }
151 }
152
153 public ShellProcess createProcess(String request) {
154 log.debug("Invoking request " + request);
155 final ShellResponse response;
156 if ("bye".equals(request) || "exit".equals(request)) {
157 response = ShellResponse.close();
158 } else {
159 // Create pipeline from request
160 Parser parser = new Parser(request);
161 PipeLine pipeline = parser.parse();
162 if (pipeline != null) {
163 // Create commands first
164 try {
165 return pipeline.create(this, request);
166 } catch (NoSuchCommandException e) {
167 log.error("Could not create shell process", e);
168 response = ShellResponse.unknownCommand(e.getCommandName());
169 }
170 } else {
171 response = ShellResponse.noCommand();
172 }
173 }
174
175 //
176 return new CRaSHProcess(this, request) {
177 @Override
178 ShellResponse doInvoke(ShellProcessContext context) throws InterruptedException {
179 return response;
180 }
181 };
182 }
183
184 /**
185 * For now basic implementation
186 */
187 public CommandCompletion complete(final String prefix) {
188 ClassLoader previous = setCRaSHLoader();
189 try {
190 log.debug("Want prefix of " + prefix);
191 PipeLine ast = new Parser(prefix).parse();
192 String termPrefix;
193 if (ast != null) {
194 PipeLine last = ast.getLast();
195 termPrefix = Utils.trimLeft(last.getLine());
196 } else {
197 termPrefix = "";
198 }
199
200 //
201 log.debug("Retained term prefix is " + prefix);
202 CommandCompletion completion;
203 int pos = termPrefix.indexOf(' ');
204 if (pos == -1) {
205 ValueCompletion completions = ValueCompletion.create();
206 for (String resourceId : crash.context.listResourceId(ResourceKind.COMMAND)) {
207 if (resourceId.startsWith(termPrefix)) {
208 completions.put(resourceId.substring(termPrefix.length()), true);
209 }
210 }
211 completion = new CommandCompletion(Delimiter.EMPTY, completions);
212 } else {
213 String commandName = termPrefix.substring(0, pos);
214 termPrefix = termPrefix.substring(pos);
215 try {
216 ShellCommand command = crash.getCommand(commandName);
217 if (command != null) {
218 completion = command.complete(new BaseCommandContext(this, crash.context.getAttributes()), termPrefix);
219 } else {
220 completion = new CommandCompletion(Delimiter.EMPTY, ValueCompletion.create());
221 }
222 }
223 catch (NoSuchCommandException e) {
224 log.debug("Could not create command for completion of " + prefix, e);
225 completion = new CommandCompletion(Delimiter.EMPTY, ValueCompletion.create());
226 }
227 }
228
229 //
230 log.debug("Found completions for " + prefix + ": " + completion);
231 return completion;
232 }
233 finally {
234 setPreviousLoader(previous);
235 }
236 }
237
238 ClassLoader setCRaSHLoader() {
239 Thread thread = Thread.currentThread();
240 ClassLoader previous = thread.getContextClassLoader();
241 thread.setContextClassLoader(crash.context.getLoader());
242 return previous;
243 }
244
245 void setPreviousLoader(ClassLoader previous) {
246 Thread.currentThread().setContextClassLoader(previous);
247 }
248 }