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 org.crsh.cli.impl.completion.CompletionMatch;
022 import org.crsh.cli.spi.Completion;
023 import org.crsh.command.BaseRuntimeContext;
024 import org.crsh.command.RuntimeContext;
025 import org.crsh.cli.impl.Delimiter;
026 import org.crsh.command.CommandInvoker;
027 import org.crsh.command.NoSuchCommandException;
028 import org.crsh.command.ScriptException;
029 import org.crsh.command.ShellCommand;
030 import org.crsh.lang.CommandManager;
031 import org.crsh.plugin.ResourceKind;
032 import org.crsh.shell.ErrorType;
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.text.Chunk;
038 import org.crsh.util.Safe;
039 import org.crsh.util.Utils;
040
041 import java.io.Closeable;
042 import java.security.Principal;
043 import java.util.HashMap;
044 import java.util.Map;
045 import java.util.logging.Level;
046 import java.util.logging.Logger;
047
048 public class CRaSHSession extends HashMap<String, Object> implements Shell, Closeable, RuntimeContext {
049
050 /** . */
051 static final Logger log = Logger.getLogger(CRaSHSession.class.getName());
052
053 /** . */
054 static final Logger accessLog = Logger.getLogger("org.crsh.shell.access");
055
056 /** . */
057 final CRaSH crash;
058
059 /** . */
060 final Principal user;
061
062 public CommandManager getCommandManager() {
063 return crash.commandManager;
064 }
065
066 CRaSHSession(final CRaSH crash, Principal user) {
067 // Set variable available to all scripts
068 put("crash", crash);
069
070 //
071 this.crash = crash;
072 this.user = user;
073
074 //
075 ClassLoader previous = setCRaSHLoader();
076 try {
077 crash.commandManager.init(this);
078 }
079 finally {
080 setPreviousLoader(previous);
081 }
082 }
083
084 public Map<String, Object> getSession() {
085 return this;
086 }
087
088 public Map<String, Object> getAttributes() {
089 return crash.context.getAttributes();
090 }
091
092 public void close() {
093 ClassLoader previous = setCRaSHLoader();
094 try {
095 crash.commandManager.destroy(this);
096 }
097 finally {
098 setPreviousLoader(previous);
099 }
100 }
101
102 // Shell implementation **********************************************************************************************
103
104 public String getWelcome() {
105 ClassLoader previous = setCRaSHLoader();
106 try {
107 return crash.commandManager.doCallBack(this, "welcome", "");
108 }
109 finally {
110 setPreviousLoader(previous);
111 }
112 }
113
114 public String getPrompt() {
115 ClassLoader previous = setCRaSHLoader();
116 try {
117 return crash.commandManager.doCallBack(this, "prompt", "% ");
118 }
119 finally {
120 setPreviousLoader(previous);
121 }
122 }
123
124 public ShellProcess createProcess(String request) {
125 log.log(Level.FINE, "Invoking request " + request);
126 final ShellResponse response;
127 if ("bye".equals(request) || "exit".equals(request)) {
128 response = ShellResponse.close();
129 } else {
130 // Create pipeline from request
131 PipeLineParser parser = new PipeLineParser(request);
132 final PipeLineFactory factory = parser.parse();
133 if (factory != null) {
134 try {
135 final CommandInvoker<Void, Chunk> pipeLine = factory.create(this);
136 return new CRaSHProcess(this, request) {
137
138 @Override
139 ShellResponse doInvoke(final ShellProcessContext context) throws InterruptedException {
140 CRaSHProcessContext invocationContext = new CRaSHProcessContext(CRaSHSession.this, context);
141 try {
142 pipeLine.open(invocationContext);
143 pipeLine.flush();
144 return ShellResponse.ok();
145 }
146 catch (ScriptException e) {
147 return build(e);
148 } catch (Throwable t) {
149 return build(t);
150 } finally {
151 Safe.close(pipeLine);
152 Safe.close(invocationContext);
153 }
154 }
155
156 private ShellResponse.Error build(Throwable throwable) {
157 ErrorType errorType;
158 if (throwable instanceof ScriptException) {
159 errorType = ErrorType.EVALUATION;
160 Throwable cause = throwable.getCause();
161 if (cause != null) {
162 throwable = cause;
163 }
164 } else {
165 errorType = ErrorType.INTERNAL;
166 }
167 String result;
168 String msg = throwable.getMessage();
169 if (throwable instanceof ScriptException) {
170 if (msg == null) {
171 result = request + ": failed";
172 } else {
173 result = request + ": " + msg;
174 }
175 return ShellResponse.error(errorType, result, throwable);
176 } else {
177 if (msg == null) {
178 msg = throwable.getClass().getSimpleName();
179 }
180 if (throwable instanceof RuntimeException) {
181 result = request + ": exception: " + msg;
182 } else if (throwable instanceof Exception) {
183 result = request + ": exception: " + msg;
184 } else if (throwable instanceof java.lang.Error) {
185 result = request + ": error: " + msg;
186 } else {
187 result = request + ": unexpected throwable: " + msg;
188 }
189 return ShellResponse.error(errorType, result, throwable);
190 }
191 }
192 };
193 }
194 catch (NoSuchCommandException e) {
195 response = ShellResponse.unknownCommand(e.getCommandName());
196 }
197 } else {
198 response = ShellResponse.noCommand();
199 }
200 }
201
202 //
203 return new CRaSHProcess(this, request) {
204 @Override
205 ShellResponse doInvoke(ShellProcessContext context) throws InterruptedException {
206 return response;
207 }
208 };
209 }
210
211 /**
212 * For now basic implementation
213 */
214 public CompletionMatch complete(final String prefix) {
215 ClassLoader previous = setCRaSHLoader();
216 try {
217 log.log(Level.FINE, "Want prefix of " + prefix);
218 PipeLineFactory ast = new PipeLineParser(prefix).parse();
219 String termPrefix;
220 if (ast != null) {
221 PipeLineFactory last = ast.getLast();
222 termPrefix = Utils.trimLeft(last.getLine());
223 } else {
224 termPrefix = "";
225 }
226
227 //
228 log.log(Level.FINE, "Retained term prefix is " + prefix);
229 CompletionMatch completion;
230 int pos = termPrefix.indexOf(' ');
231 if (pos == -1) {
232 Completion.Builder builder = Completion.builder(prefix);
233 for (String resourceId : crash.context.listResourceId(ResourceKind.COMMAND)) {
234 if (resourceId.startsWith(termPrefix)) {
235 builder.add(resourceId.substring(termPrefix.length()), true);
236 }
237 }
238 completion = new CompletionMatch(Delimiter.EMPTY, builder.build());
239 } else {
240 String commandName = termPrefix.substring(0, pos);
241 termPrefix = termPrefix.substring(pos);
242 try {
243 ShellCommand command = crash.getCommand(commandName);
244 if (command != null) {
245 completion = command.complete(new BaseRuntimeContext(this, crash.context.getAttributes()), termPrefix);
246 } else {
247 completion = new CompletionMatch(Delimiter.EMPTY, Completion.create());
248 }
249 }
250 catch (NoSuchCommandException e) {
251 log.log(Level.FINE, "Could not create command for completion of " + prefix, e);
252 completion = new CompletionMatch(Delimiter.EMPTY, Completion.create());
253 }
254 }
255
256 //
257 log.log(Level.FINE, "Found completions for " + prefix + ": " + completion);
258 return completion;
259 }
260 finally {
261 setPreviousLoader(previous);
262 }
263 }
264
265 ClassLoader setCRaSHLoader() {
266 Thread thread = Thread.currentThread();
267 ClassLoader previous = thread.getContextClassLoader();
268 thread.setContextClassLoader(crash.context.getLoader());
269 return previous;
270 }
271
272 void setPreviousLoader(ClassLoader previous) {
273 Thread.currentThread().setContextClassLoader(previous);
274 }
275 }