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