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    }