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