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.impl.BaseCommandContext;
030    import org.crsh.command.GroovyScriptCommand;
031    import org.crsh.command.ShellCommand;
032    import org.crsh.plugin.ResourceKind;
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.util.Utils;
038    import org.slf4j.Logger;
039    import org.slf4j.LoggerFactory;
040    
041    import java.io.Closeable;
042    import java.util.HashMap;
043    import java.util.Map;
044    
045    /**
046     * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
047     * @version $Revision$
048     */
049    public class CRaSHSession implements Shell, Closeable {
050    
051      /** . */
052      static final Logger log = LoggerFactory.getLogger(CRaSHSession.class);
053    
054      /** . */
055      private GroovyShell groovyShell;
056    
057      /** . */
058      private final CRaSH crash;
059    
060      /** . */
061      final Map<String, Object> attributes;
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(attributes), config);
074        }
075        return groovyShell;
076      }
077    
078      /**
079       * Attempt to obtain a command instance. Null is returned when such command does not exist.
080       *
081       * @param name the command name
082       * @return a command instance
083       * @throws CreateCommandException if an error occured preventing the command creation
084       * @throws NullPointerException if the name argument is null
085       */
086      public ShellCommand getCommand(String name) throws CreateCommandException, NullPointerException {
087        return crash.commands.getInstance(name);
088      }
089    
090      public Script getLifeCycle(String name) throws CreateCommandException, NullPointerException {
091        Class<? extends Script> scriptClass = crash.lifecycles.getClass(name);
092        if (scriptClass != null) {
093          Script script = InvokerHelper.createScript(scriptClass, new Binding(attributes));
094          script.setBinding(new Binding(attributes));
095          return script;
096        } else {
097          return null;
098        }
099      }
100    
101      public Object getAttribute(String name) {
102        return attributes.get(name);
103      }
104    
105      public void setAttribute(String name, Object value) {
106        attributes.put(name, value);
107      }
108    
109      CRaSHSession(final CRaSH crash) {
110        HashMap<String, Object> attributes = new HashMap<String, Object>();
111    
112        // Set variable available to all scripts
113        attributes.put("shellContext", crash.context);
114        attributes.put("shell", this);
115    
116        //
117        this.attributes = attributes;
118        this.groovyShell = null;
119        this.crash = crash;
120    
121        //
122        try {
123          Script login = getLifeCycle("login");
124          if (login != null) {
125            login.run();
126          }
127        }
128        catch (CreateCommandException e) {
129          e.printStackTrace();
130        }
131    
132      }
133    
134      public void close() {
135        try {
136          Script login = getLifeCycle("logout");
137          if (login != null) {
138            login.run();
139          }
140        }
141        catch (CreateCommandException e) {
142          e.printStackTrace();
143        }
144      }
145    
146      // Shell implementation **********************************************************************************************
147    
148      public String getWelcome() {
149        GroovyShell shell = getGroovyShell();
150        Object ret = shell.evaluate("welcome();");
151        return String.valueOf(ret);
152      }
153    
154      public String getPrompt() {
155        GroovyShell shell = getGroovyShell();
156        Object ret = shell.evaluate("prompt();");
157        return String.valueOf(ret);
158      }
159    
160      public ShellProcess createProcess(String request) {
161        log.debug("Invoking request " + request);
162        final ShellResponse response;
163        if ("bye".equals(request) || "exit".equals(request)) {
164          response = new ShellResponse.Close();
165        } else {
166    
167          // Create AST
168          Parser parser = new Parser(request);
169          AST ast = parser.parse();
170    
171          //
172          if (ast instanceof AST.Expr) {
173            AST.Expr expr = (AST.Expr)ast;
174    
175            // Create commands first
176            try {
177              return expr.create(this, request);
178            } catch (CreateCommandException e) {
179              response = e.getResponse();
180            }
181          } else {
182            response = ShellResponse.noCommand();
183          }
184        }
185    
186        //
187        return new CRaSHProcess(this, request) {
188          @Override
189          ShellResponse doInvoke(ShellProcessContext context) throws InterruptedException {
190            return response;
191          }
192        };
193      }
194    
195      /**
196       * For now basic implementation
197       */
198      public CommandCompletion complete(final String prefix) {
199        log.debug("Want prefix of " + prefix);
200        AST ast = new Parser(prefix).parse();
201        String termPrefix;
202        if (ast != null) {
203          AST.Term last = ast.lastTerm();
204          termPrefix = Utils.trimLeft(last.getLine());
205        } else {
206          termPrefix = "";
207        }
208    
209        //
210        log.debug("Retained term prefix is " + prefix);
211        CommandCompletion completion;
212        int pos = termPrefix.indexOf(' ');
213        if (pos == -1) {
214          ValueCompletion completions = ValueCompletion.create();
215          for (String resourceId : crash.context.listResourceId(ResourceKind.COMMAND)) {
216            if (resourceId.startsWith(termPrefix)) {
217              completions.put(resourceId.substring(termPrefix.length()), true);
218            }
219          }
220          completion = new CommandCompletion(Delimiter.EMPTY, completions);
221        } else {
222          String commandName = termPrefix.substring(0, pos);
223          termPrefix = termPrefix.substring(pos);
224          try {
225            ShellCommand command = getCommand(commandName);
226            if (command != null) {
227              completion = command.complete(new BaseCommandContext(attributes), termPrefix);
228            } else {
229              completion = new CommandCompletion(Delimiter.EMPTY, ValueCompletion.create());
230            }
231          }
232          catch (CreateCommandException e) {
233            log.debug("Could not create command for completion of " + prefix, e);
234            completion = new CommandCompletion(Delimiter.EMPTY, ValueCompletion.create());
235          }
236        }
237    
238        //
239        log.debug("Found completions for " + prefix + ": " + completion);
240        return completion;
241      }
242    }