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 */
019package org.crsh.lang.groovy;
020
021import groovy.lang.Binding;
022import groovy.lang.Closure;
023import groovy.lang.GroovyShell;
024import org.codehaus.groovy.control.CompilerConfiguration;
025import org.codehaus.groovy.runtime.InvokerHelper;
026import org.crsh.command.BaseCommand;
027import org.crsh.command.CommandCreationException;
028import org.crsh.command.ShellCommand;
029import org.crsh.command.BaseShellCommand;
030import org.crsh.plugin.CRaSHPlugin;
031import org.crsh.util.AbstractClassCache;
032import org.crsh.util.ClassCache;
033import org.crsh.shell.impl.command.CommandManager;
034import org.crsh.lang.groovy.command.GroovyScript;
035import org.crsh.lang.groovy.command.GroovyScriptCommand;
036import org.crsh.lang.groovy.command.GroovyScriptShellCommand;
037import org.crsh.plugin.PluginContext;
038import org.crsh.plugin.ResourceKind;
039import org.crsh.shell.ErrorType;
040import org.crsh.util.TimestampedObject;
041
042import java.io.UnsupportedEncodingException;
043import java.util.Collections;
044import java.util.HashMap;
045import java.util.Map;
046import java.util.Set;
047import java.util.logging.Level;
048import java.util.logging.Logger;
049
050/** @author Julien Viet */
051public class GroovyCommandManager extends CRaSHPlugin<CommandManager> implements CommandManager {
052
053  /** . */
054  static final Logger log = Logger.getLogger(GroovyCommandManager.class.getName());
055
056  /** . */
057  static final Set<String> EXT = Collections.singleton("groovy");
058
059  /** . */
060  private AbstractClassCache<GroovyScript> scriptCache;
061
062  /** . */
063  private GroovyClassFactory<Object> objectGroovyClassFactory;
064
065  public GroovyCommandManager() {
066  }
067
068  @Override
069  public CommandManager getImplementation() {
070    return this;
071  }
072
073  public Set<String> getExtensions() {
074    return EXT;
075  }
076
077  @Override
078  public void init() {
079    PluginContext context = getContext();
080
081    //
082    this.objectGroovyClassFactory = new GroovyClassFactory<Object>(context.getLoader(), Object.class, GroovyScriptCommand.class);
083    this.scriptCache = new ClassCache<GroovyScript>(context, new GroovyClassFactory<GroovyScript>(context.getLoader(), GroovyScript.class, GroovyScript.class), ResourceKind.LIFECYCLE);
084  }
085
086  public String doCallBack(HashMap<String, Object> session, String name, String defaultValue) {
087    return eval(session, name, defaultValue);
088  }
089
090  public void init(HashMap<String, Object> session) {
091    try {
092      GroovyScript login = getLifeCycle(session, "login");
093      if (login != null) {
094        login.setBinding(new Binding(session));
095        login.run();
096      }
097    }
098    catch (CommandCreationException e) {
099      e.printStackTrace();
100    }
101  }
102
103  public void destroy(HashMap<String, Object> session) {
104    try {
105      GroovyScript logout = getLifeCycle(session, "logout");
106      if (logout != null) {
107        logout.setBinding(new Binding(session));
108        logout.run();
109      }
110    }
111    catch (CommandCreationException e) {
112      e.printStackTrace();
113    }
114  }
115
116  public GroovyShell getGroovyShell(Map<String, Object> session) {
117    return getGroovyShell(getContext(), session);
118  }
119
120  /**
121   * The underlying groovu shell used for the REPL.
122   *
123   * @return a groovy shell operating on the session attributes
124   */
125  public static GroovyShell getGroovyShell(PluginContext context, Map<String, Object> session) {
126    GroovyShell shell = (GroovyShell)session.get("shell");
127    if (shell == null) {
128      CompilerConfiguration config = new CompilerConfiguration();
129      config.setRecompileGroovySource(true);
130      ShellBinding binding = new ShellBinding(session);
131      shell = new GroovyShell(context.getLoader(), binding, config);
132      session.put("shell", shell);
133    }
134    return shell;
135  }
136
137  private String eval(HashMap<String, Object> session, String name, String def) {
138    try {
139      GroovyShell shell = getGroovyShell(session);
140      Object ret = shell.evaluate("return " + name + ";");
141      if (ret instanceof Closure) {
142        log.log(Level.FINEST, "Invoking " + name + " closure");
143        Closure c = (Closure)ret;
144        ret = c.call();
145      } else if (ret == null) {
146        log.log(Level.FINEST, "No " + name + " will use empty");
147        return def;
148      }
149      return String.valueOf(ret);
150    }
151    catch (Exception e) {
152      log.log(Level.SEVERE, "Could not get a " + name + " message, will use empty", e);
153      return def;
154    }
155  }
156
157  public GroovyScript getLifeCycle(HashMap<String, Object> session, String name) throws CommandCreationException, NullPointerException {
158    TimestampedObject<Class<? extends GroovyScript>> ref = scriptCache.getClass(name);
159    if (ref != null) {
160      Class<? extends GroovyScript> scriptClass = ref.getObject();
161      GroovyScript script = (GroovyScript)InvokerHelper.createScript(scriptClass, new Binding(session));
162      script.setBinding(new Binding(session));
163      return script;
164    } else {
165      return null;
166    }
167  }
168
169  public ShellCommand resolveCommand(String name, byte[] source) throws CommandCreationException, NullPointerException {
170
171    //
172    if (source == null) {
173      throw new NullPointerException("No null command source allowed");
174    }
175
176    //
177    String script;
178    try {
179      script = new String(source, "UTF-8");
180    }
181    catch (UnsupportedEncodingException e) {
182      throw new CommandCreationException(name, ErrorType.INTERNAL, "Could not compile command script " + name, e);
183    }
184
185    //
186    Class<?> clazz = objectGroovyClassFactory.parse(name, script);
187    if (BaseCommand.class.isAssignableFrom(clazz)) {
188      Class<? extends BaseCommand> cmd = clazz.asSubclass(BaseCommand.class);
189      return make(cmd);
190    } else if (GroovyScriptCommand.class.isAssignableFrom(clazz)) {
191      Class<? extends GroovyScriptCommand> cmd = clazz.asSubclass(GroovyScriptCommand.class);
192      return make2(cmd);
193    } else {
194      throw new CommandCreationException(name, ErrorType.INTERNAL, "Could not create command " + name + " instance");
195    }
196  }
197
198  private <C extends BaseCommand> BaseShellCommand<C> make(Class<C> clazz) {
199    return new BaseShellCommand<C>(clazz);
200  }
201
202  private <C extends GroovyScriptCommand> GroovyScriptShellCommand<C> make2(Class<C> clazz) {
203    return new GroovyScriptShellCommand<C>(clazz);
204  }
205}