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    }