/** * Helios, OpenSource Monitoring * Brought to you by the Helios Development Group * * Copyright 2012, Helios Development Group and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * */ package org.helios.apmrouter.groovy; import groovy.lang.Binding; import groovy.lang.Closure; import groovy.lang.GroovyClassLoader; import groovy.lang.GroovyShell; import groovy.lang.GroovySystem; import groovy.lang.Script; import java.io.Closeable; import java.io.File; import java.io.OutputStream; import java.io.PrintStream; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.URL; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicLong; import javax.management.MBeanServer; import javax.management.MBeanServerConnection; import javax.management.ObjectName; import javax.management.QueryExp; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; import org.codehaus.groovy.control.CompilerConfiguration; import org.codehaus.groovy.control.customizers.ImportCustomizer; import org.helios.apmrouter.groovy.annotations.ScriptName; import org.helios.apmrouter.groovy.annotations.Start; import org.helios.apmrouter.jmx.JMXHelper; import org.helios.apmrouter.jmx.ScheduledThreadPoolFactory; import org.helios.apmrouter.jmx.TaskScheduler; import org.helios.apmrouter.jmx.ThreadPoolFactory; import org.helios.apmrouter.server.ServerComponentBean; import org.helios.apmrouter.util.URLHelper; import org.springframework.context.event.ContextStartedEvent; import org.springframework.jmx.export.annotation.ManagedAttribute; import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.jmx.export.annotation.ManagedOperationParameter; import org.springframework.jmx.export.annotation.ManagedOperationParameters; /** * <p>Title: GroovyService</p> * <p>Description: Interactive groovy service</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.groovy.GroovyService</code></p> */ public class GroovyService extends ServerComponentBean implements GroovyLoadedScriptListener { /** A map of compiled scripts keyed by an arbitrary reference name */ protected final Map<String, Script> compiledScripts = new ConcurrentHashMap<String, Script>(); /** Thread pool for asynch tasks */ protected final Executor threadPool = ThreadPoolFactory.newCachedThreadPool(getClass().getPackage().getName(), getClass().getSimpleName()); /** Scheduler for scheduled tasks */ protected final TaskScheduler scheduler = ScheduledThreadPoolFactory.newScheduler(getClass().getSimpleName()); /** The compiler configuration for script compilations */ protected final CompilerConfiguration compilerConfiguration = new CompilerConfiguration(); /** A groovy classloader for compiling scripts */ protected final GroovyClassLoader groovyClassLoader; /** The shared bindings */ protected final Map<String, Object> beans = new ConcurrentHashMap<String, Object>(); /** The global variables */ protected final Map<String, Object> globalVariables = new ConcurrentHashMap<String, Object>(); /** A set of registered class listeners */ protected final Set<GroovyLoadedScriptListener> listeners = new CopyOnWriteArraySet<GroovyLoadedScriptListener>(); /** The compiler configuration's JMX ObjectName */ protected final ObjectName compilerConfigurationObjectName; /** The source of the console to execute */ protected String consoleSource = null; /** A set of implicit imports for the compiler configuration */ protected final Set<String> imports = new CopyOnWriteArraySet<String>(Arrays.asList("import static " + getClass().getName() + ".*")); /** The initial and default imports customizer for the compiler configuration */ protected final ImportCustomizer importCustomizer = new ImportCustomizer(); /** * Creates a new GroovyService */ public GroovyService() { objectName = JMXHelper.objectName(getClass().getPackage().getName(), "service", getClass().getSimpleName()); compilerConfigurationObjectName = JMXHelper.objectName(getClass().getPackage().getName(), "service", getClass().getSimpleName(), "type", "CompilerConfiguration"); imports.add("import org.helios.apmrouter.groovy.annotations.*"); compilerConfiguration.setOptimizationOptions(Collections.singletonMap("indy", true)); groovyClassLoader = new GroovyClassLoader(getClass().getClassLoader(), compilerConfiguration); registerLoadListener(this); } /** * {@inheritDoc} * @see org.helios.apmrouter.server.ServerComponentBean#doStart() */ @Override protected void doStart() { applyImports(importCustomizer, imports.toArray(new String[imports.size()])); try { consoleSource = new String(URLHelper.getBytesFromURL(ClassLoader.getSystemResource("groovy/ui/Console.groovy"))); // this is slow, so chucking it into a one time thread. threadPool.execute(new Runnable(){ @Override public void run() { try { compile("console", consoleSource); info("Background compilation of gconsole completed"); } catch (Exception ex) { warn("Background compilation of gconsole failed", ex); } } }); } catch (Exception ex) { ex.printStackTrace(System.err); } } /** * Applies the configured imports to the compiler configuration * @param impCustomizer The import customizer to add the imports to * @param imps The imports to add */ protected void applyImports(ImportCustomizer impCustomizer, String...imps) { for(String imp: imps) { String _imp = imp.trim().replaceAll("\\s+", " "); if(!_imp.startsWith("import")) { warn("Unrecognized import [", imp, "]"); continue; } if(_imp.startsWith("import static ")) { if(_imp.endsWith(".*")) { impCustomizer.addStaticStars(_imp.replace("import static ", "").replace(".*", "")); } else { String cleaned = _imp.replace("import static ", "").replace(".*", ""); int index = cleaned.lastIndexOf('.'); if(index==-1) { warn("Failed to parse non-star static import [", imp, "]"); continue; } impCustomizer.addStaticImport(cleaned.substring(0, index), cleaned.substring(index+1)); } } else { if(_imp.endsWith(".*")) { impCustomizer.addStarImports(_imp.replace("import ", "").replace(".*", "")); } else { impCustomizer.addImports(_imp.replace("import ", "")); } } } compilerConfiguration.addCompilationCustomizers(impCustomizer); } /** * Registers the passed load listener * @param listener the load listener */ public void registerLoadListener(GroovyLoadedScriptListener listener) { if(listener!=null) { listeners.add(listener); } } /** * Unregisters the passed load listener * @param listener the load listener */ public void unregisterLoadListener(GroovyLoadedScriptListener listener) { if(listener!=null) { listeners.remove(listener); } } /** * Flushes the compiled script cache */ @ManagedOperation(description="Flushes the compiled script cache") public void flushScriptCache() { compiledScripts.clear(); } /** * Removes the named script from the script cache * @param name The name of the script to remove */ @ManagedOperation(description="Removes the named script from the script cache") @ManagedOperationParameter(name="ScriptName", description="The name of the script to remove") public void flushScript(String name) { if(name==null || name.trim().isEmpty()) throw new IllegalArgumentException("The passed name was null or empty", new Throwable()); compiledScripts.remove(name); } /** * Returns the groovy version * @return the groovy version */ @ManagedAttribute(description="The groovy version") public String getGroovyVersion() { return GroovySystem.getVersion(); } /** * Returns the names of the cached compiled scripts * @return the names of the cached compiled scripts */ @ManagedAttribute(description="The names of the cached compiled scripts") public String[] getScriptNames() { return compiledScripts.keySet().toArray(new String[compiledScripts.size()]); } /** * Indicates if groovy is using reflection * @return true if groovy is using reflection, false if using .... ? */ @ManagedAttribute(description="Indicates if groovy is using reflection") public boolean isUseReflection() { return GroovySystem.isUseReflection(); } /* * compile(String name, String source) * compile(String name, URL source) // needs check for source update * compile(String name, File source) // needs check for source update * all options: * name * source * properties (compiler options) * url[] (additional classpaths) * classloader * * * invoke(String name, OutputStream os, Object...args) // run, with args in bindings * invoke(String name, Object...args) // run, with args in bindings, ditch output * invokeMethod(String name, String methodName, Object...args) * invokeMethod(String name, OutputStream os, String methodName, Object...args) * * compileAndInvoke(...) * */ /** * Invokes the named method in the named script and returns the value returned from the invocation * @param name The name of the script to run * @param methodName The name of the method to run * @param os The output stream the script will write to when it calls <p><code>out</code></p>. * @param es The output stream the script will write to when it calls <p><code>err</code></p>. * @param args The arguments passed to the script as <p><code>args</code></p>. * @return the value returned from the script */ public Object invoke(String name, String methodName, OutputStream os, OutputStream es, Object...args) { if(name==null || name.trim().isEmpty() ) throw new IllegalArgumentException("The passed script name was null or empty", new Throwable()); if(methodName==null || methodName.trim().isEmpty() ) throw new IllegalArgumentException("The passed method name was null or empty", new Throwable()); Script script = compiledScripts.get(name); if(script==null) throw new IllegalArgumentException("No script found for passed script name [" + name + "]", new Throwable()); if(os!=null) { script.setProperty("out", new PrintStream(os, true)); } if(es!=null) { script.setProperty("err", new PrintStream(es, true)); } return script.invokeMethod(methodName, args); } /** * Invokes the named method in the named script and returns the value returned from the invocation * @param name The name of the script to run * @param methodName The name of the method to run * @param args The arguments passed to the script as <p><code>args</code></p>. * @return the value returned from the script */ public Object invoke(String name, String methodName, Object...args) { if(name==null || name.trim().isEmpty() ) throw new IllegalArgumentException("The passed script name was null or empty", new Throwable()); if(methodName==null || methodName.trim().isEmpty() ) throw new IllegalArgumentException("The passed method name was null or empty", new Throwable()); Script script = compiledScripts.get(name); if(script==null) throw new IllegalArgumentException("No script found for passed script name [" + name + "]", new Throwable()); return invoke(name, methodName, args); } /** * Invokes the named method in the named script and returns the value returned from the invocation * @param name The name of the script to run * @param methodName The name of the method to run * @return the value returned from the script */ public Object invoke(String name, String methodName) { return invoke(name, methodName, EMPTY_OBJ_ARR); } /** * Runs the named script and returns the value returned from the invocation * @param name The name of the script to run * @param os The output stream the script will write to when it calls <p><code>out</code></p>. * @param es The output stream the script will write to when it calls <p><code>err</code></p>. * @param args The arguments passed to the script as <p><code>args</code></p>. * @return the value returned from the script */ public Object run(String name, OutputStream os, OutputStream es, Object...args) { if(name==null || name.trim().isEmpty() ) throw new IllegalArgumentException("The passed script name was null or empty", new Throwable()); Script script = compiledScripts.get(name); if(script==null) throw new IllegalArgumentException("No script found for passed script name [" + name + "]", new Throwable()); if(args==null || args.length==0) { script.setProperty("args", EMPTY_OBJ_ARR); } else { script.setProperty("args", args); } if(os!=null) { script.setProperty("out", new PrintStream(os, true)); } if(es!=null) { script.setProperty("err", new PrintStream(es, true)); } return script.run(); } /** A synthetic script name serial generator */ protected static final AtomicLong nameSerial = new AtomicLong(); /** * Compiles the passed source and assigns it the passed name * @param scriptName The name assigned to the compiled script. If this is null, and no {@link ScriptName} annotation is found, a synthetic name will be assigned. * @param source The source code of the script to compiled * @return The name of the compiled script */ @ManagedOperation(description="Compiles the passed source and assigns it the passed name") @ManagedOperationParameters({ @ManagedOperationParameter(name="ScriptName", description="The name assigned to the compiled script"), @ManagedOperationParameter(name="Source", description="The source code of the script to be compiled") }) public String compile(String scriptName, String source) { if(scriptName!=null && scriptName.trim().isEmpty()) scriptName=null; //else scriptName = scriptName.trim(); if(source==null || source.length()==0) throw new IllegalArgumentException("The passed source was null or empty", new Throwable()); //source = source.replace("\\n", "\n"); Script script = null; String name = scriptName!=null ? scriptName.trim() : "groovy#" + nameSerial.incrementAndGet(); // GroovyClassLoader gcl = new GroovyClassLoader(Thread.currentThread().getContextClassLoader(), compilerConfiguration); try { script = new GroovyShell(compilerConfiguration).parse(source, name); // Class<?> clazz = gcl.parseClass(source); ScriptName sn = script.getClass().getAnnotation(ScriptName.class); if(sn!=null && !sn.value().trim().isEmpty()) { name = sn.value().trim(); } info("Compiled script named [" , name , "]. Class is: [", script.getClass().getName(), "]"); } catch (Exception ex) { ex.printStackTrace(System.err); throw new RuntimeException(ex); } finally { // try { gcl.close(); } catch (Exception ex) {} } Binding bindings = getBindings(); script.setBinding(bindings); script.setProperty("bindings", bindings); compiledScripts.put(name, script); Class<?> clazz = script.getClass();//groovyClassLoader.parseClass(source); scanLoadedClass(clazz, script); return name; } /** * Compiles the passed source and assigns it the passed name * @param source The source code of the script to compiled * @return The name of the compiled script */ @ManagedOperation(description="Compiles the passed source and assigns it the passed name") @ManagedOperationParameters({ @ManagedOperationParameter(name="Source", description="The source code of the script to be compiled") }) public String compile(String source) { return compile(null, source); } /** * Sets a compiler optimization option * @param name The name of the option * @param value true to enable, false to disable */ @ManagedOperation(description="Sets a compiler optimization option") @ManagedOperationParameters({ @ManagedOperationParameter(name="OptionName", description="The name of the option to set"), @ManagedOperationParameter(name="Enabled", description="true to enable, false to disable") }) public void setOptimizationOption(String name, boolean value) { compilerConfiguration.setOptimizationOptions(Collections.singletonMap(name, value)); } /** * Compiles the source read from the passed URL and assigns it the passed name * @param sourceUrl The source code URL of the script to compiled * @return The name of the compiled script */ @ManagedOperation(description="Compiles the source read from the passed URL and assigns it the passed name") @ManagedOperationParameters({ @ManagedOperationParameter(name="SourceURL", description="The source code of the script to be compiled") }) public String compileFromUrl(String sourceUrl) { URL url = URLHelper.toURL(sourceUrl); String source = URLHelper.getTextFromURL(url); return compile(source); } /** * Scans the passed class and looks for matches with registered groovy loaded class listeners, firing their callbacks when matches are found * @param clazz The class to scan * @param instance The instance of the scanned class */ protected void scanLoadedClass(Class<?> clazz, Object instance) { if(listeners.isEmpty()) return; Map<Class<? extends Annotation>, Annotation> typeAnns = new HashMap<Class<? extends Annotation>, Annotation>(); Map<Class<? extends Annotation>, Map<Method, Set<Annotation>>> methodAnns = new HashMap<Class<? extends Annotation>, Map<Method, Set<Annotation>>>(); for(Annotation annot: clazz.getAnnotations()) { typeAnns.put(annot.annotationType(), annot); } for(Method m: clazz.getMethods()) { for(Annotation annot: m.getAnnotations()) { addMethodAnnotation(methodAnns, m, annot); } } for(Method m: clazz.getDeclaredMethods()) { for(Annotation annot: m.getAnnotations()) { addMethodAnnotation(methodAnns, m, annot); } } for(GroovyLoadedScriptListener listener: listeners) { Set<Annotation> matchedAnnotations = new HashSet<Annotation>(); for(Class<? extends Annotation> cl: listener.getScanTypeAnnotations()) { Annotation matchedAnnotation = typeAnns.get(cl); if(matchedAnnotation!=null) { matchedAnnotations.add(matchedAnnotation); } } if(!matchedAnnotations.isEmpty()) { listener.onScanType(matchedAnnotations, clazz, instance); } Map<Method, Set<Annotation>> matchedMethodAnnotations = new HashMap<Method, Set<Annotation>>(); Set<Class<? extends Annotation>> listenerMethodAnnotationTypes = listener.getScanMethodAnnotations(); for(Method method: clazz.getMethods()) { for(Annotation methodAnnotation: method.getAnnotations()) { if(listenerMethodAnnotationTypes.contains(methodAnnotation.annotationType())) { Set<Annotation> annotationSet = matchedMethodAnnotations.get(method); if(annotationSet==null) { annotationSet = new HashSet<Annotation>(); matchedMethodAnnotations.put(method, annotationSet); } annotationSet.add(methodAnnotation); } } } for(Method method: clazz.getDeclaredMethods()) { for(Annotation methodAnnotation: method.getAnnotations()) { if(listenerMethodAnnotationTypes.contains(methodAnnotation.annotationType())) { Set<Annotation> annotationSet = matchedMethodAnnotations.get(method); if(annotationSet==null) { annotationSet = new HashSet<Annotation>(); matchedMethodAnnotations.put(method, annotationSet); } annotationSet.add(methodAnnotation); } } } if(!matchedMethodAnnotations.isEmpty()) { listener.onScanMethod(matchedMethodAnnotations, clazz, instance); } Set<Class<?>> matchedParentClasses = new HashSet<Class<?>>(); for(Class<?> parentClass: listener.getScanClasses()) { if(parentClass.isAssignableFrom(clazz)) { matchedParentClasses.add(parentClass); } } if(!matchedParentClasses.isEmpty()) { listener.onScanClasses(matchedParentClasses, clazz, instance); } } } /** * Adds the passed method and it's associated annotation to the passed annotation tree * @param annotationTree The method annotation tree for the class being scanned * @param method The scanned method * @param annotation The annotation associated with the passed method */ protected void addMethodAnnotation(final Map<Class<? extends Annotation>, Map<Method, Set<Annotation>>> annotationTree, final Method method, final Annotation annotation) { Class<? extends Annotation> annClass = annotation.annotationType(); Map<Method, Set<Annotation>> methodSets = annotationTree.get(annClass); if(methodSets==null) { methodSets = new HashMap<Method, Set<Annotation>>(); methodSets.put(method, new HashSet<Annotation>()); annotationTree.put(annClass, methodSets); } methodSets.get(method).add(annotation); } /** * Compiles the passed source and assignes it the passed name * @param name The name assigned to the compiled script * @param source The source code of the script to compiled */ @ManagedOperation(description="Compiles the passed source and assignes it the passed name") @ManagedOperationParameters({ @ManagedOperationParameter(name="ScriptName", description="The name assigned to the compiled script"), @ManagedOperationParameter(name="Source", description="The source code of the script to be compiled") }) public void compileBuffer(String name, CharSequence source) { if(source==null || source.length()==0) throw new IllegalArgumentException("The passed source was null or empty", new Throwable()); compile(name, source.toString()); } /** Empty object array constant */ protected static final Object[] EMPTY_OBJ_ARR = {}; /** * Executes the main function of the named script * @param name The name of the script to execute * @param args The optional arguments to pass to the script * @return the return value from the script invocation */ @ManagedOperation(description="Executes the main function of the named script") @ManagedOperationParameters({ @ManagedOperationParameter(name="ScriptName", description="The name of the compiled script to run"), @ManagedOperationParameter(name="Args", description="The optional arguments to pass to the script") }) public Object run(String name, Object...args) { return run(name, null, null, args); } /** * Executes the main function of the named script * @param name The name of the script to execute * @return the return value from the script invocation */ @ManagedOperation(description="Executes the main function of the named script") @ManagedOperationParameters({ @ManagedOperationParameter(name="ScriptName", description="The name of the compiled script to run") }) public Object runScript(String name) { return run(name, EMPTY_OBJ_ARR); } /** * Returns a bindings instance * @return a bindings instance */ protected Binding getBindings() { if(beans.isEmpty()) { synchronized(beans) { if(beans.isEmpty()) { for(String beanName: applicationContext.getBeanDefinitionNames()) { beans.put(beanName, applicationContext.getBean(beanName)); } beans.put("RootCtx", applicationContext); beans.put("globalVariables", globalVariables); } } } //return new ThreadSafeNoNullsBinding(beans); return new Binding(new HashMap<String, Object>(beans)); } /** * Launches the groovy console, loading an empty console */ @ManagedOperation(description="Launches the empty groovy console") public void launchConsole() { launchConsole(null); } /** * Launches the groovy console * @param fileName The name of the local file to load */ @ManagedOperation(description="Launches the groovy console and loads the passed file name") @ManagedOperationParameter(name="fileName", description="The name of the local file to load" ) public void launchConsole(String fileName) { try { try { GroovyClassLoader loader = new GroovyClassLoader(getClass().getClassLoader(), compilerConfiguration); Class<?> clazz = loader.parseClass(new String(URLHelper.getBytesFromURL(ClassLoader.getSystemResource("groovy/ui/Console.groovy")))); loader.close(); Constructor<?> ctor = clazz.getDeclaredConstructor(Binding.class); Object console = ctor.newInstance(getBindings()); console.getClass().getDeclaredMethod("run").invoke(console); if(fileName!=null) { File file = new File(fileName); if(file.canRead()) { //clazz.getDeclaredMethod("main", String[].class).invoke(null, new Object[]{new String[]{fileName}}); //loadScriptFile(args[0] as File) clazz.getDeclaredMethod("loadScriptFile", File.class).invoke(console, file); //return; } } //clazz.getDeclaredMethod("main", String[].class).invoke(null, new Object[]{new String[]{}}); return; } catch (Exception ex) { ex.printStackTrace(System.err); } Class<?> clazz = Class.forName("groovy.ui.Console"); Constructor<?> ctor = clazz.getDeclaredConstructor(Binding.class); Object console = ctor.newInstance(getBindings()); console.getClass().getDeclaredMethod("run").invoke(console); } catch (Exception e) { error("Failed to launch console", e); throw new RuntimeException("Failed to launch console", e); } } /** * <p>Fired when the app context starts. Triggers the creation of default bindings provided to all script invocations.</p> * {@inheritDoc} * @see org.helios.apmrouter.server.ServerComponentBean#onApplicationContextStart(org.springframework.context.event.ContextStartedEvent) */ @Override public void onApplicationContextStart(ContextStartedEvent event) { /* Build bindings */ } /** * Returns the compiler warning level * @return the compiler warning level * @see org.codehaus.groovy.control.CompilerConfiguration#getWarningLevel() */ @ManagedAttribute(description="The compiler warning level. Recognized values are: NONE:0, LIKELY_ERRORS:1, POSSIBLE_ERRORS:2, PARANOIA:3") public int getWarningLevel() { return compilerConfiguration.getWarningLevel(); } /** * Sets the compiler warning level * @param level the compler warning level * @see org.codehaus.groovy.control.CompilerConfiguration#setWarningLevel(int) */ @ManagedAttribute(description="The compiler warning level. Recognized values are: NONE:0, LIKELY_ERRORS:1, POSSIBLE_ERRORS:2, PARANOIA:3") public void setWarningLevel(int level) { compilerConfiguration.setWarningLevel(level); } /** * Returns the compiler source encoding * @return the compiler source encoding * @see org.codehaus.groovy.control.CompilerConfiguration#getSourceEncoding() */ @ManagedAttribute(description="The compiler source encoding") public String getSourceEncoding() { return compilerConfiguration.getSourceEncoding(); } /** * Sets the compiler source encoding * @param encoding the compiler source encoding * @see org.codehaus.groovy.control.CompilerConfiguration#setSourceEncoding(java.lang.String) */ @ManagedAttribute(description="The compiler source encoding") public void setSourceEncoding(String encoding) { compilerConfiguration.setSourceEncoding(encoding); } /** * Returns the compiler target directory * @return the compiler target directory * @see org.codehaus.groovy.control.CompilerConfiguration#getTargetDirectory() */ @ManagedAttribute(description="The compiler target directory") public String getTargetDirectory() { File f = compilerConfiguration.getTargetDirectory(); return f==null ? null : f.getAbsolutePath(); } /** * Sets the compiler target directory * @param directory the compiler target directory * @see org.codehaus.groovy.control.CompilerConfiguration#setTargetDirectory(java.lang.String) */ @ManagedAttribute(description="The compiler target directory") public void setTargetDirectory(String directory) { compilerConfiguration.setTargetDirectory(directory); } /** * Indicates if the compiler is verbose * @return true if the compiler is verbose, false otherwise * @see org.codehaus.groovy.control.CompilerConfiguration#getVerbose() */ @ManagedAttribute(description="Indicates if the compiler is verbose") public boolean isVerbose() { return compilerConfiguration.getVerbose(); } /** * Sets the verbosity of the compiler * @param verbose true to make verbose, false otherwise * @see org.codehaus.groovy.control.CompilerConfiguration#setVerbose(boolean) */ @ManagedAttribute(description="Indicates if the compiler is verbose") public void setVerbose(boolean verbose) { compilerConfiguration.setVerbose(verbose); } /** * Indicates if the compiler is in debug mode * @return true if the compiler is in debug mode, false otherwise * @see org.codehaus.groovy.control.CompilerConfiguration#getDebug() */ @ManagedAttribute(description="Indicates if the compiler is in debug mode") public boolean isDebug() { return compilerConfiguration.getDebug(); } /** * Sets the debug mode of the compiler * @param debug true for debug, false otherwise * @see org.codehaus.groovy.control.CompilerConfiguration#setDebug(boolean) */ @ManagedAttribute(description="Indicates if the compiler is in debug mode") public void setDebug(boolean debug) { compilerConfiguration.setDebug(debug); } /** * Returns the compiler tolerance, which is the maximum number of non-fatal errors before compilation is aborted * @return the maximum number of non-fatal errors before compilation is aborted * @see org.codehaus.groovy.control.CompilerConfiguration#getTolerance() */ @ManagedAttribute(description="The maximum number of non-fatal errors before compilation is aborted") public int getTolerance() { return compilerConfiguration.getTolerance(); } /** * Sets the compiler tolerance, which is the maximum number of non-fatal errors before compilation is aborted * @param tolerance the maximum number of non-fatal errors before compilation is aborted * @see org.codehaus.groovy.control.CompilerConfiguration#setTolerance(int) */ @ManagedAttribute(description="The maximum number of non-fatal errors before compilation is aborted") public void setTolerance(int tolerance) { compilerConfiguration.setTolerance(tolerance); } /** * Returns the compiler's base script class * @return the compiler's base script class * @see org.codehaus.groovy.control.CompilerConfiguration#getScriptBaseClass() */ @ManagedAttribute(description="The compiler's base script class") public String getScriptBaseClass() { return compilerConfiguration.getScriptBaseClass(); } /** * Sets the compiler's base script class * @param scriptBaseClass the compiler's base script class * @see org.codehaus.groovy.control.CompilerConfiguration#setScriptBaseClass(java.lang.String) */ @ManagedAttribute(description="The compiler's base script class") public void setScriptBaseClass(String scriptBaseClass) { compilerConfiguration.setScriptBaseClass(scriptBaseClass); } /** * Sets the compiler's minimum recompilation interval in seconds * @param time the compiler's minimum recompilation interval in seconds * @see org.codehaus.groovy.control.CompilerConfiguration#setMinimumRecompilationInterval(int) */ @ManagedAttribute(description="The compiler's minimum recompilation interval in seconds") public void setMinimumRecompilationInterval(int time) { compilerConfiguration.setMinimumRecompilationInterval(time); } /** * Returns the compiler's minimum recompilation interval in seconds * @return the compiler's minimum recompilation interval in seconds * @see org.codehaus.groovy.control.CompilerConfiguration#getMinimumRecompilationInterval() */ @ManagedAttribute(description="The compiler's minimum recompilation interval in seconds") public int getMinimumRecompilationInterval() { return compilerConfiguration.getMinimumRecompilationInterval(); } /** * Sets the compiler's target bytecode version * @param version the compiler's target bytecode version * @see org.codehaus.groovy.control.CompilerConfiguration#setTargetBytecode(java.lang.String) */ @ManagedAttribute(description="The compiler's target bytecode version") public void setTargetBytecode(String version) { compilerConfiguration.setTargetBytecode(version); } /** * Returns the compiler's target bytecode version * @return the compiler's target bytecode version * @see org.codehaus.groovy.control.CompilerConfiguration#getTargetBytecode() */ @ManagedAttribute(description="The compiler's target bytecode version") public String getTargetBytecode() { return compilerConfiguration.getTargetBytecode(); } /** * Returns the compiler's optimization options * @return the compiler's optimization options * @see org.codehaus.groovy.control.CompilerConfiguration#getOptimizationOptions() */ @ManagedAttribute(description="The compiler's optimization options") public Map<String, Boolean> getOptimizationOptions() { return compilerConfiguration.getOptimizationOptions(); } /** * Sets the compiler's optimization options * @param options the compiler's optimization options * @see org.codehaus.groovy.control.CompilerConfiguration#setOptimizationOptions(java.util.Map) */ @ManagedAttribute(description="The compiler's optimization options") public void setOptimizationOptions(Map<String, Boolean> options) { compilerConfiguration.setOptimizationOptions(options); } /** * {@inheritDoc} * @see org.helios.apmrouter.groovy.GroovyLoadedScriptListener#getScanTypeAnnotations() */ @Override public Set<Class<? extends Annotation>> getScanTypeAnnotations() { return Collections.emptySet(); } /** * {@inheritDoc} * @see org.helios.apmrouter.groovy.GroovyLoadedScriptListener#getScanMethodAnnotations() */ @Override public Set<Class<? extends Annotation>> getScanMethodAnnotations() { return new HashSet<Class<? extends Annotation>>(Arrays.asList(Start.class)); } /** * {@inheritDoc} * @see org.helios.apmrouter.groovy.GroovyLoadedScriptListener#getScanClasses() */ @Override public Set<Class<?>> getScanClasses() { return Collections.emptySet(); } /** * {@inheritDoc} * @see org.helios.apmrouter.groovy.GroovyLoadedScriptListener#onScanType(java.util.Set, java.lang.Class, java.lang.Object) */ @Override public void onScanType(Set<? extends Annotation> annotations, Class<?> clazz, Object instance) { info("\n\t===================================\n\tType Annotation Match:", clazz.getName(), "\n\t===================================\n"); } /** * {@inheritDoc} * @see org.helios.apmrouter.groovy.GroovyLoadedScriptListener#onScanMethod(java.util.Map, java.lang.Class, java.lang.Object) */ @Override public void onScanMethod(Map<Method, Set<Annotation>> methods, Class<?> clazz, Object instance) { StringBuilder b = new StringBuilder("\n\t===================================\n\tMethod Annotation Match:" + clazz.getName() + "\n\t===================================\n"); for(Map.Entry<Method, Set<Annotation>> match: methods.entrySet()) { b.append("\n\tMethod:").append(match.getKey().getName()); for(Annotation ann: match.getValue()) { b.append("\n\t\tAnn:" + ann.annotationType().getSimpleName()); } } info(b); } /** * {@inheritDoc} * @see org.helios.apmrouter.groovy.GroovyLoadedScriptListener#onScanClasses(java.util.Set, java.lang.Class, java.lang.Object) */ @Override public void onScanClasses(Set<Class<?>> annotations, Class<?> clazz, Object instance) { info("\n\t===================================\n\tInherritance Match:", clazz.getName(), "\n\t===================================\n"); } /** * Returns the currently configured compiler imports * @return the currently configured compiler imports */ @ManagedAttribute(description="The currently configured compiler imports") public Set<String> getImports() { return imports; } /** * Adds the passed imports to the configured compiler imports * @param imps the imports to add */ public void setImports(Set<String> imps) { if(imps!=null) { imps.removeAll(imports); if(imps.isEmpty()) return; if(this.isStarted()) { applyImports(new ImportCustomizer(), imps.toArray(new String[imps.size()])); imports.addAll(imps); } else { imports.addAll(imps); } } } /** * Sets a global variable, returning the previosuly bound object or null there was none * @param key The global variable key * @param value The global variable value * @return the previosuly bound object or null there was none */ public Object setGlobaVariable(String key, Object value) { if(key==null || key.trim().isEmpty()) throw new IllegalArgumentException("The passed key was null or empty", new Throwable()); if(value==null) throw new IllegalArgumentException("The passed value was null or empty", new Throwable()); return globalVariables.put(key, value); } /** * Returns the name global variable * @param key The global variable key * @return The global variable value or null if it has not been bound */ public Object getGlobaVariable(String key) { if(key==null || key.trim().isEmpty()) throw new IllegalArgumentException("The passed key was null or empty", new Throwable()); return globalVariables.get(key); } // ============================================================================================ // Static JMX Groovy Helper Functions // ============================================================================================ /** * Connects to the MBeanServer listening on the specified jmx service url and passes the {@link MBeanServerConnection} to the passed closure. * If the closure has max parameters more than 1, the {@link JMXConnector} will be passed as the second parameter and the closure is responsible * for closing the connection, unless the closure throws an exception in which the connection will be automatically closed. Othwerwise, only the * {@link MBeanServerConnection} will be passed to the closure and the connection will be automatically closed on closure completion. * @param jmxServiceUrl The JMXServiceURL string * @param username The username credentials * @param password The password credentials * @param closure The groovy closure that will handle the {@link MBeanServerConnection} */ public static void mbeanserver(String jmxServiceUrl, String username, String password, Closure<MBeanServerConnection> closure) { try { Map<String, ?> environment = null; if(username!=null) { environment = Collections.singletonMap(JMXConnector.CREDENTIALS, new String[]{username, password}); } JMXServiceURL svcUrl = new JMXServiceURL(jmxServiceUrl); int maxParams = closure.getMaximumNumberOfParameters(); JMXConnector connector = JMXConnectorFactory.connect(svcUrl, environment); try { if(maxParams>1) { closure.call(connector.getMBeanServerConnection(), connector); } else { closure.call(connector.getMBeanServerConnection()); } } catch (Exception ex) { try { connector.close(); } catch (Exception e) {} throw new RuntimeException(ex); } finally { if(maxParams==1) { try { connector.close(); } catch (Exception e) {} } } } catch (Exception ex) { throw new RuntimeException(ex); } } /** * Connects to the MBeanServer listening on the specified jmx service url and passes the {@link MBeanServerConnection} to the passed closure. * If the closure has max parameters more than 1, the {@link JMXConnector} will be passed as the second parameter and the closure is responsible * for closing the connection, unless the closure throws an exception in which the connection will be automatically closed. Othwerwise, only the * {@link MBeanServerConnection} will be passed to the closure and the connection will be automatically closed on closure completion. * @param jmxServiceUrl The JMXServiceURL string * @param closure The groovy closure that will handle the {@link MBeanServerConnection} */ public static void mbeanserver(String jmxServiceUrl, Closure<MBeanServerConnection> closure) { mbeanserver(jmxServiceUrl, null, null, closure); } /** * Passes the Helios {@link MBeanServer} to the passed closure. * @param closure The groovy closure that will handle the {@link MBeanServer} */ public static void mbeanserver(Closure<MBeanServer> closure) { closure.call(JMXHelper.getHeliosMBeanServer()); } /** * Executes a name query against the passed mbeanserver and passes each found {@link ObjectName} to the passed closure. * If the closure has 1 parameter, only the located {@link ObjectName}s will be passed to the closure. * Otherwise the the located {@link ObjectName}s and the {@link MBeanServerConnection} will be passed to the closure. * @param connection The mbeanserver to query * @param filter The object name filter which can be an actual {@link ObjectName}, or one will be built from the parameter's {@link #toString()}. * @param qe The option query expression. Ignored if null. * @param closure The closure to pass the located {@link ObjectName}s to. */ public static void querymbeans(MBeanServerConnection connection, Object filter, QueryExp qe, Closure<ObjectName> closure) { try { ObjectName on = null; if(filter instanceof ObjectName) { on = (ObjectName)filter; } else { on = JMXHelper.objectName(filter); } for(ObjectName o: connection.queryNames(on, qe)) { if(closure.getMaximumNumberOfParameters()>1) { closure.call(o, connection); } else { closure.call(o); } } } catch (Exception ex) { throw new RuntimeException(ex); } } /** * Executes a name query against the passed mbeanserver and passes each found {@link ObjectName} to the passed closure. * If the closure has 1 parameter, only the located {@link ObjectName}s will be passed to the closure. * Otherwise the the located {@link ObjectName}s and the {@link MBeanServerConnection} will be passed to the closure. * @param connection The mbeanserver to query * @param filter The object name filter which can be an actual {@link ObjectName}, or one will be built from the parameter's {@link #toString()}. * @param closure The closure to pass the located {@link ObjectName}s to. */ public static void querymbeans(MBeanServerConnection connection, Object filter, Closure<ObjectName> closure) { querymbeans(connection, filter, closure); } /** * Creates a JMX notification subscription * @param connection The mbean server to subscribe to * @param objectName The object name to subscribe to * @param listener A listener closure * @param filter A filter closure * @return the subscription closer */ public static Closeable subscribe(MBeanServerConnection connection, String objectName, Closure<Void> listener, Closure<Boolean> filter) { ObjectName on = JMXHelper.objectName(objectName); return GroovyNotificationListenerControl.builder(on, listener) .filter(filter) .connection(connection) .finalSub(on.isPattern()) .build(); } /** * Creates a JMX notification subscription to the local helios mbean server * @param objectName The object name to subscribe to * @param listener A listener closure * @param filter A filter closure * @return the subscription closer */ public static Closeable subscribe(String objectName, Closure<Void> listener, Closure<Boolean> filter) { return subscribe(JMXHelper.getHeliosMBeanServer(), objectName, listener, filter); } /** * Creates a JMX notification subscription to the local helios mbean server * @param objectName The object name to subscribe to * @param listener A listener closure * @return the subscription closer */ public static Closeable subscribe(String objectName, Closure<Void> listener) { return subscribe(JMXHelper.getHeliosMBeanServer(), objectName, listener, null); } }