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