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