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}