package org.jactr.scripting.javascript; /* * default logging */ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jactr.core.model.IModel; import org.jactr.core.production.VariableBindings; import org.jactr.core.production.condition.CannotMatchException; import org.jactr.core.production.condition.match.ExceptionMatchFailure; import org.jactr.core.utils.ModelerException; import org.jactr.scripting.IScriptableFactory; import org.jactr.scripting.ScriptSupport; import org.jactr.scripting.ScriptingManager; import org.jactr.scripting.condition.IConditionScript; import org.jactr.scripting.condition.ScriptExecutionFailure; import org.mozilla.javascript.Context; import org.mozilla.javascript.Function; import org.mozilla.javascript.JavaScriptException; import org.mozilla.javascript.Script; import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.ScriptableObject; import org.mozilla.javascript.WrappedException; public class JavascriptCondition implements IConditionScript { /** * Logger definition */ static private final transient Log LOGGER = LogFactory .getLog(JavascriptCondition.class); private final IScriptableFactory _factory; private final String _script; private final Script _compiledScript; private boolean _hasFiredCleanly = false; private boolean _cleanResult = false; public JavascriptCondition(String script, IScriptableFactory factory) { _factory = factory; _script = script; ScopeManager.getPublicScope(); Context cx = Context.enter(); try { _compiledScript = cx.compileString(_script, "ScriptableCondition", 0, null); } catch (Exception ioe) { Context.exit(); LOGGER.error("Error in scriptable condition", ioe); throw new ModelerException("Error in Scriptable ICondition", ioe, "double check your sytanx. The script parser detected an error"); } } private JavascriptCondition(String script, Script compiled, IScriptableFactory factory) { _script = script; _factory = factory; _compiledScript = compiled; } public void dispose() { // noop since we reuse this object in clone } public IScriptableFactory getFactory() { return _factory; } public IConditionScript clone(IModel model, VariableBindings variableBindings) throws CannotMatchException { return new JavascriptCondition(_script, _compiledScript, _factory); } public int bind(ScriptSupport scriptSupport, IModel model, VariableBindings variableBindings, boolean isIterative) throws CannotMatchException { try { if (!execute(scriptSupport, model, variableBindings)) throw new CannotMatchException(new ScriptExecutionFailure( "Script evaluated to false")); } catch (CannotMatchException cme) { if (!isIterative) throw cme; return 1; } return 0; } public String getScript() { return _script; } protected boolean execute(ScriptSupport scriptSupport, IModel model, VariableBindings variableBindings) throws CannotMatchException { /* * we cache the result if it has fired cleanly (no exceptions). This allows * us to avoid multiple calls during iterative instantiation process where * bind will be called repeatedly. */ if (_hasFiredCleanly) return _cleanResult; _cleanResult = false; // enter the context for the thread and get the shared scope for // the model.. try { Context cx = Context.enter(); Scriptable scope = ScopeManager.newScope(ScopeManager .getScopeForModel(model)); ScopeManager.defineVariable(scope, "jactr", scriptSupport); ScriptingManager .configureScripting(_factory, model, scriptSupport, scope); _compiledScript.exec(cx, scope); // let's get the function fire(model, prod, bindings) Object matches = ScriptableObject.getProperty(scope, "matches"); if (!(matches instanceof Function)) throw new CannotMatchException(new ScriptExecutionFailure( "Could not find function matches()")); // and fire that beatch boolean matched = false; Object[] args = {}; Object result = ((Function) matches).call(cx, scope, scope, args); try { matched = Context.toBoolean(result); _hasFiredCleanly = true; _cleanResult = matched; } catch (Exception e) { matched = false; } return matched; } catch (WrappedException we) { Throwable cause = we.getWrappedException(); // if CME is thrown from inside the script if (cause instanceof CannotMatchException) throw (CannotMatchException) cause; throw new CannotMatchException(new ExceptionMatchFailure(null, String.format("%s.script", variableBindings.get("=production")), cause)); } catch (JavaScriptException jse) { LOGGER.error("Error in scriptable condition", jse); throw new CannotMatchException( new ExceptionMatchFailure(null, String.format("%s.script syntax", variableBindings.get("=production")), jse)); } finally { Context.exit(); } } }