org.rhq.bindings.ScriptEngineFactory Maven / Gradle / Ivy
Show all versions of rhq-script-bindings Show documentation
/*
* RHQ Management Platform
* Copyright (C) 2005-2011 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.bindings;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.MethodDescriptor;
import java.io.IOException;
import java.lang.reflect.Method;
import java.security.PermissionCollection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.bindings.client.RhqManager;
import org.rhq.bindings.util.MultiScriptSourceProvider;
import org.rhq.bindings.util.NoTopLevelIndirection;
import org.rhq.bindings.util.PackageFinder;
import org.rhq.scripting.CodeCompletion;
import org.rhq.scripting.ScriptEngineInitializer;
import org.rhq.scripting.ScriptEngineProvider;
import org.rhq.scripting.ScriptSourceProvider;
/**
* This is RHQ specific imitation of ScriptEngineFactory.
*
* In RHQ, we provide a standard set of bound variables in the script context
* and also import classes from our standard packages to ease the development
* of the scripts.
*
* This factory is able to instantiate a script engine and initialize it consistently
* so that all users of the script engine get the uniform environment to write the scripts
* in.
*
* @author Lukas Krejci
*/
public class ScriptEngineFactory {
private static final Log LOG = LogFactory.getLog(ScriptEngineFactory.class);
private static final Map KNOWN_PROVIDERS;
static {
KNOWN_PROVIDERS = new HashMap();
reloadScriptEngineProviders(null);
}
private ScriptEngineFactory() {
}
/**
* Reloads the list of the known script engine providers using the given classloader
* or the current thread's context classloader if it is null.
*
* @param classLoader the classloader used to find the script engine providers on the classpath
*
* @throws IllegalStateException if more than 1 script engine provider is found for a single language
*/
public static void reloadScriptEngineProviders(ClassLoader classLoader) {
if (classLoader == null) {
classLoader = Thread.currentThread().getContextClassLoader();
}
ServiceLoader loader = ServiceLoader.load(ScriptEngineProvider.class, classLoader);
KNOWN_PROVIDERS.clear();
for (ScriptEngineProvider provider : loader) {
String lang = provider.getSupportedLanguage();
if (KNOWN_PROVIDERS.containsKey(lang)) {
String existing = KNOWN_PROVIDERS.get(lang).getClass().getName();
String thisOne = provider.getClass().getName();
throw new IllegalStateException("'" + lang + "' scripting language provided by at least 2 providers: '"
+ existing + "' and '" + thisOne + "'. Only 1 provider per language is allowed.");
}
KNOWN_PROVIDERS.put(lang, provider);
}
}
/**
* @return the set of the scripting languages supported by this factory
*/
public static Set getSupportedLanguages() {
return new HashSet(KNOWN_PROVIDERS.keySet());
}
public static String getLanguageByScriptFileExtension(String fileExtension) {
for (ScriptEngineProvider p : KNOWN_PROVIDERS.values()) {
if (fileExtension.equals(p.getScriptFileExtension())) {
return p.getSupportedLanguage();
}
}
return null;
}
public static String getFileExtensionForLanguage(String language) {
ScriptEngineProvider provider = KNOWN_PROVIDERS.get(language);
if (provider == null) {
return null;
}
return provider.getScriptFileExtension();
}
/**
* Initializes the script engine for given language.
*
* @param language the language of the script to instantiate
* @param packageFinder the package finder to find the standard packages in user provided locations
* @param bindings the initial standard bindings or null if none required
*
* @return the initialized engine or null if a {@link ScriptEngineProvider} for given language isn't known.
*
* @throws ScriptException on error during initialization of the script environment
* @throws IOException if the package finder fails to find the packages
*/
public static ScriptEngine getScriptEngine(String language, PackageFinder packageFinder, StandardBindings bindings) throws ScriptException, IOException {
return getSecuredScriptEngine(language, packageFinder, bindings, null);
}
/**
* This method is similar to the {@link #getScriptEngine(String, PackageFinder, StandardBindings, ScriptSourceProvider...)} method
* but additionally applies a security wrapper on the returned script engine so that the scripts execute
* with the provided java permissions.
*
* @see #getScriptEngine(String, PackageFinder, StandardBindings, ScriptSourceProvider...)
*/
public static ScriptEngine getSecuredScriptEngine(String language, PackageFinder packageFinder,
StandardBindings bindings, PermissionCollection permissions) throws ScriptException, IOException {
ScriptEngineInitializer initializer = getInitializer(language);
if (initializer == null) {
return null;
}
ScriptEngine engine = initializer.instantiate(packageFinder.findPackages("org.rhq.core.domain"),
permissions);
if (bindings != null) {
injectStandardBindings(engine, bindings, true);
}
return engine;
}
/**
* Injects the values provided in the bindings into the {@link ScriptContext#ENGINE_SCOPE engine scope}
* of the provided script engine.
*
* @param engine the engine
* @param bindings the bindings
* @param deleteExistingBindings true if the existing bindings should be replaced by the provided ones, false
* if the provided bindings should be added to the existing ones (possibly overwriting bindings with the same name).
* @param scriptSourceProviders the list of script source providers to be used by the script engine to locate the scripts.
* Note that the providers become associated with the script engine and its bindings and therefore should
* not be used with any other script engine. If the providers implement the
* {@link StandardBindings.RhqFacadeChangeListener} interface, they will be automatically hooked up with the
* bindings
so that the providers will get notified whenever the rhq facade changes.
*/
public static void injectStandardBindings(ScriptEngine engine, StandardBindings bindings,
boolean deleteExistingBindings, ScriptSourceProvider... scriptSourceProviders) {
bindings.preInject(engine);
Bindings engineBindings = deleteExistingBindings ? engine.createBindings() : engine
.getBindings(ScriptContext.ENGINE_SCOPE);
for (Map.Entry entry : bindings.entrySet()) {
engineBindings.put(entry.getKey(), entry.getValue());
}
if (scriptSourceProviders != null) {
//first figure out which initializer to use with this script engine
String language = (String) engine.getFactory().getParameter(ScriptEngine.NAME);
ScriptEngineProvider engineProvider = KNOWN_PROVIDERS.get(language);
if (engineProvider == null) {
throw new IllegalArgumentException("The supplied script engine [" + engine + "] is not supported.");
}
ScriptEngineInitializer initializer = engineProvider.getInitializer();
ScriptSourceProvider provider = null;
for (ScriptSourceProvider p : scriptSourceProviders) {
if (p instanceof StandardBindings.RhqFacadeChangeListener) {
bindings.addRhqFacadeChangeListener((StandardBindings.RhqFacadeChangeListener) p);
}
}
provider = new MultiScriptSourceProvider(scriptSourceProviders);
initializer.installScriptSourceProvider(engine, provider);
}
engine.setBindings(engineBindings, ScriptContext.ENGINE_SCOPE);
bindings.postInject(engine);
}
/**
* Remove the specified bindings from the engine.
*
* @param engine the engine
* @param keySet the binding keys to be removed
*/
public static void removeBindings(ScriptEngine engine, Set keySet) {
Bindings engineBindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
for (RhqManager key : keySet) {
engineBindings.remove(key.name());
}
engine.setBindings(engineBindings, ScriptContext.ENGINE_SCOPE);
}
/**
* Goes through the methods of the object found in the scriptEngine
's ENGINE_SCOPE
* and for each of them generates a top-level function that is called the same name and accepts the same
* parameters.
*
* @param scriptEngine the script engine to generate the top-level functions in
* @param bindingName the name of the object in the script engine to generate the functions from
*
* @see ScriptEngineInitializer#generateIndirectionMethod(String, Method)
* @see NoTopLevelIndirection
*/
public static void bindIndirectionMethods(ScriptEngine scriptEngine, String bindingName) {
Object object = scriptEngine.get(bindingName);
if (object == null) {
LOG.debug("The script engine doesn't contain a binding called '" + bindingName
+ "'. No indirection functions will be generated.");
return;
}
ScriptEngineInitializer initializer = getInitializer((String) scriptEngine.getFactory().getParameter(
ScriptEngine.NAME));
try {
BeanInfo beanInfo = Introspector.getBeanInfo(object.getClass(), Object.class);
MethodDescriptor[] methodDescriptors = beanInfo.getMethodDescriptors();
Map> overloadsPerMethodName = new HashMap>();
for (MethodDescriptor methodDescriptor : methodDescriptors) {
Method method = methodDescriptor.getMethod();
if (shouldIndirect(method)) {
Set overloads = overloadsPerMethodName.get(method.getName());
if (overloads == null) {
overloads = new HashSet();
overloadsPerMethodName.put(method.getName(), overloads);
}
overloads.add(method);
}
}
for (Set overloads : overloadsPerMethodName.values()) {
Set methodDefs = initializer.generateIndirectionMethods(bindingName, overloads);
for (String methodDef : methodDefs) {
try {
scriptEngine.eval(methodDef);
} catch (ScriptException e) {
LOG.warn("Unable to define global function declared as:\n" + methodDef, e);
}
}
}
} catch (IntrospectionException e) {
LOG.debug("Could not inspect class " + object.getClass().getName()
+ ". No indirection methods for variable '" + bindingName + "' will be generated.", e);
}
}
public static ScriptEngineInitializer getInitializer(String language) {
ScriptEngineProvider provider = KNOWN_PROVIDERS.get(language);
return provider == null ? null : provider.getInitializer();
}
public static CodeCompletion getCodeCompletion(String language) {
ScriptEngineProvider provider = KNOWN_PROVIDERS.get(language);
return provider == null ? null : provider.getCodeCompletion();
}
private static boolean shouldIndirect(Method method) {
return method.getAnnotation(NoTopLevelIndirection.class) == null;
}
}