From 254ee6f47eebfc00462c10756a92066e82cc1a96 Mon Sep 17 00:00:00 2001 From: Andrew Branson Date: Tue, 21 Jun 2011 15:46:02 +0200 Subject: Initial commit --- source/com/c2kernel/scripting/Script.java | 438 ++++++++++++++++++++++++++++++ 1 file changed, 438 insertions(+) create mode 100755 source/com/c2kernel/scripting/Script.java (limited to 'source/com/c2kernel/scripting/Script.java') diff --git a/source/com/c2kernel/scripting/Script.java b/source/com/c2kernel/scripting/Script.java new file mode 100755 index 0000000..7b1c6db --- /dev/null +++ b/source/com/c2kernel/scripting/Script.java @@ -0,0 +1,438 @@ +package com.c2kernel.scripting; + +import java.io.StringReader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.w3c.dom.Text; +import org.xml.sax.InputSource; + +import com.c2kernel.common.ObjectNotFoundException; +import com.c2kernel.entity.agent.Job; +import com.c2kernel.entity.proxy.AgentProxy; +import com.c2kernel.entity.proxy.ItemProxy; +import com.c2kernel.utils.LocalObjectLoader; +import com.c2kernel.utils.Logger; +import com.ibm.bsf.BSFException; +import com.ibm.bsf.BSFManager; + +/************************************************************************** + * + * $Revision: 1.25 $ + * $Date: 2005/10/05 07:39:37 $ + * + * Copyright (C) 2003 CERN - European Organization for Nuclear Research + * All rights reserved. + **************************************************************************/ +public class Script +{ + Class mOutputClass; + String mOutputName; + String mScript = ""; + String mName; + String mVersion; + String mLang; + HashMap mInputParams = new HashMap(); + HashMap mAllInputParams = new HashMap(); + ArrayList mIncludes = new ArrayList(); + BSFManager scriptManager; + + /** + * Loads script xml and parses it for script source, parameters and output specifications. + * First tries to load the script from resource path /scriptFiles/scriptName_scriptVersion.xml + * If not found tries to find item at /desc/ScriptDesc/scriptName and load Viewpoint scriptVersion from it. + * + * For the specification of script xml, see the Script schema from resources. + * + * @param scriptName - name of the script + * @param scriptVersion - named version of the script (must be numbered viewpoint) + * @throws ScriptParsingException - when script not found (ScriptLoadingException) or xml is invalid (ScriptParsingException) + */ + public Script(String scriptName, int scriptVersion) throws ScriptingEngineException + { + this(scriptName, scriptVersion, new BSFManager()); + } + + public Script(String scriptName, int scriptVersion, BSFManager scriptManager) throws ScriptingEngineException + { + mName = scriptName; + mVersion = String.valueOf(scriptVersion); + if (scriptName.equals("")) return; + setScriptEnv(scriptManager); + setScript(mName, mVersion); + } + + /** + * Creates a script executor for the supplied expression, bypassing the xml parsing bit + * Output class is forced to an object. + */ + public Script(String lang, String expr, BSFManager scriptManager) throws ScriptingEngineException + { + setScriptEnv(scriptManager); + mName = ""; + mLang = lang; + mVersion = ""; + mOutputClass = Object.class; + mScript = expr; + } + + public Script(String lang, String expr) throws ScriptingEngineException + { + this(lang, expr, new BSFManager()); + } + + public Script(ItemProxy object, AgentProxy subject, Job job) throws ScriptingEngineException + { + this(job.getActPropString("ScriptName"), job.getActPropString("ScriptVersion") == null ? -1 : Integer.parseInt(job.getActPropString("ScriptVersion"))); + // set enviroment - this needs to be well documented for script developers + addInputParam("item", "com.c2kernel.entity.proxy.ItemProxy"); + setInputParamValue("item", object); + + addInputParam("agent", "com.c2kernel.entity.proxy.AgentProxy"); + setInputParamValue("agent", subject); + + addInputParam("job", "com.c2kernel.entity.agent.Job"); + setInputParamValue("job", job); + + setOutput("errors", "com.c2kernel.scripting.ErrorInfo"); + } + + public void setScript(String scriptName, String scriptVersion) throws ScriptingEngineException + { + try + { + mName = scriptName; + mVersion = scriptVersion; + parseScriptXML(LocalObjectLoader.getScript(scriptName, scriptVersion)); + } + catch (ObjectNotFoundException e) + { + throw new ScriptingEngineException("Script "+scriptName+" not found"); + } + } + + public void setScriptEnv(BSFManager manager) { + this.scriptManager = manager; + } + + /** + * Extracts script data from script xml. + * + * @param scriptXML + * @throws ScriptParsingException - when script is invalid + */ + private void parseScriptXML(String scriptXML) throws ScriptParsingException, ParameterException + { + Document scriptDoc = null; + + // get the DOM document from the XML + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + try + { + DocumentBuilder domBuilder = factory.newDocumentBuilder(); + scriptDoc = domBuilder.parse(new InputSource(new StringReader(scriptXML))); + } + catch (Exception ex) + { + throw new ScriptParsingException("Error parsing Script XML : " + ex.toString()); + } + + Element root = scriptDoc.getDocumentElement(); + NodeList scriptNodes = root.getChildNodes(); + for (int i = 0; i < scriptNodes.getLength(); i++) + { + Element currentParam; + String paramName; + + try + { + currentParam = (Element) scriptNodes.item(i); + } + catch (ClassCastException ex) + { + // not an element, skip + continue; + } + paramName = currentParam.getTagName(); + Logger.msg(9, "Script.parseScriptXML() - Found element " + paramName); + + // Process script parameters + + // New input parameter + if (paramName.equals("param")) + { + if (!(currentParam.hasAttribute("name") && currentParam.hasAttribute("type"))) + throw new ScriptParsingException("Script Input Param incomplete, must have name and type"); + addInputParam(currentParam.getAttribute("name"), currentParam.getAttribute("type")); + } + + //load output type + else if (paramName.equals("output")) + { + if (!currentParam.hasAttribute("type")) + throw new ScriptParsingException("Script Output declaration incomplete, must have type"); + setOutput(currentParam.getAttribute("name"), currentParam.getAttribute("type")); + } + + //load any included scripts + else if (paramName.equals("include")) + { + if (!(currentParam.hasAttribute("name") && currentParam.hasAttribute("version"))) + throw new ScriptParsingException("Script include declaration incomplete, must have name and version"); + String includeName = currentParam.getAttribute("name"); + String includeVersion = currentParam.getAttribute("version"); + try { + Script includedScript = new Script(includeName, Integer.parseInt(includeVersion), scriptManager); + mIncludes.add(includedScript); + for (Iterator iter = includedScript.getInputParams().values().iterator(); iter.hasNext();) { + Parameter includeParam = (Parameter) iter.next(); + addIncludedInputParam(includeParam.getName(), includeParam.getType()); + } + } catch (NumberFormatException e) { + throw new ScriptParsingException("Invalid version in imported script "+includeName+"_"+includeVersion); + } catch (ScriptingEngineException e) { + throw new ScriptParsingException("Error parsing imported script "+includeName+"_"+includeVersion+": "+e.getMessage()); + } + + + } + //load Script + else if (paramName.equals("script")) + { + if (!currentParam.hasAttribute("language")) + throw new ScriptParsingException("Script data incomplete, must have language"); + Logger.msg(6, "Script.parseScriptXML() - Script Language: " + currentParam.getAttribute("language")); + mLang = currentParam.getAttribute("language"); + + // get script source + NodeList scriptChildNodes = currentParam.getChildNodes(); + if (scriptChildNodes.getLength() != 1) + throw new ScriptParsingException("More than one child element found under script tag. Script characters may need escaping - suggest convert to CDATA section"); + if (scriptChildNodes.item(0) instanceof Text) + mScript = ((Text) scriptChildNodes.item(0)).getData(); + else + throw new ScriptParsingException("Child element of script tag was not text"); + Logger.msg(6, "Script.parseScriptXML() - script:" + mScript); + } + } + } + + protected void addInputParam(String name, String type) throws ParameterException + { + Parameter inputParam = new Parameter(name); + + try + { + inputParam.setType(Class.forName(type)); + } + catch (ClassNotFoundException ex) + { + throw new ParameterException("Input parameter " + inputParam.getName() + " specifies class " + type + " which was not found."); + } + + Logger.msg(6, "ScriptExecutor.parseScriptXML() - declared parameter " + name + " (" + type + ")"); + //add parameter to hashtable + mInputParams.put(inputParam.getName(), inputParam); + mAllInputParams.put(inputParam.getName(), inputParam); + + } + + protected void addIncludedInputParam(String name, Class type) throws ParameterException + { + // check if we already have it + if (mAllInputParams.containsKey(name)) { + Parameter existingParam = (Parameter)mAllInputParams.get(name); + // check the types match + if (existingParam.getType() == type) + return; // matches + else // error + throw new ParameterException("Parameter conflict. Parameter'"+name+"' is declared as " + +existingParam.getType().getName()+" is declared in another script as "+type.getName()); + } + + Parameter inputParam = new Parameter(name); + inputParam.setType(type); + + //add parameter to hashtable + mAllInputParams.put(inputParam.getName(), inputParam); + + } + + + protected void setOutput(String name, String type) throws ScriptParsingException + { + mOutputName = name; + + // set name to null if empty + if (mOutputName != null && mOutputName.equals("")) + mOutputName = null; + + try + { + Logger.msg(6, "Script.setOutput() - Output: " + name + " (" + type + ")"); + mOutputClass = Class.forName(type); + } + catch (ClassNotFoundException ex) + { + throw new ScriptParsingException("Output class "+type+" not found"); + } + + // set up the output object if named + // we declare this now so imported scripts using the same object will not overwrite each other during execution + if (mOutputName!=null) + try + { + Logger.msg(8, "Script.setOutput() - Initialising output bean '" + mOutputName + "'"); + Object emptyObject = mOutputClass.newInstance(); + scriptManager.declareBean(mOutputName, emptyObject, mOutputClass); + } + catch (Exception ex) + { + Logger.error(ex); + throw new ScriptParsingException("Error initialising output bean: "+ex.getMessage()); + } + + } + + /** + * Gets all declared parameters + * @return HashMap of String (name), com.c2kernel.scripting.Parameter (param) + * @see com.c2kernel.scripting.Parameter + */ + public HashMap getInputParams() + { + return mInputParams; + } + + /** + * Gets all declared parameters, including those of imported scripts + * @return HashMap of String (name), com.c2kernel.scripting.Parameter (param) + * @see com.c2kernel.scripting.Parameter + */ + public HashMap getAllInputParams() + { + return mAllInputParams; + } + + /** + * Submits an input parameter to the script. Must be declared by name and type in the script XML. + * + * @param name - input parameter name from the script xml + * @param value - object to use for this parameter + * @throws ParameterException - name not found or wrong type + */ + public void setInputParamValue(String name, Object value) throws ParameterException + { + Parameter param = (Parameter) mInputParams.get(name); + + if (!mAllInputParams.containsKey(name)) + throw new ParameterException("Parameter " + name + " not found in parameter list"); + + if (param != null) { // param is in this script + if (value.getClass() != param.getType()) + throw new ParameterException( + "Parameter " + name + " is wrong type \n" + "Required: " + param.getType().toString() + "\n" + "Supplied: " + value.getClass().toString()); + try { + scriptManager.declareBean(name, value, param.getType()); + Logger.msg(7, "Script.setInputParamValue() - " + name + ": " + value.toString()); + param.setInitialised(true); + } catch (BSFException ex) { + throw new ParameterException("Error initialising parameter '"+name+"' - "+ex.getMessage()); + } + } + + // pass param down to child scripts + for (Iterator iter = mIncludes.iterator(); iter.hasNext();) { + Script importScript = (Script) iter.next(); + importScript.setInputParamValue(name, value); + } + } + + /** + * Executes the script with the submitted parameters. All declared input parametes should have been set first. + * + * @return The return value depends on the way the output type was declared in the script xml. + * + * @throws ScriptingEngineException - input parameters weren't set, there was an error executing the script, or the output was invalid + */ + public Object execute() throws ScriptingEngineException + { + Object returnValue = null; + Object outputValue = null; + + // check input params + StringBuffer missingParams = new StringBuffer(); + for (Iterator iter = mInputParams.values().iterator(); iter.hasNext();) + { + Parameter thisParam = (Parameter) iter.next(); + if (!thisParam.getInitialised()) + missingParams.append(thisParam.getName()).append("\n"); + } + // croak if any missing + if (missingParams.length() > 0) + throw new ScriptingEngineException("Execution aborted, the following declared parameters were not set: \n" + missingParams.toString()); + + // execute the child scripts + for (Iterator iter = mIncludes.iterator(); iter.hasNext();) { + Script importScript = (Script) iter.next(); + importScript.execute(); + } + + + // run the script + try + { + Logger.msg(7, "Script.execute() - Executing script"); + scriptManager.setDebug(Logger.doLog(8)); + returnValue = scriptManager.eval(mLang, mName, 0, 0, mScript); + Logger.msg(8, "Script.execute() - script returned \"" + returnValue + "\""); + if (mOutputName != null) + { + // retrieve the value from the registered output bean + outputValue = scriptManager.lookupBean(mOutputName); + Logger.msg(8, "Script.execute() - output bean value: \"" + outputValue + "\""); + } + } + catch (Exception ex) + { + throw new ScriptingEngineException("Error executing script: " + ex.getMessage()); + } + + // if no output class specified, return null + if (mOutputClass == null) + return null; + + // if output name not specified, then check the return value against the output class + if (mOutputName == null && returnValue != null && !(mOutputClass.isInstance(returnValue))) + throw new ScriptingEngineException( + "Script return value was not null or instance of " + mOutputClass.getName() + ", it was " + returnValue.getClass().getName()); + + // return the output, or the return value if output name isn't defined + if (mOutputName != null) + return outputValue; + else + return returnValue; + } + + /** + * Resets the scripting enviroment, clearing all state and parameters for another execution. + */ + public void reset() + { + for (Iterator iter = mInputParams.values().iterator(); iter.hasNext();) + ((Parameter) iter.next()).setInitialised(false); + scriptManager = new BSFManager(); + } +} -- cgit v1.2.3