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