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          PipeLineParser parser = new PipeLineParser(request);
161          PipeLineFactory pipeline = parser.parse();
162          if (pipeline != null) {
163            // Create commands first
164            return pipeline.create(this, request);
165          } else {
166            response = ShellResponse.noCommand();
167          }
168        }
169    
170        //
171        return new CRaSHProcess(this, request) {
172          @Override
173          ShellResponse doInvoke(ShellProcessContext context) throws InterruptedException {
174            return response;
175          }
176        };
177      }
178    
179      /**
180       * For now basic implementation
181       */
182      public CommandCompletion complete(final String prefix) {
183        ClassLoader previous = setCRaSHLoader();
184        try {
185          log.debug("Want prefix of " + prefix);
186          PipeLineFactory ast = new PipeLineParser(prefix).parse();
187          String termPrefix;
188          if (ast != null) {
189            PipeLineFactory last = ast.getLast();
190            termPrefix = Utils.trimLeft(last.getLine());
191          } else {
192            termPrefix = "";
193          }
194    
195          //
196          log.debug("Retained term prefix is " + prefix);
197          CommandCompletion completion;
198          int pos = termPrefix.indexOf(' ');
199          if (pos == -1) {
200            ValueCompletion completions = ValueCompletion.create();
201            for (String resourceId : crash.context.listResourceId(ResourceKind.COMMAND)) {
202              if (resourceId.startsWith(termPrefix)) {
203                completions.put(resourceId.substring(termPrefix.length()), true);
204              }
205            }
206            completion = new CommandCompletion(Delimiter.EMPTY, completions);
207          } else {
208            String commandName = termPrefix.substring(0, pos);
209            termPrefix = termPrefix.substring(pos);
210            try {
211              ShellCommand command = crash.getCommand(commandName);
212              if (command != null) {
213                completion = command.complete(new BaseCommandContext(this, crash.context.getAttributes()), termPrefix);
214              } else {
215                completion = new CommandCompletion(Delimiter.EMPTY, ValueCompletion.create());
216              }
217            }
218            catch (NoSuchCommandException e) {
219              log.debug("Could not create command for completion of " + prefix, e);
220              completion = new CommandCompletion(Delimiter.EMPTY, ValueCompletion.create());
221            }
222          }
223    
224          //
225          log.debug("Found completions for " + prefix + ": " + completion);
226          return completion;
227        }
228        finally {
229          setPreviousLoader(previous);
230        }
231      }
232    
233      ClassLoader setCRaSHLoader() {
234        Thread thread = Thread.currentThread();
235        ClassLoader previous = thread.getContextClassLoader();
236        thread.setContextClassLoader(crash.context.getLoader());
237        return previous;
238      }
239    
240      void setPreviousLoader(ClassLoader previous) {
241        Thread.currentThread().setContextClassLoader(previous);
242      }
243    }