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 = ScriptREPL.getInstance();
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        if ("script".equals(name)) {
162          found = ScriptREPL.getInstance();
163        } else {
164          for (REPL repl : crash.getContext().getPlugins(REPL.class)) {
165            if (repl.getName().equals(name)) {
166              found = repl;
167              break;
168            }
169          }
170        }
171        if (found != null) {
172          repl = found;
173          msg.append("Using repl ").append(found.getName());
174          response = ShellResponse.ok();
175        } else {
176          response = ShellResponse.error(ErrorType.EVALUATION, "Repl " + name + " not found");
177        }
178      }
179    } else {
180      EvalResponse r = repl.eval(this, request);
181      if (r instanceof EvalResponse.Response) {
182        EvalResponse.Response rr = (EvalResponse.Response)r;
183        response = rr.response;
184      } else {
185        final CommandInvoker<Void, ?> pipeLine = ((EvalResponse.Invoke)r).invoker;
186        return new CRaSHProcess(this, request) {
187
188          @Override
189          ShellResponse doInvoke(final ShellProcessContext context) throws InterruptedException {
190            CRaSHProcessContext invocationContext = new CRaSHProcessContext(CRaSHSession.this, context);
191            try {
192              pipeLine.invoke(invocationContext);
193              return ShellResponse.ok();
194            }
195            catch (ScriptException e) {
196              return build(e);
197            } catch (Throwable t) {
198              return build(t);
199            } finally {
200              Safe.close(invocationContext);
201            }
202          }
203
204          private ShellResponse.Error build(Throwable throwable) {
205            ErrorType errorType;
206            if (throwable instanceof ScriptException || throwable instanceof UndeclaredThrowableException) {
207              errorType = ErrorType.EVALUATION;
208              Throwable cause = throwable.getCause();
209              if (cause != null) {
210                throwable = cause;
211              }
212            } else {
213              errorType = ErrorType.INTERNAL;
214            }
215            String result;
216            String msg = throwable.getMessage();
217            if (throwable instanceof ScriptException) {
218              if (msg == null) {
219                result = request + ": failed";
220              } else {
221                result = request + ": " + msg;
222              }
223              return ShellResponse.error(errorType, result, throwable);
224            } else {
225              if (msg == null) {
226                msg = throwable.getClass().getSimpleName();
227              }
228              if (throwable instanceof RuntimeException) {
229                result = request + ": exception: " + msg;
230              } else if (throwable instanceof Exception) {
231                result = request + ": exception: " + msg;
232              } else if (throwable instanceof java.lang.Error) {
233                result = request + ": error: " + msg;
234              } else {
235                result = request + ": unexpected throwable: " + msg;
236              }
237              return ShellResponse.error(errorType, result, throwable);
238            }
239          }
240        };
241      }
242    }
243    return new CRaSHProcess(this, request) {
244      @Override
245      ShellResponse doInvoke(ShellProcessContext context) throws InterruptedException {
246        if (msg.length() > 0) {
247          try {
248            context.write(Text.create(msg));
249          }
250          catch (IOException ignore) {
251          }
252        }
253        return response;
254      }
255    };
256  }
257
258  /**
259   * For now basic implementation
260   */
261  public CompletionMatch complete(final String prefix) {
262    ClassLoader previous = setCRaSHLoader();
263    try {
264      return repl.complete(this, prefix);
265    }
266    finally {
267      setPreviousLoader(previous);
268    }
269  }
270
271  ClassLoader setCRaSHLoader() {
272    Thread thread = Thread.currentThread();
273    ClassLoader previous = thread.getContextClassLoader();
274    thread.setContextClassLoader(crash.context.getLoader());
275    return previous;
276  }
277
278  void setPreviousLoader(ClassLoader previous) {
279    Thread.currentThread().setContextClassLoader(previous);
280  }
281}