/* * The contents of this file are subject to the Mozilla Public License Version 1.1 * (the "License"); you may not use this file except in compliance with the License. * You may obtain a copy of the License at . * * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT * WARRANTY OF ANY KIND, either express or implied. See the License for the specific * language governing rights and limitations under the License. * * The Original Code is the Venice Web Communities System. * * The Initial Developer of the Original Code is Eric J. Bowersox , * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are * Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. * * Contributor(s): */ package com.silverwrist.venice.ui.script; import java.lang.reflect.*; import java.io.*; import java.util.*; import com.ibm.bsf.*; import org.apache.log4j.*; import com.silverwrist.util.*; import com.silverwrist.venice.ui.*; import com.silverwrist.venice.ui.helpers.ThrowableContent; public class ScriptManager { /*-------------------------------------------------------------------------------- * Internal class for script utility *-------------------------------------------------------------------------------- */ public class MyScriptLibrary extends ScriptLibrary { public Object exec(String script) throws ScriptingException, ThrowableContent { RequestInput ri = (RequestInput)(ScriptManager.this.lookupObject("request")); String script_file = ri.getScriptName(script); String logger_name = ri.getScriptLoggerName(script); if (logger.isDebugEnabled()) logger.debug("EXECUTING " + script_file); ScriptReturn sro = new ScriptReturn(); ScriptManager.this.exec(new File(script_file),logger_name,sro); return sro.get(); } // end exec public void output(Object o) { if (return_stack.size()>0) { // set the return value ScriptReturn sro = (ScriptReturn)(return_stack.getFirst()); sro.set(o); } // end if } // end output } // end class MyScriptLibrary /*-------------------------------------------------------------------------------- * Internal class for doing logging *-------------------------------------------------------------------------------- */ public class ScriptLogger { public void fatal(Object message) { if (logger_stack.size()>0) ((Category)(logger_stack.getFirst())).fatal(message); } // end fatal public void error(Object message) { if (logger_stack.size()>0) ((Category)(logger_stack.getFirst())).error(message); } // end error public void warn(Object message) { if (logger_stack.size()>0) ((Category)(logger_stack.getFirst())).warn(message); } // end warn public void info(Object message) { if (logger_stack.size()>0) { // check to make sure info is enabled first Category cat = (Category)(logger_stack.getFirst()); if (cat.isInfoEnabled()) cat.info(message); } // end if } // end info public void debug(Object message) { if (logger_stack.size()>0) { // check to make sure debug is enabled first Category cat = (Category)(logger_stack.getFirst()); if (cat.isDebugEnabled()) cat.debug(message); } // end if } // end debug } // end class ScriptLogger /*-------------------------------------------------------------------------------- * Static data members *-------------------------------------------------------------------------------- */ private static Category logger = Category.getInstance(ScriptManager.class); /*-------------------------------------------------------------------------------- * Attributes *-------------------------------------------------------------------------------- */ private BSFManager mgr; // Bean Scripting Framework manager private LinkedList registered_names; // stack of lists of registered names private LinkedList logger_stack; // stack of loggers for nested evaluation private LinkedList return_stack; // stack of return values /*-------------------------------------------------------------------------------- * Constructor *-------------------------------------------------------------------------------- */ public ScriptManager(String temp_dir) throws ScriptingException { // initialize the BSFManager mgr = new BSFManager(); mgr.setTempDir(temp_dir); if (BSFManager.isLanguageRegistered("javascript")) { // we need to so some fixing up to make Rhino work try { // ensure the engine is loaded, and do our fixup routine mgr.loadScriptingEngine("javascript"); fixupRhino(); } // end if catch (BSFException e) { // do nothing - we don't have JavaScript loaded } // end catch } // end if try { // declare beans used by the scripting engines mgr.declareBean("vlib",new MyScriptLibrary(),MyScriptLibrary.class); mgr.declareBean("logger",new ScriptLogger(),ScriptLogger.class); } // end try catch (BSFException e) { // translate the exception logger.error("bean declarations threw a BSFException",e); throw new ScriptingException("initialization error in ScriptManager",e); } // end catch // initialize the rest of the object registered_names = new LinkedList(); registered_names.addFirst(new HashSet()); logger_stack = new LinkedList(); return_stack = new LinkedList(); } // end constructor /*-------------------------------------------------------------------------------- * Internal operations *-------------------------------------------------------------------------------- */ private static void fixupRhino() { // What fixupRhino does is to turn off the fancy caching in the JavaScript engine. This is because // caching involves dynamic creation of classes on the fly, and, for some reason (probably because // of differing ClassLoader implementations), that doesn't work, whether Rhino is loaded in the // Web application or in the JRE extensions. When caching is turned off, Rhino can be loaded as // part of the web application, and it can see all the standard classes and all the Web application // classes. But we have to turn it off by manipulating it via the Reflection API, as, for all we know, // Rhino isn't even being loaded right now. (Though we have BSF load Rhino right away if it can, // to try and get this straightened out.) if (logger.isDebugEnabled()) logger.debug("Entered fixupRhino"); try { // get a pointer to the Context class Class klass = Class.forName("org.mozilla.javascript.Context"); // get a pointer to its setCachingEnabled method final Class[] parmtypes = { Boolean.TYPE }; Method m = klass.getMethod("setCachingEnabled",parmtypes); // invoke it to turn caching off! final Object[] parms = { Boolean.FALSE }; m.invoke(null,parms); // all done! if (logger.isDebugEnabled()) logger.debug("fixupRhino COMPLETED SUCCESSFULLY"); } // end try catch (Exception e) { // just log the exception and proceed if (logger.isDebugEnabled()) logger.debug("fixupRhino NOT COMPLETED",e); } // end catch } // end fixupRhino private static void translateReturnedObject(ScriptReturn sro) throws ScriptingException { Object obj = sro.get(); if (obj==null) return; // nothing to do Class klass = obj.getClass(); final String klassname = klass.getName(); // Special processing for JavaScript NativeError objects if (klassname.equals("org.mozilla.javascript.NativeError")) throw new ScriptingException("JavaScript runtime error: " + obj.toString()); } // end translateReturnedObject private void translateScriptException(String source, String lang, BSFException e) throws ScriptingException, ThrowableContent { if (logger.isDebugEnabled()) logger.debug("translateScriptException for " + source + " [" + lang + "]"); Throwable t = e.getTargetException(); if (t instanceof ScriptExit) { // that was a deliberate exit from the script - discard it and continue if (logger.isDebugEnabled()) logger.debug("ScriptExit - normal condition"); return; } // end if if (t instanceof ThrowableContent) { // treat ThrowableContent specially if (logger.isDebugEnabled()) logger.debug("ThrowableContent (" + t.getClass().getName() + ") - no need to process"); throw (ThrowableContent)t; } // end if if (e.getReason()==BSFException.REASON_EXECUTION_ERROR) { // throw a ScriptingException logger.error("Execution exception while running script",t); if (t!=null) throw new ScriptingException(source,lang,e.getMessage(),t); else throw new ScriptingException(source,lang,e.getMessage(),e); } // end if else if (e.getReason()==BSFException.REASON_OTHER_ERROR) { // usually indicates a compiler error in the scripting engine logger.error("Script compilation error",e); if (t!=null) logger.error("Root cause:",t); throw new ScriptingException(source,lang,e.getMessage()); } // end else if else { // unknown exception type here! logger.error("Unknown BSFException in script execution (reason " + e.getReason() + ")",e); if (t!=null) logger.error("Root cause:",t); throw new ScriptingException("unexpected exception while executing " + source + " [" + lang + "]",e); } // end else } // end translateScriptException /*-------------------------------------------------------------------------------- * External operations *-------------------------------------------------------------------------------- */ public void shutdown() { mgr.terminate(); registered_names.clear(); logger_stack.clear(); } // end shutdown public void exec(File file, String logger_name, ScriptReturn sro) throws ScriptingException, ThrowableContent { String filename = file.getAbsolutePath(); String lang; StringBuffer code; if (logger.isDebugEnabled()) logger.debug("Exec'ing " + filename + ", logger " + logger_name); try { // look up the scripting language for this file lang = mgr.getLangFromFilename(filename); code = IOUtil.loadText(file); if (logger.isDebugEnabled()) logger.debug("Code language is " + lang); } // end try catch (BSFException e) { // this may be thrown by getLangFromFilename throw new ScriptingException("unknown scripting language: file " + filename); } // end catch catch (IOException ie) { // this may be thrown by IOUtil.load throw new ScriptingException("unable to load script file: " + filename,ie); } // end catch if (logger_name!=null) // push a new logger onto the stack logger_stack.addFirst(Category.getInstance(logger_name)); if (sro!=null) return_stack.addFirst(sro); try { // execute the script! mgr.exec(lang,filename,1,1,code); } // end try catch (BSFException e) { // translate the exception thrown by the script translateScriptException(filename,lang,e); } // end catch finally { // pop the logger stack if necessary if (logger_name!=null) logger_stack.removeFirst(); if (sro!=null) return_stack.removeFirst(); } // end finally // if we've gotten this far, see if the returned object is actually an error that needs // translation before it can be presented to the world translateReturnedObject(sro); } // end exec public void register(String name, Object obj) { mgr.registerBean(name,obj); HashSet tbl = (HashSet)(registered_names.getFirst()); tbl.add(name); } // end register public void pushContext() { registered_names.addFirst(new HashSet()); } // end pushContext public void popContext() { HashSet tbl = (HashSet)(registered_names.removeFirst()); Iterator it = tbl.iterator(); while (it.hasNext()) mgr.unregisterBean((String)(it.next())); if (registered_names.isEmpty()) registered_names.addFirst(new HashSet()); } // end popContext public Object lookupObject(String name) { return mgr.lookupBean(name); } // end lookupObject } // end class ScriptManager