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 groovy.lang.Binding;
022    import groovy.lang.GroovyShell;
023    import groovy.lang.Script;
024    import org.codehaus.groovy.control.CompilerConfiguration;
025    import org.codehaus.groovy.runtime.InvokerHelper;
026    import org.crsh.cmdline.CommandCompletion;
027    import org.crsh.cmdline.Delimiter;
028    import org.crsh.cmdline.spi.ValueCompletion;
029    import org.crsh.command.BaseCommandContext;
030    import org.crsh.command.NoSuchCommandException;
031    import org.crsh.command.GroovyScriptCommand;
032    import org.crsh.command.ShellCommand;
033    import org.crsh.plugin.ResourceKind;
034    import org.crsh.shell.Shell;
035    import org.crsh.shell.ShellProcess;
036    import org.crsh.shell.ShellProcessContext;
037    import org.crsh.shell.ShellResponse;
038    import org.crsh.util.Utils;
039    import org.slf4j.Logger;
040    import org.slf4j.LoggerFactory;
041    
042    import java.io.Closeable;
043    import java.security.Principal;
044    import java.util.HashMap;
045    
046    public class CRaSHSession extends HashMap<String, Object> implements Shell, Closeable {
047    
048      /** . */
049      static final Logger log = LoggerFactory.getLogger(CRaSHSession.class);
050    
051      /** . */
052      static final Logger accessLog = LoggerFactory.getLogger("org.crsh.shell.access");
053    
054      /** . */
055      private GroovyShell groovyShell;
056    
057      /** . */
058      final CRaSH crash;
059    
060      /** . */
061      final Principal user;
062    
063      /**
064       * Used for testing purposes.
065       *
066       * @return a groovy shell operating on the session attributes
067       */
068      public GroovyShell getGroovyShell() {
069        if (groovyShell == null) {
070          CompilerConfiguration config = new CompilerConfiguration();
071          config.setRecompileGroovySource(true);
072          config.setScriptBaseClass(GroovyScriptCommand.class.getName());
073          groovyShell = new GroovyShell(crash.context.getLoader(), new Binding(this), config);
074        }
075        return groovyShell;
076      }
077    
078      public Script getLifeCycle(String name) throws NoSuchCommandException, NullPointerException {
079        Class<? extends Script> scriptClass = crash.lifecycles.getClass(name);
080        if (scriptClass != null) {
081          Script script = InvokerHelper.createScript(scriptClass, new Binding(this));
082          script.setBinding(new Binding(this));
083          return script;
084        } else {
085          return null;
086        }
087      }
088    
089      CRaSHSession(final CRaSH crash, Principal user) {
090        // Set variable available to all scripts
091        put("crash", crash);
092    
093        //
094        this.groovyShell = null;
095        this.crash = crash;
096        this.user = user;
097    
098        //
099        try {
100          Script login = getLifeCycle("login");
101          if (login != null) {
102            login.run();
103          }
104        }
105        catch (NoSuchCommandException e) {
106          e.printStackTrace();
107        }
108    
109      }
110    
111      public void close() {
112        ClassLoader previous = setCRaSHLoader();
113        try {
114          Script login = getLifeCycle("logout");
115          if (login != null) {
116            login.run();
117          }
118        }
119        catch (NoSuchCommandException e) {
120          e.printStackTrace();
121        }
122        finally {
123          setPreviousLoader(previous);
124        }
125      }
126    
127      // Shell implementation **********************************************************************************************
128    
129      public String getWelcome() {
130        ClassLoader previous = setCRaSHLoader();
131        try {
132          GroovyShell shell = getGroovyShell();
133          Object ret = shell.evaluate("welcome();");
134          return String.valueOf(ret);
135        }
136        finally {
137          setPreviousLoader(previous);
138        }
139      }
140    
141      public String getPrompt() {
142        ClassLoader previous = setCRaSHLoader();
143        try {
144          GroovyShell shell = getGroovyShell();
145          Object ret = shell.evaluate("prompt();");
146          return String.valueOf(ret);
147        }
148        finally {
149          setPreviousLoader(previous);
150        }
151      }
152    
153      public ShellProcess createProcess(String request) {
154        log.debug("Invoking request " + request);
155        final ShellResponse response;
156        if ("bye".equals(request) || "exit".equals(request)) {
157          response = ShellResponse.close();
158        } else {
159          // Create pipeline from request
160          Parser parser = new Parser(request);
161          PipeLine pipeline = parser.parse();
162          if (pipeline != null) {
163            // Create commands first
164            try {
165              return pipeline.create(this, request);
166            } catch (NoSuchCommandException e) {
167              log.error("Could not create shell process", e);
168              response = ShellResponse.unknownCommand(e.getCommandName());
169            }
170          } else {
171            response = ShellResponse.noCommand();
172          }
173        }
174    
175        //
176        return new CRaSHProcess(this, request) {
177          @Override
178          ShellResponse doInvoke(ShellProcessContext context) throws InterruptedException {
179            return response;
180          }
181        };
182      }
183    
184      /**
185       * For now basic implementation
186       */
187      public CommandCompletion complete(final String prefix) {
188        ClassLoader previous = setCRaSHLoader();
189        try {
190          log.debug("Want prefix of " + prefix);
191          PipeLine ast = new Parser(prefix).parse();
192          String termPrefix;
193          if (ast != null) {
194            PipeLine last = ast.getLast();
195            termPrefix = Utils.trimLeft(last.getLine());
196          } else {
197            termPrefix = "";
198          }
199    
200          //
201          log.debug("Retained term prefix is " + prefix);
202          CommandCompletion completion;
203          int pos = termPrefix.indexOf(' ');
204          if (pos == -1) {
205            ValueCompletion completions = ValueCompletion.create();
206            for (String resourceId : crash.context.listResourceId(ResourceKind.COMMAND)) {
207              if (resourceId.startsWith(termPrefix)) {
208                completions.put(resourceId.substring(termPrefix.length()), true);
209              }
210            }
211            completion = new CommandCompletion(Delimiter.EMPTY, completions);
212          } else {
213            String commandName = termPrefix.substring(0, pos);
214            termPrefix = termPrefix.substring(pos);
215            try {
216              ShellCommand command = crash.getCommand(commandName);
217              if (command != null) {
218                completion = command.complete(new BaseCommandContext(this, crash.context.getAttributes()), termPrefix);
219              } else {
220                completion = new CommandCompletion(Delimiter.EMPTY, ValueCompletion.create());
221              }
222            }
223            catch (NoSuchCommandException e) {
224              log.debug("Could not create command for completion of " + prefix, e);
225              completion = new CommandCompletion(Delimiter.EMPTY, ValueCompletion.create());
226            }
227          }
228    
229          //
230          log.debug("Found completions for " + prefix + ": " + completion);
231          return completion;
232        }
233        finally {
234          setPreviousLoader(previous);
235        }
236      }
237    
238      ClassLoader setCRaSHLoader() {
239        Thread thread = Thread.currentThread();
240        ClassLoader previous = thread.getContextClassLoader();
241        thread.setContextClassLoader(crash.context.getLoader());
242        return previous;
243      }
244    
245      void setPreviousLoader(ClassLoader previous) {
246        Thread.currentThread().setContextClassLoader(previous);
247      }
248    }