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.ast.AnnotationNode; 025import org.codehaus.groovy.ast.ClassNode; 026import org.codehaus.groovy.ast.CompileUnit; 027import org.codehaus.groovy.ast.MethodNode; 028import org.codehaus.groovy.control.CompilationFailedException; 029import org.codehaus.groovy.control.CompilationUnit; 030import org.codehaus.groovy.control.CompilerConfiguration; 031import org.codehaus.groovy.control.Phases; 032import org.codehaus.groovy.runtime.InvokerHelper; 033import org.crsh.cli.Usage; 034import org.crsh.command.BaseCommand; 035import org.crsh.lang.java.ShellCommandImpl; 036import org.crsh.shell.impl.command.spi.CommandCreationException; 037import org.crsh.shell.impl.command.spi.ShellCommand; 038import org.crsh.lang.groovy.command.GroovyScriptShellCommand; 039import org.crsh.shell.impl.command.CRaSHSession; 040import org.crsh.shell.impl.command.spi.CommandResolution; 041import org.crsh.util.ClassCache; 042import org.crsh.shell.impl.command.spi.CommandManager; 043import org.crsh.lang.groovy.command.GroovyScript; 044import org.crsh.lang.groovy.command.GroovyScriptCommand; 045import org.crsh.plugin.PluginContext; 046import org.crsh.plugin.ResourceKind; 047import org.crsh.shell.ErrorType; 048import org.crsh.util.TimestampedObject; 049 050import java.io.UnsupportedEncodingException; 051import java.util.HashMap; 052import java.util.Set; 053import java.util.logging.Level; 054import java.util.logging.Logger; 055 056/** @author Julien Viet */ 057public class GroovyCommandManagerImpl implements CommandManager { 058 059 /** . */ 060 static final Logger log = Logger.getLogger(GroovyCommandManagerImpl.class.getName()); 061 062 /** . */ 063 private ClassCache<GroovyScript> scriptCache; 064 065 /** . */ 066 private GroovyClassFactory<Object> objectGroovyClassFactory; 067 068 public GroovyCommandManagerImpl(PluginContext context) { 069 this.objectGroovyClassFactory = new GroovyClassFactory<Object>(context.getLoader(), Object.class, GroovyScriptCommand.class); 070 this.scriptCache = new ClassCache<GroovyScript>(context, new GroovyClassFactory<GroovyScript>(context.getLoader(), GroovyScript.class, GroovyScript.class), ResourceKind.LIFECYCLE); 071 } 072 073 public Set<String> getExtensions() { 074 return GroovyCommandManager.EXT; 075 } 076 077 public boolean isActive() { 078 return true; 079 } 080 081 public String doCallBack(HashMap<String, Object> session, String name, String defaultValue) { 082 return eval(session, name, defaultValue); 083 } 084 085 public void init(HashMap<String, Object> session) { 086 try { 087 GroovyScript login = getLifeCycle(session, "login"); 088 if (login != null) { 089 login.setBinding(new Binding(session)); 090 login.run(); 091 } 092 } 093 catch (CommandCreationException e) { 094 e.printStackTrace(); 095 } 096 } 097 098 public void destroy(HashMap<String, Object> session) { 099 try { 100 GroovyScript logout = getLifeCycle(session, "logout"); 101 if (logout != null) { 102 logout.setBinding(new Binding(session)); 103 logout.run(); 104 } 105 } 106 catch (CommandCreationException e) { 107 e.printStackTrace(); 108 } 109 } 110 111 /** 112 * The underlying groovu shell used for the REPL. 113 * 114 * @return a groovy shell operating on the session attributes 115 */ 116 public static GroovyShell getGroovyShell(CRaSHSession session) { 117 GroovyShell shell = (GroovyShell)session.get("shell"); 118 if (shell == null) { 119 CompilerConfiguration config = new CompilerConfiguration(); 120 config.setRecompileGroovySource(true); 121 ShellBinding binding = new ShellBinding(session, session); 122 shell = new GroovyShell(session.crash.getContext().getLoader(), binding, config); 123 session.put("shell", shell); 124 } 125 return shell; 126 } 127 128 private String eval(HashMap<String, Object> session, String name, String def) { 129 try { 130 GroovyShell shell = getGroovyShell((CRaSHSession)session); 131 Object ret = shell.getContext().getVariable(name); 132 if (ret instanceof Closure) { 133 log.log(Level.FINEST, "Invoking " + name + " closure"); 134 Closure c = (Closure)ret; 135 ret = c.call(); 136 } else if (ret == null) { 137 log.log(Level.FINEST, "No " + name + " will use empty"); 138 return def; 139 } 140 return String.valueOf(ret); 141 } 142 catch (Exception e) { 143 log.log(Level.SEVERE, "Could not get a " + name + " message, will use empty", e); 144 return def; 145 } 146 } 147 148 public GroovyScript getLifeCycle(HashMap<String, Object> session, String name) throws CommandCreationException, NullPointerException { 149 TimestampedObject<Class<? extends GroovyScript>> ref = scriptCache.getClass(name); 150 if (ref != null) { 151 Class<? extends GroovyScript> scriptClass = ref.getObject(); 152 GroovyScript script = (GroovyScript)InvokerHelper.createScript(scriptClass, new Binding(session)); 153 script.setBinding(new Binding(session)); 154 return script; 155 } else { 156 return null; 157 } 158 } 159 160 public CommandResolution resolveCommand(final String name, byte[] source) throws CommandCreationException, NullPointerException { 161 162 // 163 if (source == null) { 164 throw new NullPointerException("No null command source allowed"); 165 } 166 167 // 168 final String script; 169 try { 170 script = new String(source, "UTF-8"); 171 } 172 catch (UnsupportedEncodingException e) { 173 throw new CommandCreationException(name, ErrorType.INTERNAL, "Could not compile command script " + name, e); 174 } 175 176 // Get the description using a partial compilation because it is much faster than compiling the class 177 // the class will be compiled lazyly 178 String resolveDescription = null; 179 CompilationUnit cu = new CompilationUnit(objectGroovyClassFactory.config); 180 cu.addSource(name, script); 181 try { 182 cu.compile(Phases.CONVERSION); 183 } 184 catch (CompilationFailedException e) { 185 throw new CommandCreationException(name, ErrorType.INTERNAL, "Could not compile command", e); 186 } 187 CompileUnit ast = cu.getAST(); 188 if (ast.getClasses().size() > 0) { 189 ClassNode classNode= (ClassNode)ast.getClasses().get(0); 190 if (classNode != null) { 191 for (AnnotationNode annotation : classNode.getAnnotations()) { 192 if (annotation.getClassNode().getName().equals(Usage.class.getSimpleName())) { 193 resolveDescription = annotation.getMember("value").getText(); 194 break; 195 } 196 } 197 if (resolveDescription == null) { 198 for (MethodNode main : classNode.getMethods("main")) { 199 for (AnnotationNode annotation : main.getAnnotations()) { 200 if (annotation.getClassNode().getName().equals(Usage.class.getSimpleName())) { 201 resolveDescription = annotation.getMember("value").getText(); 202 break; 203 } 204 } 205 } 206 } 207 } 208 } 209 final String description = resolveDescription; 210 211 // 212 return new CommandResolution() { 213 ShellCommand<?> command; 214 @Override 215 public String getDescription() { 216 return description; 217 } 218 @Override 219 public ShellCommand<?> getCommand() throws CommandCreationException { 220 if (command == null) { 221 Class<?> clazz = objectGroovyClassFactory.parse(name, script); 222 if (BaseCommand.class.isAssignableFrom(clazz)) { 223 Class<? extends BaseCommand> cmd = clazz.asSubclass(BaseCommand.class); 224 command = make(cmd); 225 } 226 else if (GroovyScriptCommand.class.isAssignableFrom(clazz)) { 227 Class<? extends GroovyScriptCommand> cmd = clazz.asSubclass(GroovyScriptCommand.class); 228 command = make2(cmd); 229 } 230 else { 231 throw new CommandCreationException(name, ErrorType.INTERNAL, "Could not create command " + name + " instance"); 232 } 233 } 234 return command; 235 } 236 }; 237 } 238 239 private <C extends BaseCommand> ShellCommandImpl<C> make(Class<C> clazz) { 240 return new ShellCommandImpl<C>(clazz); 241 } 242 private <C extends GroovyScriptCommand> GroovyScriptShellCommand<C> make2(Class<C> clazz) { 243 return new GroovyScriptShellCommand<C>(clazz); 244 } 245}