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.lang.groovy;
020
021 import groovy.lang.Binding;
022 import groovy.lang.Closure;
023 import groovy.lang.GroovyShell;
024 import org.codehaus.groovy.ast.AnnotationNode;
025 import org.codehaus.groovy.ast.ClassNode;
026 import org.codehaus.groovy.ast.CompileUnit;
027 import org.codehaus.groovy.ast.MethodNode;
028 import org.codehaus.groovy.control.CompilationFailedException;
029 import org.codehaus.groovy.control.CompilationUnit;
030 import org.codehaus.groovy.control.CompilerConfiguration;
031 import org.codehaus.groovy.control.Phases;
032 import org.codehaus.groovy.runtime.InvokerHelper;
033 import org.crsh.cli.Usage;
034 import org.crsh.command.BaseCommand;
035 import org.crsh.lang.java.ShellCommandImpl;
036 import org.crsh.shell.impl.command.spi.CommandCreationException;
037 import org.crsh.shell.impl.command.spi.ShellCommand;
038 import org.crsh.lang.groovy.command.GroovyScriptShellCommand;
039 import org.crsh.shell.impl.command.CRaSHSession;
040 import org.crsh.shell.impl.command.spi.CommandResolution;
041 import org.crsh.util.ClassCache;
042 import org.crsh.shell.impl.command.spi.CommandManager;
043 import org.crsh.lang.groovy.command.GroovyScript;
044 import org.crsh.lang.groovy.command.GroovyScriptCommand;
045 import org.crsh.plugin.PluginContext;
046 import org.crsh.plugin.ResourceKind;
047 import org.crsh.shell.ErrorType;
048 import org.crsh.util.TimestampedObject;
049
050 import java.io.UnsupportedEncodingException;
051 import java.util.HashMap;
052 import java.util.Set;
053 import java.util.logging.Level;
054 import java.util.logging.Logger;
055
056 /** @author Julien Viet */
057 public 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 }