/* * Copyright 2004,2004 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.bsf; import java.beans.PropertyChangeSupport; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.Enumeration; import java.util.Hashtable; import java.util.Iterator; import java.util.MissingResourceException; import java.util.NoSuchElementException; import java.util.Properties; import java.util.StringTokenizer; import java.util.Vector; import org.apache.bsf.util.CodeBuffer; import org.apache.bsf.util.ObjectRegistry; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * This class is the entry point to the bean scripting framework. An * application wishing to integrate scripting to a Java app would * place an instance of a BSFManager in their code and use its services * to register the beans they want to make available for scripting, * load scripting engines, and run scripts. * <p> * BSFManager serves as the registry of available scripting engines * as well. Loading and unloading of scripting engines is * supported as well. Each BSFManager loads one engine per language. * Several BSFManagers can be created per JVM. * * @author Sanjiva Weerawarana * @author Matthew J. Duftler * @author Sam Ruby * @author Olivier Gruber (added original debugging support) * @author Don Schwarz (added support for registering languages dynamically) */ public class BSFManager { // version string is in the form "abc.yyyymmdd" where // "abc" represents a dewey decimal number (three levels, each between 0 and 9), // and "yyyy" a four digit year, "mm" a two digit month, "dd" a two digit day. // // Example: "240.20060925" stands for: BSF version "2.4.0" as of "2006-09-25" protected static String version="240.20061006"; // table of registered scripting engines protected static Hashtable registeredEngines = new Hashtable(); // mapping of file extensions to languages protected static Hashtable extn2Lang = new Hashtable(); // table of scripting engine instances created by this manager. // only one instance of a given language engine is created by a single // manager instance. protected Hashtable loadedEngines = new Hashtable(); // table of registered beans for use by scripting engines. protected ObjectRegistry objectRegistry = new ObjectRegistry(); // prop change support containing loaded engines to inform when any // of my interesting properties change protected PropertyChangeSupport pcs; // the class loader to use if a class loader is needed. Default is // he who loaded me (which may be null in which case its Class.forName). // protected ClassLoader classLoader = getClass().getClassLoader(); protected ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // rgf, 2006-01-05 // temporary directory to use to dump temporary files into. Note that // if class files are dropped here then unless this dir is in the // classpath or unless the classloader knows to look here, the classes // will not be found. protected String tempDir = "."; // classpath used by those that need a classpath protected String classPath; // stores BSFDeclaredBeans representing objects // introduced by a client of BSFManager protected Vector declaredBeans = new Vector(); private Log logger = LogFactory.getLog(this.getClass().getName()); ////////////////////////////////////////////////////////////////////// // // pre-register engines that BSF supports off the shelf // ////////////////////////////////////////////////////////////////////// static { try { Enumeration e = BSFManager.class.getClassLoader().getResources("org/apache/bsf/Languages.properties"); while (e.hasMoreElements()) { URL url = (URL)e.nextElement(); InputStream is = url.openStream(); Properties p = new Properties(); p.load(is); for (Enumeration keys = p.propertyNames(); keys.hasMoreElements();) { String key = (String) keys.nextElement(); String value = p.getProperty(key); String className = value.substring(0, value.indexOf(",")); // get the extensions for this language String exts = value.substring(value.indexOf(",")+1, value.length()); StringTokenizer st = new StringTokenizer(exts, "|"); String[] extensions = new String[st.countTokens()]; for (int i = 0; st.hasMoreTokens(); i++) { extensions[i] = ((String) st.nextToken()).trim(); } registerScriptingEngine(key, className, extensions); } } } catch (IOException ex) { ex.printStackTrace(); System.err.println("Error reading Languages file " + ex); } catch (NoSuchElementException nsee) { nsee.printStackTrace(); System.err.println("Syntax error in Languages resource bundle"); } catch (MissingResourceException mre) { mre.printStackTrace(); System.err.println("Initialization error: " + mre.toString()); } } public BSFManager() { pcs = new PropertyChangeSupport(this); } /** Returns the version string of BSF. * * @return version string in the form "abc.yyyymmdd" where "abc" represents a dewey decimal number (three levels, each between 0 and 9), and "yyyy" a four digit year, "mm" a two digit month, "dd" a two digit day. * <br>Example: "<code>240.20061006</code>" stands for: BSF version <code>2.4.0</code> as of <code>2006-10-06</code>. * * * @since 2006-01-17 */ public static String getVersion() { return version; } /** * Apply the given anonymous function of the given language to the given * parameters and return the resulting value. * * @param lang language identifier * @param source (context info) the source of this expression (e.g., filename) * @param lineNo (context info) the line number in source for expr * @param columnNo (context info) the column number in source for expr * @param funcBody the multi-line, value returning script to evaluate * @param paramNames the names of the parameters above assumes * @param arguments values of the above parameters * * @exception BSFException if anything goes wrong while running the script */ public Object apply(String lang, String source, int lineNo, int columnNo, Object funcBody, Vector paramNames, Vector arguments) throws BSFException { logger.debug("BSFManager:apply"); final BSFEngine e = loadScriptingEngine(lang); final String sourcef = source; final int lineNof = lineNo, columnNof = columnNo; final Object funcBodyf = funcBody; final Vector paramNamesf = paramNames; final Vector argumentsf = arguments; Object result = null; try { final Object resultf = AccessController.doPrivileged(new PrivilegedExceptionAction() { public Object run() throws Exception { return e.apply(sourcef, lineNof, columnNof, funcBodyf, paramNamesf, argumentsf); } }); result = resultf; } catch (PrivilegedActionException prive) { logger.error("Exception: ", prive); throw (BSFException) prive.getException(); } return result; } /** * Compile the application of the given anonymous function of the given * language to the given parameters into the given <tt>CodeBuffer</tt>. * * @param lang language identifier * @param source (context info) the source of this expression (e.g., filename) * @param lineNo (context info) the line number in source for expr * @param columnNo (context info) the column number in source for expr * @param funcBody the multi-line, value returning script to evaluate * @param paramNames the names of the parameters above assumes * @param arguments values of the above parameters * @param cb code buffer to compile into * * @exception BSFException if anything goes wrong while running the script */ public void compileApply(String lang, String source, int lineNo, int columnNo, Object funcBody, Vector paramNames, Vector arguments, CodeBuffer cb) throws BSFException { logger.debug("BSFManager:compileApply"); final BSFEngine e = loadScriptingEngine(lang); final String sourcef = source; final int lineNof = lineNo, columnNof = columnNo; final Object funcBodyf = funcBody; final Vector paramNamesf = paramNames; final Vector argumentsf = arguments; final CodeBuffer cbf = cb; try { AccessController.doPrivileged(new PrivilegedExceptionAction() { public Object run() throws Exception { e.compileApply(sourcef, lineNof, columnNof, funcBodyf, paramNamesf, argumentsf, cbf); return null; } }); } catch (PrivilegedActionException prive) { logger.error("Exception :", prive); throw (BSFException) prive.getException(); } } /** * Compile the given expression of the given language into the given * <tt>CodeBuffer</tt>. * * @param lang language identifier * @param source (context info) the source of this expression (e.g., filename) * @param lineNo (context info) the line number in source for expr * @param columnNo (context info) the column number in source for expr * @param expr the expression to compile * @param cb code buffer to compile into * * @exception BSFException if any error while compiling the expression */ public void compileExpr(String lang, String source, int lineNo, int columnNo, Object expr, CodeBuffer cb) throws BSFException { logger.debug("BSFManager:compileExpr"); final BSFEngine e = loadScriptingEngine(lang); final String sourcef = source; final int lineNof = lineNo, columnNof = columnNo; final Object exprf = expr; final CodeBuffer cbf = cb; try { AccessController.doPrivileged(new PrivilegedExceptionAction() { public Object run() throws Exception { e.compileExpr(sourcef, lineNof, columnNof, exprf, cbf); return null; } }); } catch (PrivilegedActionException prive) { logger.error("Exception :", prive); throw (BSFException) prive.getException(); } } /** * Compile the given script of the given language into the given * <tt>CodeBuffer</tt>. * * @param lang language identifier * @param source (context info) the source of this script (e.g., filename) * @param lineNo (context info) the line number in source for script * @param columnNo (context info) the column number in source for script * @param script the script to compile * @param cb code buffer to compile into * * @exception BSFException if any error while compiling the script */ public void compileScript(String lang, String source, int lineNo, int columnNo, Object script, CodeBuffer cb) throws BSFException { logger.debug("BSFManager:compileScript"); final BSFEngine e = loadScriptingEngine(lang); final String sourcef = source; final int lineNof = lineNo, columnNof = columnNo; final Object scriptf = script; final CodeBuffer cbf = cb; try { AccessController.doPrivileged(new PrivilegedExceptionAction() { public Object run() throws Exception { e.compileScript(sourcef, lineNof, columnNof, scriptf, cbf); return null; } }); } catch (PrivilegedActionException prive) { logger.error("Exception :", prive); throw (BSFException) prive.getException(); } } /** * Declare a bean. The difference between declaring and registering * is that engines are spsed to make declared beans "pre-available" * in the scripts as far as possible. That is, if a script author * needs a registered bean, he needs to look it up in some way. However * if he needs a declared bean, the language has the responsibility to * make those beans avaialable "automatically." * <p> * When a bean is declared it is automatically registered as well * so that any declared bean can be gotton to by looking it up as well. * <p> * If any of the languages that are already running in this manager * says they don't like this (by throwing an exception) then this * method will simply quit with that exception. That is, any engines * that come after than in the engine enumeration will not even be * told about this new bean. * <p> * So, in general its best to declare beans before the manager has * been asked to load any engines because then the user can be informed * when an engine rejects it. Also, its much more likely that an engine * can declare a bean at start time than it can at any time. * * @param beanName name to declare bean as * @param bean the bean that's being declared * @param type the type to represent the bean as * * @exception BSFException if any of the languages that are already * running decides to throw an exception when asked to * declare this bean. */ public void declareBean(String beanName, Object bean, Class type) throws BSFException { logger.debug("BSFManager:declareBean"); registerBean(beanName, bean); BSFDeclaredBean tempBean = new BSFDeclaredBean(beanName, bean, type); declaredBeans.addElement(tempBean); Enumeration enginesEnum = loadedEngines.elements(); BSFEngine engine; while (enginesEnum.hasMoreElements()) { engine = (BSFEngine) enginesEnum.nextElement(); engine.declareBean(tempBean); } } /** * Evaluate the given expression of the given language and return the * resulting value. * * @param lang language identifier * @param source (context info) the source of this expression (e.g., filename) * @param lineNo (context info) the line number in source for expr * @param columnNo (context info) the column number in source for expr * @param expr the expression to evaluate * * @exception BSFException if anything goes wrong while running the script */ public Object eval(String lang, String source, int lineNo, int columnNo, Object expr) throws BSFException { logger.debug("BSFManager:eval"); final BSFEngine e = loadScriptingEngine(lang); final String sourcef = source; final int lineNof = lineNo, columnNof = columnNo; final Object exprf = expr; Object result = null; try { final Object resultf = AccessController.doPrivileged(new PrivilegedExceptionAction() { public Object run() throws Exception { return e.eval(sourcef, lineNof, columnNof, exprf); } }); result = resultf; } catch (PrivilegedActionException prive) { logger.error("Exception: ", prive); throw (BSFException) prive.getException(); } return result; } ////////////////////////////////////////////////////////////////////// // // Convenience functions for exec'ing and eval'ing scripts directly // without loading and dealing with engines etc.. // ////////////////////////////////////////////////////////////////////// /** * Execute the given script of the given language. * * @param lang language identifier * @param source (context info) the source of this expression (e.g., filename) * @param lineNo (context info) the line number in source for expr * @param columnNo (context info) the column number in source for expr * @param script the script to execute * * @exception BSFException if anything goes wrong while running the script */ public void exec(String lang, String source, int lineNo, int columnNo, Object script) throws BSFException { logger.debug("BSFManager:exec"); final BSFEngine e = loadScriptingEngine(lang); final String sourcef = source; final int lineNof = lineNo, columnNof = columnNo; final Object scriptf = script; try { AccessController.doPrivileged(new PrivilegedExceptionAction() { public Object run() throws Exception { e.exec(sourcef, lineNof, columnNof, scriptf); return null; } }); } catch (PrivilegedActionException prive) { logger.error("Exception :", prive); throw (BSFException) prive.getException(); } } /** * Execute the given script of the given language, attempting to * emulate an interactive session w/ the language. * * @param lang language identifier * @param source (context info) the source of this expression * (e.g., filename) * @param lineNo (context info) the line number in source for expr * @param columnNo (context info) the column number in source for expr * @param script the script to execute * * @exception BSFException if anything goes wrong while running the script */ public void iexec(String lang, String source, int lineNo, int columnNo, Object script) throws BSFException { logger.debug("BSFManager:iexec"); final BSFEngine e = loadScriptingEngine(lang); final String sourcef = source; final int lineNof = lineNo, columnNof = columnNo; final Object scriptf = script; try { AccessController.doPrivileged(new PrivilegedExceptionAction() { public Object run() throws Exception { e.iexec(sourcef, lineNof, columnNof, scriptf); return null; } }); } catch (PrivilegedActionException prive) { logger.error("Exception :", prive); throw (BSFException) prive.getException(); } } /** * Get classLoader */ public ClassLoader getClassLoader() { logger.debug("BSFManager:getClassLoader"); return classLoader; } /** * Get classPath */ public String getClassPath() { logger.debug("BSFManager:getClassPath"); if (classPath == null) { try { classPath = System.getProperty("java.class.path"); } catch (Throwable t) { logger.debug("Exception :", t); // prolly a security exception .. so no can do } } return classPath; } /** * Determine the language of a script file by looking at the file * extension. * * @param fileName the name of the file * * @return the scripting language the file is in if the file extension * is known to me (must have been registered via * registerScriptingEngine). * * @exception BSFException if file's extension is unknown. */ public static String getLangFromFilename(String fileName) throws BSFException { int dotIndex = fileName.lastIndexOf("."); if (dotIndex != -1) { String extn = fileName.substring(dotIndex + 1); String langval = (String) extn2Lang.get(extn); String lang = null; int index, loops = 0; if (langval != null) { while ((index = langval.indexOf(":", 0)) != -1) { // Great. Multiple language engines registered // for this extension. // Try to find first one that is in our classpath. lang = langval.substring(0, index); langval = langval.substring(index + 1); loops++; // Test to see if in classpath try { String engineName = (String) registeredEngines.get(lang); Class.forName(engineName); } catch (ClassNotFoundException cnfe) { // Bummer. lang = langval; continue; } // Got past that? Good. break; } if (loops == 0) { lang = langval; } } if (lang != null && lang != "") { return lang; } } throw new BSFException(BSFException.REASON_OTHER_ERROR, "file extension missing or unknown: " + "unable to determine language for '" + fileName + "'"); } /** * Return the current object registry of the manager. * * @return the current registry. */ public ObjectRegistry getObjectRegistry() { return objectRegistry; } /** * Get tempDir */ public String getTempDir() { return tempDir; } /** * Determine whether a language is registered. * * @param lang string identifying a language * * @return true iff it is */ public static boolean isLanguageRegistered(String lang) { return (registeredEngines.get(lang) != null); } ////////////////////////////////////////////////////////////////////// // // Bean scripting framework services // ////////////////////////////////////////////////////////////////////// /** * Load a scripting engine based on the lang string identifying it. * * @param lang string identifying language * @exception BSFException if the language is unknown (i.e., if it * has not been registered) with a reason of * REASON_UNKNOWN_LANGUAGE. If the language is known but * if the interface can't be created for some reason, then * the reason is set to REASON_OTHER_ERROR and the actual * exception is passed on as well. */ public BSFEngine loadScriptingEngine(String lang) throws BSFException { logger.debug("BSFManager:loadScriptingEngine"); // if its already loaded return that BSFEngine eng = (BSFEngine) loadedEngines.get(lang); if (eng != null) { return eng; } // is it a registered language? String engineClassName = (String) registeredEngines.get(lang); if (engineClassName == null) { logger.error("unsupported language: " + lang); throw new BSFException(BSFException.REASON_UNKNOWN_LANGUAGE, "unsupported language: " + lang); } // create the engine and initialize it. if anything goes wrong // except. try { Class engineClass = (classLoader == null) ? Class.forName(engineClassName) : classLoader.loadClass(engineClassName); final BSFEngine engf = (BSFEngine) engineClass.newInstance(); final BSFManager thisf = this; final String langf = lang; final Vector dbf = declaredBeans; AccessController.doPrivileged(new PrivilegedExceptionAction() { public Object run() throws Exception { engf.initialize(thisf, langf, dbf); return null; } }); eng = engf; loadedEngines.put(lang, eng); pcs.addPropertyChangeListener(eng); return eng; } catch (PrivilegedActionException prive) { logger.error("Exception :", prive); throw (BSFException) prive.getException(); } catch (Throwable t) { logger.error("Exception :", t); throw new BSFException(BSFException.REASON_OTHER_ERROR, "unable to load language: " + lang, t); } } /** * return a handle to a bean registered in the bean registry by the * application or a scripting engine. Returns null if bean is not found. * * @param beanName name of bean to look up * * @return the bean if its found or null */ public Object lookupBean(String beanName) { logger.debug("BSFManager:lookupBean"); try { return ((BSFDeclaredBean)objectRegistry.lookup(beanName)).bean; } catch (IllegalArgumentException e) { logger.debug("Exception :", e); return null; } } /** * Registering a bean allows a scripting engine or the application to * access that bean by name and to manipulate it. * * @param beanName name to register under * @param bean the bean to register */ public void registerBean(String beanName, Object bean) { logger.debug("BSFManager:registerBean"); BSFDeclaredBean tempBean; if(bean == null) { tempBean = new BSFDeclaredBean(beanName, null, null); } else { tempBean = new BSFDeclaredBean(beanName, bean, bean.getClass()); } objectRegistry.register(beanName, tempBean); } /** * Register a scripting engine in the static registry of the * BSFManager. * * @param lang string identifying language * @param engineClassName fully qualified name of the class interfacing * the language to BSF. * @param extensions array of file extensions that should be mapped to * this language type. may be null. */ public static void registerScriptingEngine(String lang, String engineClassName, String[] extensions) { registeredEngines.put(lang, engineClassName); if (extensions != null) { for (int i = 0; i < extensions.length; i++) { String langstr = (String) extn2Lang.get(extensions[i]); langstr = (langstr == null) ? lang : lang + ":" + langstr; extn2Lang.put(extensions[i], langstr); } } } /** * Set the class loader for those that need to use it. Default is he * who loaded me or null (i.e., its Class.forName). * * @param classLoader the class loader to use. */ public void setClassLoader(ClassLoader classLoader) { logger.debug("BSFManager:setClassLoader"); pcs.firePropertyChange("classLoader", this.classLoader, classLoader); this.classLoader = classLoader; } /** * Set the classpath for those that need to use it. Default is the value * of the java.class.path property. * * @param classPath the classpath to use */ public void setClassPath(String classPath) { logger.debug("BSFManager:setClassPath"); pcs.firePropertyChange("classPath", this.classPath, classPath); this.classPath = classPath; } /** * Set the object registry used by this manager. By default a new * one is created when the manager is new'ed and this overwrites * that one. * * @param objectRegistry the registry to use */ public void setObjectRegistry(ObjectRegistry objectRegistry) { logger.debug("BSFManager:setObjectRegistry"); this.objectRegistry = objectRegistry; } /** * Temporary directory to put stuff into (for those who need to). Note * that unless this directory is in the classpath or unless the * classloader knows to look in here, any classes here will not * be found! BSFManager provides a service method to load a class * which uses either the classLoader provided by the class loader * property or, if that fails, a class loader which knows to load from * the tempdir to try to load the class. Default value of tempDir * is "." (current working dir). * * @param tempDir the temporary directory */ public void setTempDir(String tempDir) { logger.debug("BSFManager:setTempDir"); pcs.firePropertyChange("tempDir", this.tempDir, tempDir); this.tempDir = tempDir; } /** * Gracefully terminate all engines */ public void terminate() { logger.debug("BSFManager:terminate"); Enumeration enginesEnum = loadedEngines.elements(); BSFEngine engine; while (enginesEnum.hasMoreElements()) { engine = (BSFEngine) enginesEnum.nextElement(); engine.terminate(); } loadedEngines = new Hashtable(); } /** * Undeclare a previously declared bean. This removes the bean from * the list of declared beans in the manager as well as asks every * running engine to undeclared the bean. As with above, if any * of the engines except when asked to undeclare, this method does * not catch that exception. Quietly returns if the bean is unknown. * * @param beanName name of bean to undeclare * * @exception BSFException if any of the languages that are already * running decides to throw an exception when asked to * undeclare this bean. */ public void undeclareBean(String beanName) throws BSFException { logger.debug("BSFManager:undeclareBean"); unregisterBean(beanName); BSFDeclaredBean tempBean = null; boolean found = false; for (Iterator i = declaredBeans.iterator(); i.hasNext();) { tempBean = (BSFDeclaredBean) i.next(); if (tempBean.name.equals(beanName)) { found = true; break; } } if (found) { declaredBeans.removeElement(tempBean); Enumeration enginesEnum = loadedEngines.elements(); while (enginesEnum.hasMoreElements()) { BSFEngine engine = (BSFEngine) enginesEnum.nextElement(); engine.undeclareBean(tempBean); } } } /** * Unregister a previously registered bean. Silent if name is not found. * * @param beanName name of bean to unregister */ public void unregisterBean(String beanName) { logger.debug("BSFManager:unregisterBean"); objectRegistry.unregister(beanName); } }