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