Repository: commons-scxml Updated Branches: refs/heads/master 031f8ff32 -> 1b91a77c1
SCXML-245: Reimplement Nashorn Javascript Evaluator - See: https://issues.apache.org/jira/browse/SCXML-245 for details Project: http://git-wip-us.apache.org/repos/asf/commons-scxml/repo Commit: http://git-wip-us.apache.org/repos/asf/commons-scxml/commit/1b91a77c Tree: http://git-wip-us.apache.org/repos/asf/commons-scxml/tree/1b91a77c Diff: http://git-wip-us.apache.org/repos/asf/commons-scxml/diff/1b91a77c Branch: refs/heads/master Commit: 1b91a77c1a41a57799da93ce0ba3a4b58760148e Parents: 031f8ff Author: Ate Douma <[email protected]> Authored: Sat Jan 2 16:11:30 2016 +0100 Committer: Ate Douma <[email protected]> Committed: Sat Jan 2 16:11:30 2016 +0100 ---------------------------------------------------------------------- pom.xml | 9 + .../scxml2/env/javascript/JSBindings.java | 277 ++------ .../scxml2/env/javascript/JSContext.java | 15 +- .../scxml2/env/javascript/JSEvaluator.java | 192 ++++-- .../scxml2/env/javascript/JSFunctions.java | 50 -- .../scxml2/env/javascript/init_global.js | 142 ++++ .../scxml2/env/javascript/JSBindingsTest.java | 669 ------------------- .../env/javascript/JavaScriptEngineTest.java | 108 +-- .../org/apache/commons/scxml2/w3c/tests.xml | 14 +- 9 files changed, 390 insertions(+), 1086 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/commons-scxml/blob/1b91a77c/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index 06d3a65..a6bf606 100644 --- a/pom.xml +++ b/pom.xml @@ -204,6 +204,15 @@ <build> <sourceDirectory>src/main/java</sourceDirectory> + <resources> + <resource> + <filtering>false</filtering> + <directory>src/main/java</directory> + <includes> + <include>**/*.js</include> + </includes> + </resource> + </resources> <testSourceDirectory>src/test/java</testSourceDirectory> <testResources> <testResource> http://git-wip-us.apache.org/repos/asf/commons-scxml/blob/1b91a77c/src/main/java/org/apache/commons/scxml2/env/javascript/JSBindings.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/scxml2/env/javascript/JSBindings.java b/src/main/java/org/apache/commons/scxml2/env/javascript/JSBindings.java index 6b7142f..dc9ab7c 100644 --- a/src/main/java/org/apache/commons/scxml2/env/javascript/JSBindings.java +++ b/src/main/java/org/apache/commons/scxml2/env/javascript/JSBindings.java @@ -18,339 +18,154 @@ package org.apache.commons.scxml2.env.javascript; import java.util.Collection; -import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.script.Bindings; -import javax.script.SimpleBindings; import org.apache.commons.scxml2.Context; /** - * Wrapper class for the JDK Javascript engine Bindings class that extends the - * wrapped Bindings to search the SCXML context for variables and predefined - * functions that do not exist in the wrapped Bindings. - * + * JDK Javascript engine Bindings class that delegates to a SCXML context */ public class JSBindings implements Bindings { - private static final String NASHORN_GLOBAL = "nashorn.global"; - - // INSTANCE VARIABLES - - private Bindings bindings; - private Context context; - - // CONSTRUCTORS + private JSContext context; /** - * Initialises the internal Bindings delegate and SCXML context. + * Initialise the Bindings * - * @param context SCXML Context to use for script variables. - * @param bindings Javascript engine bindings for Javascript variables. + * @param jsContext initial SCXML Context to use for script variables. * - * @throws IllegalArgumentException Thrown if either <code>context</code> - * or <code>bindings</code> is <code>null</code>. + * @throws IllegalArgumentException Thrown if <code>jsContext</code> is <code>null</code>. * */ - public JSBindings(Context context, Bindings bindings) { - // ... validate - - if (context == null) { - throw new IllegalArgumentException("Invalid SCXML context"); - } + public JSBindings(JSContext jsContext) { + setContext(jsContext); + } - if (bindings == null) { - throw new IllegalArgumentException("Invalid script Bindings"); + /** + * Set or update the SCXML context delegate + * + * @param jsContext the SCXML context to use for script variables. + * @throws IllegalArgumentException Thrown if <code>jsContext</code> is <code>null</code>. + */ + public void setContext(JSContext jsContext) { + if (jsContext == null) { + throw new IllegalArgumentException("SCXML context is required"); } - - // ... initialise - - this.bindings = bindings; - this.context = context; + this.context = jsContext; } - // INSTANCE METHODS - /** - * Returns <code>true</code> if the wrapped Bindings delegate - * or SCXML context contains a variable identified by - * <code>key</code>. - * + * Returns <code>true</code> if the SCXML context contains a variable identified by <code>key</code>. */ @Override public boolean containsKey(Object key) { - if (hasGlobalBindings() && getGlobalBindings().containsKey(key)) { - return true; - } - - if (bindings.containsKey(key)) { - return true; - } - return context.has(key.toString()); } /** - * Returns a union of the wrapped Bindings entry set and the - * SCXML context entry set. - * <p> - * NOTE: doesn't seem to be invoked ever. Not thread-safe. - * + * Returns the SCXML context key set */ @Override public Set<String> keySet() { - Set<String> keys = new HashSet<String>(); - - keys.addAll(context.getVars().keySet()); - keys.addAll(bindings.keySet()); - - if (hasGlobalBindings()) { - keys.addAll(getGlobalBindings().keySet()); - } - - return keys; + return context.getVars().keySet(); } /** - * Returns the combined size of the wrapped Bindings entry set and the - * SCXML context entry set. - * <p> - * NOTE: doesn't seem to be invoked ever so not sure if it works in - * context. Not thread-safe. - * + * Returns the size of the SCXML context size. */ @Override public int size() { - Set<String> keys = new HashSet<String>(); - - keys.addAll(context.getVars().keySet()); - keys.addAll(bindings.keySet()); - - if (hasGlobalBindings()) { - keys.addAll(getGlobalBindings().keySet()); - } - - return keys.size(); + return context.getVars().size(); } /** - * Returns <code>true</code> if the wrapped Bindings delegate - * or SCXML context contains <code>value</code>. - * <p> - * NOTE: doesn't seem to be invoked ever so not sure if it works in - * context. Not thread-safe. + * Returns <code>true</code> if the SCXML context contains <code>value</code>. */ @Override public boolean containsValue(Object value) { - if (hasGlobalBindings() && getGlobalBindings().containsValue(value)) { - return true; - } - - if (bindings.containsValue(value)) { - return true; - } - return context.getVars().containsValue(value); } /** - * Returns a union of the wrapped Bindings entry set and the - * SCXML context entry set. - * <p> - * NOTE: doesn't seem to be invoked ever so not sure if it works in - * context. Not thread-safe. + * Returns the SCXML context entry set. */ @Override public Set<Map.Entry<String,Object>> entrySet() { - return union().entrySet(); + return context.getVars().entrySet(); } /** - * Returns a union of the wrapped Bindings value list and the - * SCXML context value list. - * <p> - * NOTE: doesn't seem to be invoked ever so not sure if it works in - * context. Not thread-safe. + * Returns the SCXML context values. */ @Override public Collection<Object> values() { - return union().values(); + return context.getVars().values(); } /** - * Returns a <code>true</code> if both the Bindings delegate and - * the SCXML context maps are empty. - * <p> - * NOTE: doesn't seem to be invoked ever so not sure if it works in - * context. Not thread-safe. + * Returns <code>true</code> if the SCXML context is empty. */ @Override public boolean isEmpty() { - if (hasGlobalBindings() && !getGlobalBindings().isEmpty()) { - return false; - } - - if (!bindings.isEmpty()) { - return false; - } - return context.getVars().isEmpty(); } /** - * Returns the value from the wrapped Bindings delegate - * or SCXML context contains identified by <code>key</code>. - * + * Returns the value from the SCXML context identified by <code>key</code>. */ @Override public Object get(Object key) { - // nashorn.global should be retrieved from the bindings, not from context. - if (NASHORN_GLOBAL.equals(key)) { - return bindings.get(key); - } - - if (hasGlobalBindings() && getGlobalBindings().containsKey(key)) { - return getGlobalBindings().get(key); - } - - if (bindings.containsKey(key)) { - return bindings.get(key); - } - return context.get(key.toString()); } /** * The following delegation model is used to set values: * <ol> - * <li>Delegates to {@link Context#set(String,Object)} if the - * {@link Context} contains the key (name), else</li> - * <li>Delegates to the wrapped {@link Bindings#put(String, Object)} - * if the {@link Bindings} contains the key (name), else</li> + * <li>Delegates to {@link Context#set(String,Object)} if the Context contains the key (name), else</li> * <li>Delegates to {@link Context#setLocal(String, Object)}</li> * </ol> - * + * @param name The variable name + * @param value The variable value */ @Override public Object put(String name, Object value) { - Object old = context.get(name); - - // nashorn.global should be put into the bindings, not into context. - if (NASHORN_GLOBAL.equals(name)) { - return bindings.put(name, value); - } else if (context.has(name)) { + Object old = null; + if (context.has(name)) { + old = context.get(name); context.set(name, value); - } else if (bindings.containsKey(name)) { - return bindings.put(name, value); - } else if (hasGlobalBindings() && getGlobalBindings().containsKey(name)) { - return getGlobalBindings().put(name, value); } else { context.setLocal(name, value); } - return old; } /** - * Delegates to the wrapped Bindings <code>putAll</code> method i.e. does - * not store variables in the SCXML context. - * <p> - * NOTE: doesn't seem to be invoked ever so not sure if it works in - * context. Not thread-safe. + * Sets all entries in the provided map via {@link #put(String, Object)} + * @param toMerge the map of variables to merge */ @Override public void putAll(Map<? extends String, ? extends Object> toMerge) { - bindings.putAll(toMerge); + for (Map.Entry<? extends String, ? extends Object> entry : toMerge.entrySet()) { + put(entry.getKey(), entry.getValue()); + } } /** - * Removes the object from the wrapped Bindings instance or the contained - * SCXML context. Not entirely sure about this implementation but it - * follows the philosophy of using the Javascript Bindings as a child context - * of the SCXML context. - * <p> - * NOTE: doesn't seem to be invoked ever so not sure if it works in - * context. Not thread-safe. + * Removes the named variable from the contained SCXML context. + * @param name the variable name */ @Override - public Object remove(Object key) { - if (hasGlobalBindings() && getGlobalBindings().containsKey(key)) { - getGlobalBindings().remove(key); - } - - if (bindings.containsKey(key)) { - return bindings.remove(key); - } - - if (context.has(key.toString())) { - return context.getVars().remove(key); - } - - return Boolean.FALSE; + public Object remove(Object name) { + return context.getVars().remove(name); } /** - * Delegates to the wrapped Bindings <code>clear</code> method. Does not clear - * the SCXML context. - * <p> - * NOTE: doesn't seem to be invoked ever so not sure if it works in - * context. Not thread-safe. + * Does nothing - never invoked anyway */ @Override public void clear() { - bindings.clear(); - } - - /** - * Internal method to create a union of the SCXML context and the Javascript - * Bindings. Does a heavyweight copy - and so far only invoked by the - * not used methods. - */ - private Bindings union() { - Bindings set = new SimpleBindings(); - - set.putAll(context.getVars()); - - for (String key : bindings.keySet()) { - set.put(key, bindings.get(key)); - } - - if (hasGlobalBindings()) { - for (String key : getGlobalBindings().keySet()) { - set.put(key, getGlobalBindings().get(key)); - } - } - - return set; - } - - /** - * Return true if a global bindings (i.e. nashorn Global instance) was ever set by the script engine. - * <p> - * Note: because the global binding can be set by the script engine when evaluating a script, we should - * check or retrieve the global binding whenever needed instead of initialization time. - * </p> - * @return true if a global bindings (i.e. nashorn Global instance) was ever set by the script engine - */ - protected boolean hasGlobalBindings() { - if (bindings.containsKey(NASHORN_GLOBAL)) { - return true; - } - - return false; - } - - /** - * Return the global bindings (i.e. nashorn Global instance) set by the script engine if existing. - * @return the global bindings (i.e. nashorn Global instance) set by the script engine, or null if not existing. - */ - protected Bindings getGlobalBindings() { - if (bindings.containsKey(NASHORN_GLOBAL)) { - return (Bindings) bindings.get(NASHORN_GLOBAL); - } - - return null; } } http://git-wip-us.apache.org/repos/asf/commons-scxml/blob/1b91a77c/src/main/java/org/apache/commons/scxml2/env/javascript/JSContext.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/scxml2/env/javascript/JSContext.java b/src/main/java/org/apache/commons/scxml2/env/javascript/JSContext.java index d63d25a..f1b063c 100644 --- a/src/main/java/org/apache/commons/scxml2/env/javascript/JSContext.java +++ b/src/main/java/org/apache/commons/scxml2/env/javascript/JSContext.java @@ -27,26 +27,21 @@ import org.apache.commons.scxml2.env.SimpleContext; * extension of SimpleContext that has been implemented to reduce the impact * if the JSEvaluator requires additional functionality at a later stage. * <p> - * Could easily be dispensed with. - * */ public class JSContext extends SimpleContext { /** Serial version UID. */ private static final long serialVersionUID = 1L; - // CONSTRUCTORS - /** - * Default constructor - just invokes the SimpleContext default - * constructor. + * Default constructor - just invokes the SimpleContext default constructor. */ public JSContext() { super(); } /** - * Constructor with initial vars. + * Constructor with initial vars - Just invokes the identical SimpleContext constructor. * @param parent The parent context * @param initialVars The initial set of variables. */ @@ -55,15 +50,11 @@ public class JSContext extends SimpleContext { } /** - * Child constructor. Just invokes the identical SimpleContext - * constructor. - * + * Child constructor - Just invokes the identical SimpleContext constructor. * @param parent Parent context for this context. - * */ public JSContext(final Context parent) { super(parent); } - } http://git-wip-us.apache.org/repos/asf/commons-scxml/blob/1b91a77c/src/main/java/org/apache/commons/scxml2/env/javascript/JSEvaluator.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/scxml2/env/javascript/JSEvaluator.java b/src/main/java/org/apache/commons/scxml2/env/javascript/JSEvaluator.java index 2f9641d..d71933e 100644 --- a/src/main/java/org/apache/commons/scxml2/env/javascript/JSEvaluator.java +++ b/src/main/java/org/apache/commons/scxml2/env/javascript/JSEvaluator.java @@ -17,14 +17,17 @@ package org.apache.commons.scxml2.env.javascript; +import java.io.IOException; import java.util.UUID; -import java.util.regex.Pattern; import javax.script.Bindings; import javax.script.ScriptContext; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import javax.script.SimpleScriptContext; +import org.apache.commons.io.IOUtils; import org.apache.commons.scxml2.Context; import org.apache.commons.scxml2.Evaluator; import org.apache.commons.scxml2.EvaluatorProvider; @@ -34,11 +37,21 @@ import org.apache.commons.scxml2.env.EffectiveContextMap; import org.apache.commons.scxml2.model.SCXML; /** - * Embedded JavaScript expression evaluator for SCXML expressions. This - * implementation is a just a 'thin' wrapper around the Javascript engine in - * JDK 8. + * Embedded JavaScript expression evaluator for SCXML expressions using the JDK 8+ Nashorn Script Engine. + * <p> + * Each JSEvaluator maintains a single {@link ScriptContext} instance to be used for only a single SCXML instance as + * the Nashorn global state is shared through the {@link ScriptContext#ENGINE_SCOPE} binding. + * </p> + * <p>Sharing and reusing JSEvaluator instances for multiple SCXML instances therefore should <em>not</em> be done.</p> + * <p> + * As the JDK Script Engine state is <em>not</em> serializable, and neither are Javascript <code>native</code> Objects, + * the {@link ScriptContext} state is <em>not</em> retained during serialization (transient). + * </p> + * <p> + * SCXML instance (de)serialization using the javascript language therefore only will work reliably as long as no + * Javascript native Objects are used/stored in the context nor (other) modifications are made to the Nashorn global state. + * </p> */ - public class JSEvaluator extends AbstractBaseEvaluator { /** @@ -66,33 +79,98 @@ public class JSEvaluator extends AbstractBaseEvaluator { } } + private static final String SCXML_SYSTEM_CONTEXT = "_scxmlSystemContext"; + /** Error message if evaluation context is not a JexlContext. */ private static final String ERR_CTX_TYPE = "Error evaluating JavaScript " + "expression, Context must be a org.apache.commons.scxml2.env.javascript.JSContext"; - /** Pattern for recognizing the SCXML In() special predicate. */ - private static final Pattern IN_FN = Pattern.compile("In\\("); + /** shared singleton Nashorn ScriptEngine **/ + private static ScriptEngine engine; - // INSTANCE VARIABLES + /** Nashorn Global initialization script, loaded from <code>init_global.js</code> classpath resource */ + private static String initGlobalsScript; - private transient ScriptEngineManager factory; - - // CONSTRUCTORS + /** ScriptContext for a single SCXML instance (JSEvaluator also cannot be shared between SCXML instances) */ + private transient ScriptContext scriptContext; /** - * Initialises the internal Javascript engine factory. + * Initialize the singleton Javascript ScriptEngine to be used with a separate ScriptContext for each SCXML instance + * not sharing their global scope, see {@link #getScriptContext(JSContext)}. + * <p> + * The SCXML required protected system variables and (possible) other Javascript global initializations are defined + * in a <code>init_global.js</code> script which is pre-loaded as (classpath) resource, to be executed once during + * initialization of a new Javascript (Nashorn) Global. + * </p> */ - public JSEvaluator() { - factory = new ScriptEngineManager(); + protected synchronized static void initEngine() { + if (engine == null) { + engine = new ScriptEngineManager().getEngineByName("JavaScript"); + try { + initGlobalsScript = IOUtils.toString(JSEvaluator.class.getResourceAsStream("init_global.js"), "UTF-8"); + } + catch (IOException ioe) { + throw new RuntimeException("Failed to load init_global.js from classpath", ioe); + } + } } - // INSTANCE METHODS + /** + * Get the singleton ScriptEngine, initializing it on first access + * @return The ScriptEngine + */ + protected ScriptEngine getEngine() { + if (engine == null) { + initEngine(); + } + return engine; + } - protected ScriptEngineManager getFactory() { - if (factory == null) { - factory = new ScriptEngineManager(); + /** + * Get the current ScriptContext or create a new one. + * <p> + * The ScriptContext is (to be) shared across invocations for the same SCXML instance as it holds the Javascript 'global' + * context. + * </p> + * <p> + * The ScriptContext is using a {@link ScriptContext#ENGINE_SCOPE} as provided by the engine, which in case of Nashorn + * is bound to the Javscript global context. Note: do <em>not</em> confuse this with the {@link ScriptContext#GLOBAL_SCOPE} binding. + * </p> + * <p>For a newly created ScriptContext (and thus a new Javascript global context), the Javascript global context is + * initialized with the required and protected SCXML system variables and builtin In() operator via the + * <code>init_global.js</code> script, loaded as classpath resource.</p> + * <p> + * The SCXML system variables are bound as <code>"_scxmlSystemContext"</code> variable in the ENGINE_SCOPE + * as needed for the <code>init_global.js</code> script in the global context. + * This variable is bound to the ENGINE_SCOPE to ensure it cannot be 'shadowed' by an overriding variable assignment. + * </p> + * The provided SCXML Context variables are bound via the GLOBAL_SCOPE using a {@link JSBindings} wrapper for each + * invocation. + * </p> + * <p> + * As the GLOBAL_SCOPE SCXML context variables <em>can</em> be overridden, which will result in new 'shadow' + * variables in the ENGINE_SCOPE, as well as new variables can be added to the ENGINE_SCOPE during script evaluation, + * after script execution all ENGINE_SCOPE variables (except the <code>"_scxmlSystemContext"</code> variable) must be + * copied/merged into the SCXML context to synchronize the SCXML context. + * </p> + * @param jsContext The current SCXML context + * @return The SCXML instance shared ScriptContext + * @throws ScriptException Thrown if the initialization of the Global Javascript engine itself failed + */ + protected ScriptContext getScriptContext(JSContext jsContext) throws ScriptException { + if (scriptContext == null) { + scriptContext = new SimpleScriptContext(); + scriptContext.setBindings(getEngine().createBindings(), ScriptContext.ENGINE_SCOPE); + scriptContext.setBindings(new JSBindings(jsContext), ScriptContext.GLOBAL_SCOPE); + scriptContext.getBindings(ScriptContext.ENGINE_SCOPE).put(SCXML_SYSTEM_CONTEXT, jsContext.getSystemContext().getVars()); + getEngine().eval(initGlobalsScript, scriptContext); + } + else { + // ensure updated / replaced SystemContext is used (like after SCXML instance go/reset) + scriptContext.getBindings(ScriptContext.ENGINE_SCOPE).put(SCXML_SYSTEM_CONTEXT, jsContext.getSystemContext().getVars()); + ((JSBindings)scriptContext.getBindings(ScriptContext.GLOBAL_SCOPE)).setContext(jsContext); } - return factory; + return scriptContext; } @Override @@ -121,50 +199,32 @@ public class JSEvaluator extends AbstractBaseEvaluator { } /** - * Evaluates the expression using a new Javascript engine obtained from - * factory instantiated in the constructor. The engine is supplied with - * a new JSBindings that includes the SCXML Context and SCXML builtin - * <code>In()</code> function is replaced with an equivalent internal - * Javascript function. - * + * Evaluates a Javascript expression using an SCXML instance shared {@link #getScriptContext(JSContext)}. + * <p> + * After evaluation all the resulting Javascript Global context (in {@link ScriptContext#ENGINE_SCOPE} are first + * copied/merged back into the SCXML context, before the evaluation result (if any) is returned. + * </p> * @param context SCXML context. * @param expression Expression to evaluate. - * * @return Result of expression evaluation or <code>null</code>. - * - * @throws SCXMLExpressionException Thrown if the expression was invalid. + * @throws SCXMLExpressionException Thrown if the expression was invalid or the execution raised an error itself. */ @Override public Object eval(Context context, String expression) throws SCXMLExpressionException { if (expression == null) { return null; } - if (!(context instanceof JSContext)) { throw new SCXMLExpressionException(ERR_CTX_TYPE); } try { - JSContext effectiveContext = getEffectiveContext((JSContext) context); - - // ... initialize - ScriptEngine engine = getFactory().getEngineByName("JavaScript"); - Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); - - // ... replace built-in functions - String jsExpression = IN_FN.matcher(expression).replaceAll("_builtin.In("); - - // ... evaluate - JSBindings jsBindings = new JSBindings(effectiveContext, bindings); - jsBindings.put("_builtin", new JSFunctions(effectiveContext)); - - Object ret = engine.eval(jsExpression, jsBindings); - - // copy global bindings attributes to context, so callers may get access to the evaluated variables. - copyGlobalBindingsToContext(jsBindings, effectiveContext); - + JSContext effectiveContext = getEffectiveContext((JSContext)context); + ScriptContext scriptContext = getScriptContext(effectiveContext); + Object ret = getEngine().eval(expression, scriptContext); + // copy Javascript global variables to SCXML context. + copyJavascriptGlobalsToScxmlContext(scriptContext.getBindings(ScriptContext.ENGINE_SCOPE), effectiveContext); return ret; - } catch (Exception x) { throw new SCXMLExpressionException("Error evaluating ['" + expression + "'] " + x); } @@ -177,10 +237,9 @@ public class JSEvaluator extends AbstractBaseEvaluator { * @param context SCXML context. * @param expression Expression to evaluate. * - * @return Boolean or <code>null</code>. + * @return Boolean casted result. * - * @throws SCXMLExpressionException Thrown if the expression was invalid or did - * not return a boolean. + * @throws SCXMLExpressionException Thrown if the expression was invalid. */ @Override public Boolean evalCond(Context context, String expression) throws SCXMLExpressionException { @@ -202,11 +261,7 @@ public class JSEvaluator extends AbstractBaseEvaluator { } /** - * Executes the script using a new Javascript engine obtained from - * factory instantiated in the constructor. The engine is supplied with - * a new JSBindings that includes the SCXML Context and SCXML builtin - * <code>In()</code> function is replaced with an equivalent internal - * Javascript function. + * Executes the Javascript script using the <code>eval()</code> method * * @param ctx SCXML context. * @param script Script to execute. @@ -225,8 +280,8 @@ public class JSEvaluator extends AbstractBaseEvaluator { * current state to document root, child has priority over parent * in scoping rules. * - * @param nodeCtx The JexlContext for this state. - * @return The effective JexlContext for the path leading up to + * @param nodeCtx The JSContext for this state. + * @return The effective JSContext for the path leading up to * document root. */ protected JSContext getEffectiveContext(final JSContext nodeCtx) { @@ -234,18 +289,19 @@ public class JSEvaluator extends AbstractBaseEvaluator { } /** - * Copy the global Bindings (i.e. nashorn Global instance) attributes to {@code jsContext} - * in order to make sure all the new global variables set by the JavaScript engine after evaluation + * Copy the Javscript global context (i.e. nashorn Global instance) variables to SCXML {@code jsContext} + * in order to make sure all the new global variables set by the JavaScript engine after evaluation are * available from {@link JSContext} instance as well. - * @param jsBindings - * @param jsContext + * <p>Note: the internal <code>"_scxmlSystemContext</code> variable is always skipped.</p> + * @param global The Javascript Bindings holding the Javascript Global context variables + * @param jsContext The SCXML context to copy/merge the variables into */ - private void copyGlobalBindingsToContext(final JSBindings jsBindings, final JSContext jsContext) { - Bindings globalBindings = jsBindings.getGlobalBindings(); - - if (globalBindings != null) { - for (String key : globalBindings.keySet()) { - jsContext.set(key, globalBindings.get(key)); + private void copyJavascriptGlobalsToScxmlContext(final Bindings global, final JSContext jsContext) { + if (global != null) { + for (String key : global.keySet()) { + if (!SCXML_SYSTEM_CONTEXT.equals(key)) { + jsContext.set(key, global.get(key)); + } } } } http://git-wip-us.apache.org/repos/asf/commons-scxml/blob/1b91a77c/src/main/java/org/apache/commons/scxml2/env/javascript/JSFunctions.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/scxml2/env/javascript/JSFunctions.java b/src/main/java/org/apache/commons/scxml2/env/javascript/JSFunctions.java deleted file mode 100644 index 45771a0..0000000 --- a/src/main/java/org/apache/commons/scxml2/env/javascript/JSFunctions.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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.commons.scxml2.env.javascript; - -import java.io.Serializable; - -import org.apache.commons.scxml2.Builtin; -import org.apache.commons.scxml2.Context; - -/** - * Custom Javascript engine function providing the SCXML In() predicate . - */ -public class JSFunctions implements Serializable { - - /** - * The context currently in use for evaluation. - */ - private Context ctx; - - /** - * Creates a new instance, wraps the context. - * @param ctx the context in use - */ - public JSFunctions(Context ctx) { - this.ctx = ctx; - } - - /** - * Provides the SCXML standard In() predicate for SCXML documents. - * @param state The State ID to compare with - * @return true if this state is currently active - */ - public boolean In(final String state) { - return Builtin.isMember(ctx, state); - } -} http://git-wip-us.apache.org/repos/asf/commons-scxml/blob/1b91a77c/src/main/java/org/apache/commons/scxml2/env/javascript/init_global.js ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/scxml2/env/javascript/init_global.js b/src/main/java/org/apache/commons/scxml2/env/javascript/init_global.js new file mode 100644 index 0000000..b4cb4fc --- /dev/null +++ b/src/main/java/org/apache/commons/scxml2/env/javascript/init_global.js @@ -0,0 +1,142 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ +/* + Define global and protected SCXML system properties and builtin functions, + all delegating to _scxmlSystemContext variables map provided as a ScriptEngine binding. + The _event object is wrapped in additional (frozen) _scxmlEvent property to provide ECMAScript object semantics. + */ +Object.defineProperties(this, { + // common method to throw 'protected' error + "_scxmlProtected": + { + get: function() { + return function(name) { + throw new Error(name+" is a protected SCXML system property") + } + } + }, + "_name": { + get: function () { + return _scxmlSystemContext._name + }, + set: function () { + _scxmlProtected("_name") + } + }, + "_sessionid": + { + get: function() { + return _scxmlSystemContext._sessionid + }, + set: function() { + _scxmlProtected("_sessionid") + } + }, + "_ioprocessors": + { + get: function() { + return _scxmlSystemContext._ioprocessors + }, + set: function() { + _scxmlProtected("_ioprocessors") + } + }, + // extra wrapper object needed for _event wrapping as defining this inline + // on _event.get method somehow doesn't work properly + "_scxmlEvent": + { + value: { + get name() { + return _scxmlSystemContext._event.name||undefined + }, + set name(val) { + _scxmlProtected("_event.name") + }, + get type() { + return _scxmlSystemContext._event.type||undefined + }, + set type(val) { + _scxmlProtected("_event.type") + }, + get sendid() { + return _scxmlSystemContext._event.sendid||undefined + }, + set sendid(val) { + _scxmlProtected("_event.sendid") + }, + get orgin() { + return _scxmlSystemContext._event.orgin||undefined + }, + set origin(val) { + _scxmlProtected("_event.origin") + }, + get origintype() { + return _scxmlSystemContext._event.orgintype||undefined + }, + set origintype(val) { + _scxmlProtected("_event.origintype") + }, + get invokeid() { + return _scxmlSystemContext._event.invokeid||undefined + }, + set invokeid(val) { + _scxmlProtected("_event.invokeid") + }, + get data() { + return _scxmlSystemContext._event.data||undefined + }, + set data(val) { + _scxmlProtected("_event.data") + } + }, + writable : false, + configurable : false, + enumeratable : false, + }, + "_event": + { + get: function() { + return _scxmlSystemContext._event ? _scxmlEvent : undefined; + }, + set: function() { + _scxmlProtected("_event") + } + }, + "_x": + { + get: function() { + return _scxmlSystemContext._x + }, + set: function() { + _scxmlProtected("_x") + } + }, + // required SCXML builtin In() predicate + "In": + { + get: function() { + return function(state) { + return _scxmlSystemContext._x.status.isInState(state) + } + }, + set: function() { + _scxmlProtected("_In()") + } + } +}); +// ensure extra _scxmlEvent wrapper object is deep protected +Object.freeze(_scxmlEvent); http://git-wip-us.apache.org/repos/asf/commons-scxml/blob/1b91a77c/src/test/java/org/apache/commons/scxml2/env/javascript/JSBindingsTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/commons/scxml2/env/javascript/JSBindingsTest.java b/src/test/java/org/apache/commons/scxml2/env/javascript/JSBindingsTest.java deleted file mode 100644 index 64447f9..0000000 --- a/src/test/java/org/apache/commons/scxml2/env/javascript/JSBindingsTest.java +++ /dev/null @@ -1,669 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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.commons.scxml2.env.javascript; - -import java.util.AbstractMap; -import java.util.HashMap; -import java.util.Map; - -import org.apache.commons.scxml2.Context; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import javax.script.Bindings; -import javax.script.SimpleBindings; - -/** - * JUnit 3 test case for the JSBinding implementation that imports - * SCXML context variables into a JavaScript bindings. Includes tests - * for: - * <ul> - * <li> constructor - * </ul> - */ -public class JSBindingsTest { - // TEST CONSTANTS - - private static final Map.Entry<String,Object> KOALA = new AbstractMap.SimpleEntry<String,Object>("bear","koala"); - private static final Map.Entry<String,Object> GRIZZLY = new AbstractMap.SimpleEntry<String,Object>("bear","grizzly"); - private static final Map.Entry<String,Object> FELIX = new AbstractMap.SimpleEntry<String,Object>("cat", "felix"); - private static final Map.Entry<String,Object> ROVER = new AbstractMap.SimpleEntry<String,Object>("dog", "rover"); - - // TEST VARIABLES - - // TEST SETUP - - /** - * Creates and initializes an SCXML data model in the context. - */ - @Before - public void setUp() throws Exception { - } - - // CLASS METHODS - - /** - * Stand-alone test runtime. - */ - public static void main(String args[]) { - String[] testCaseName = {JSBindingsTest.class.getName()}; - junit.textui.TestRunner.main(testCaseName); - } - - // INSTANCE METHOD TESTS - - /** - * Tests implementation of JSBindings constructor. - */ - @Test - public void testConstructor() { - Assert.assertNotNull(new JSBindings(new JSContext(),new SimpleBindings())); - } - - /** - * Test implementation of JSBindings constructor with invalid SCXML context. - */ - @Test - public void testInvalidContextConstructor() { - try { - Assert.assertNotNull(new JSBindings(null,new SimpleBindings())); - Assert.fail("JSBindings constructor accepted invalid SCXML context"); - - } catch (IllegalArgumentException x) { - // expected, ignore - } - } - - /** - * Test implementation of JSBindings constructor with invalid Javascript bindings. - */ - @Test - public void testInvalidBindingsConstructor() { - try { - Assert.assertNotNull(new JSBindings(new JSContext(),null)); - Assert.fail("JSBindings constructor accepted invalid Javascript bindings"); - - } catch (IllegalArgumentException x) { - // expected, ignore - } - } - - /** - * Tests the <code>containsKey</code> method with items in the SCXML context as well as the - * JavaScript Bindings. - * - */ - @Test - public void testContainsKey() { - Context context = new JSContext (); - Bindings bindings = new SimpleBindings(); - JSBindings jsx = new JSBindings (context,bindings); - - Assert.assertFalse("Invalid SCXML context", context.has ("bear")); - Assert.assertFalse("Invalid Javascript bindings",bindings.containsKey("bear")); - Assert.assertFalse("Invalid JSbindings", jsx.containsKey ("bear")); - - context.set("bear","koala"); - Assert.assertTrue ("Invalid SCXML context", context.has ("bear")); - Assert.assertFalse("Invalid Javascript bindings",bindings.containsKey("bear")); - Assert.assertTrue ("Invalid JSbindings", jsx.containsKey ("bear")); - - context.reset(); - bindings.put ("bear","grizzly"); - Assert.assertFalse ("Invalid SCXML context", context.has ("bear")); - Assert.assertTrue ("Invalid Javascript bindings",bindings.containsKey("bear")); - Assert.assertTrue ("Invalid JSbindings", jsx.containsKey ("bear")); - - context.set ("bear","koala"); - bindings.put("bear","grizzly"); - Assert.assertTrue ("Invalid SCXML context", context.has ("bear")); - Assert.assertTrue ("Invalid Javascript bindings",bindings.containsKey("bear")); - Assert.assertTrue ("Invalid JSbindings", jsx.containsKey ("bear")); - } - - /** - * Tests the <code>keySet</code> method with items in the SCXML context as well as the - * JavaScript Bindings. - * - */ - @Test - public void testKeySet() { - Context context = new JSContext (); - Bindings bindings = new SimpleBindings(); - JSBindings jsx = new JSBindings (context,bindings); - - Assert.assertFalse ("Invalid SCXML context", context.has ("bear")); - Assert.assertFalse ("Invalid Javascript bindings",bindings.containsKey ("bear")); - Assert.assertFalse ("Invalid JSbindings", jsx.keySet().contains("bear")); - - context.set ("bear","koala"); - bindings.clear(); - Assert.assertTrue ("Invalid SCXML context", context.has ("bear")); - Assert.assertFalse ("Invalid Javascript bindings",bindings.containsKey ("bear")); - Assert.assertTrue ("Invalid JSbindings", jsx.keySet().contains("bear")); - - context.reset (); - bindings.clear(); - bindings.put ("bear","grizzly"); - Assert.assertFalse ("Invalid SCXML context", context.has ("bear")); - Assert.assertTrue ("Invalid Javascript bindings",bindings.containsKey ("bear")); - Assert.assertTrue ("Invalid JSbindings", jsx.keySet().contains("bear")); - - context.reset (); - bindings.clear(); - context.set ("cat","felix"); - bindings.put ("dog","rover"); - Assert.assertFalse ("Invalid SCXML context", context.has ("bear")); - Assert.assertFalse ("Invalid Javascript bindings",bindings.containsKey ("bear")); - Assert.assertTrue ("Invalid SCXML context", context.has ("cat")); - Assert.assertTrue ("Invalid Javascript bindings",bindings.containsKey ("dog")); - Assert.assertTrue ("Invalid JSbindings", jsx.keySet().contains("cat")); - Assert.assertTrue ("Invalid JSbindings", jsx.keySet().contains("dog")); - } - - /** - * Tests the <code>size</code> method with items in the SCXML context as well as the - * JavaScript Bindings. - * - */ - - @Test - public void testSize() { - Context context = new JSContext (); - Bindings bindings = new SimpleBindings(); - JSBindings jsx = new JSBindings (context,bindings); - - Assert.assertFalse ("Invalid SCXML context", context.has ("bear")); - Assert.assertFalse ("Invalid Javascript bindings",bindings.containsKey ("bear")); - Assert.assertEquals("Invalid JSbindings",0,jsx.size()); - - context.set ("bear","koala"); - bindings.clear(); - Assert.assertTrue ("Invalid SCXML context", context.has ("bear")); - Assert.assertFalse ("Invalid Javascript bindings",bindings.containsKey ("bear")); - Assert.assertEquals ("Invalid JSbindings",1,jsx.size()); - - context.reset (); - bindings.clear(); - bindings.put ("bear","grizzly"); - Assert.assertFalse ("Invalid SCXML context", context.has ("bear")); - Assert.assertTrue ("Invalid Javascript bindings",bindings.containsKey ("bear")); - Assert.assertEquals ("Invalid JSbindings",1,jsx.size()); - - context.reset (); - bindings.clear(); - context.set ("bear","koala"); - bindings.put ("bear","grizzly"); - Assert.assertTrue ("Invalid SCXML context", context.has ("bear")); - Assert.assertTrue ("Invalid Javascript bindings",bindings.containsKey ("bear")); - Assert.assertEquals ("Invalid JSbindings",1,jsx.size()); - - context.reset (); - bindings.clear(); - context.set ("cat","felix"); - bindings.put ("dog","rover"); - Assert.assertFalse ("Invalid SCXML context", context.has ("bear")); - Assert.assertFalse ("Invalid Javascript bindings",bindings.containsKey ("bear")); - Assert.assertTrue ("Invalid SCXML context", context.has ("cat")); - Assert.assertTrue ("Invalid Javascript bindings",bindings.containsKey ("dog")); - Assert.assertEquals ("Invalid JSbindings",2,jsx.size()); - } - - /** - * Tests the <code>containsValue</code> method with items in the SCXML context as well as the - * JavaScript Bindings. - * - */ - @Test - public void testContainsValue() { - Context context = new JSContext (); - Bindings bindings = new SimpleBindings(); - JSBindings jsx = new JSBindings (context,bindings); - - Assert.assertFalse("Invalid SCXML context", context.getVars().containsValue("koala")); - Assert.assertFalse("Invalid Javascript bindings",bindings.containsValue("koala")); - Assert.assertFalse("Invalid JSbindings", jsx.containsValue ("koala")); - - context.reset (); - bindings.clear(); - context.set ("bear","koala"); - Assert.assertTrue ("Invalid SCXML context", context.getVars().containsValue("koala")); - Assert.assertFalse ("Invalid Javascript bindings",bindings.containsValue("koala")); - Assert.assertTrue ("Invalid JSbindings", jsx.containsValue ("koala")); - - context.reset (); - bindings.clear(); - bindings.put ("bear","grizzly"); - Assert.assertFalse ("Invalid SCXML context", context.getVars().containsValue("grizzly")); - Assert.assertTrue ("Invalid Javascript bindings",bindings.containsValue("grizzly")); - Assert.assertTrue ("Invalid JSbindings", jsx.containsValue ("grizzly")); - - context.reset (); - bindings.clear(); - context.set ("bear","koala"); - bindings.put ("bear","grizzly"); - Assert.assertTrue ("Invalid SCXML context", context.getVars().containsValue("koala")); - Assert.assertTrue ("Invalid Javascript bindings",bindings.containsValue("grizzly")); - Assert.assertTrue ("Invalid JSbindings", jsx.containsValue ("koala")); - Assert.assertTrue ("Invalid JSbindings", jsx.containsValue ("grizzly")); - } - - /** - * Tests the <code>entrySet</code> method with items in the SCXML context as well as the - * JavaScript Bindings. - * - */ - @Test - public void testEntrySet() { - Context context = new JSContext (); - Bindings bindings = new SimpleBindings(); - JSBindings jsx = new JSBindings (context,bindings); - - Assert.assertEquals("Invalid SCXML context", 0,context.getVars().entrySet().size()); - Assert.assertEquals("Invalid Javascript bindings",0,bindings.entrySet().size()); - Assert.assertEquals("Invalid JSbindings", 0,jsx.entrySet().size()); - - context.reset (); - bindings.clear(); - context.set ("bear","koala"); - Assert.assertEquals ("Invalid SCXML context", 1,context.getVars().entrySet().size()); - Assert.assertTrue ("Invalid SCXML context", context.getVars().entrySet().contains(KOALA)); - Assert.assertEquals ("Invalid Javascript bindings",0,bindings.entrySet().size()); - Assert.assertFalse ("Invalid Javascript bindings",bindings.entrySet().contains(KOALA)); - Assert.assertEquals ("Invalid JSBindings", 1,jsx.entrySet().size()); - Assert.assertTrue ("Invalid JSbindings", jsx.entrySet().contains(KOALA)); - - context.reset (); - bindings.clear(); - bindings.put ("bear","grizzly"); - Assert.assertEquals ("Invalid SCXML context", 0,context.getVars().entrySet().size()); - Assert.assertFalse ("Invalid SCXML context", context.getVars().entrySet().contains(GRIZZLY)); - Assert.assertEquals ("Invalid Javascript bindings",1,bindings.entrySet().size()); - Assert.assertTrue ("Invalid Javascript bindings",bindings.entrySet().contains(GRIZZLY)); - Assert.assertEquals ("Invalid JSBindings", 1,jsx.entrySet().size()); - Assert.assertTrue ("Invalid JSbindings", jsx.entrySet().contains(GRIZZLY)); - - context.reset (); - bindings.clear(); - context.set ("bear","koala"); - bindings.put ("bear","grizzly"); - Assert.assertEquals ("Invalid SCXML context", 1,context.getVars().entrySet().size()); - Assert.assertTrue ("Invalid SCXML context", context.getVars().entrySet().contains(KOALA)); - Assert.assertEquals ("Invalid Javascript bindings",1,bindings.entrySet().size()); - Assert.assertTrue ("Invalid Javascript bindings",bindings.entrySet().contains(GRIZZLY)); - Assert.assertEquals ("Invalid JSBindings", 1,jsx.entrySet().size()); - Assert.assertFalse ("Invalid JSbindings", jsx.entrySet().contains(KOALA)); - Assert.assertTrue ("Invalid JSbindings", jsx.entrySet().contains(GRIZZLY)); - - context.reset (); - bindings.clear(); - context.set ("cat","felix"); - bindings.put ("dog","rover"); - Assert.assertEquals ("Invalid SCXML context", 1,context.getVars().entrySet().size()); - Assert.assertTrue ("Invalid SCXML context", context.getVars().entrySet().contains(FELIX)); - Assert.assertEquals ("Invalid Javascript bindings",1,bindings.entrySet().size()); - Assert.assertTrue ("Invalid Javascript bindings",bindings.entrySet().contains(ROVER)); - Assert.assertEquals ("Invalid JSBindings", 2,jsx.entrySet().size()); - Assert.assertTrue ("Invalid JSbindings", jsx.entrySet().contains(FELIX)); - Assert.assertTrue ("Invalid JSbindings", jsx.entrySet().contains(ROVER)); - } - - /** - * Tests the <code>values</code> method with items in the SCXML context as well as the - * JavaScript Bindings. - * - */ - @Test - public void testValues() { - Context context = new JSContext (); - Bindings bindings = new SimpleBindings(); - JSBindings jsx = new JSBindings (context,bindings); - - Assert.assertEquals("Invalid SCXML context", 0,context.getVars().values().size()); - Assert.assertEquals("Invalid Javascript bindings",0,bindings.values().size()); - Assert.assertEquals("Invalid JSbindings", 0,jsx.values().size()); - - context.reset (); - bindings.clear(); - context.set ("bear","koala"); - Assert.assertEquals ("Invalid SCXML context", 1,context.getVars().values().size()); - Assert.assertTrue ("Invalid SCXML context", context.getVars().values().contains(KOALA.getValue())); - Assert.assertEquals ("Invalid Javascript bindings",0,bindings.values().size()); - Assert.assertFalse ("Invalid Javascript bindings",bindings.values().contains(KOALA.getValue())); - Assert.assertEquals ("Invalid JSBindings", 1,jsx.values().size()); - Assert.assertTrue ("Invalid JSbindings", jsx.values().contains(KOALA.getValue())); - - context.reset (); - bindings.clear(); - bindings.put ("bear","grizzly"); - Assert.assertEquals ("Invalid SCXML context", 0,context.getVars().values().size()); - Assert.assertFalse ("Invalid SCXML context", context.getVars().values().contains(GRIZZLY.getValue())); - Assert.assertEquals ("Invalid Javascript bindings",1,bindings.values().size()); - Assert.assertTrue ("Invalid Javascript bindings",bindings.values().contains(GRIZZLY.getValue())); - Assert.assertEquals ("Invalid JSBindings", 1,jsx.values().size()); - Assert.assertTrue ("Invalid JSbindings", jsx.values().contains(GRIZZLY.getValue())); - - context.reset (); - bindings.clear(); - context.set ("bear","koala"); - bindings.put ("bear","grizzly"); - Assert.assertEquals ("Invalid SCXML context", 1,context.getVars().values().size()); - Assert.assertTrue ("Invalid SCXML context", context.getVars().values().contains(KOALA.getValue())); - Assert.assertEquals ("Invalid Javascript bindings",1,bindings.values().size()); - Assert.assertTrue ("Invalid Javascript bindings",bindings.values().contains(GRIZZLY.getValue())); - Assert.assertEquals ("Invalid JSBindings", 1,jsx.values().size()); - Assert.assertFalse ("Invalid JSbindings", jsx.values().contains(KOALA.getValue())); - Assert.assertTrue ("Invalid JSbindings", jsx.values().contains(GRIZZLY.getValue())); - - context.reset (); - bindings.clear(); - context.set ("cat","felix"); - bindings.put ("dog","rover"); - Assert.assertEquals ("Invalid SCXML context", 1,context.getVars().values().size()); - Assert.assertTrue ("Invalid SCXML context", context.getVars().values().contains(FELIX.getValue())); - Assert.assertEquals ("Invalid Javascript bindings",1,bindings.values().size()); - Assert.assertTrue ("Invalid Javascript bindings",bindings.values().contains(ROVER.getValue())); - Assert.assertEquals ("Invalid JSBindings", 2,jsx.values().size()); - Assert.assertTrue ("Invalid JSbindings", jsx.values().contains(FELIX.getValue())); - Assert.assertTrue ("Invalid JSbindings", jsx.values().contains(ROVER.getValue())); - } - - /** - * Tests the <code>isEmpty</code> method with items in the SCXML context as well as the - * JavaScript Bindings. - * - */ - @Test - public void testIsEmpty() { - Context context = new JSContext (); - Bindings bindings = new SimpleBindings(); - JSBindings jsx = new JSBindings (context,bindings); - - Assert.assertTrue("Invalid SCXML context", context.getVars().isEmpty()); - Assert.assertTrue("Invalid Javascript bindings",bindings.isEmpty()); - Assert.assertTrue("Invalid JSbindings", jsx.isEmpty()); - - context.set ("bear","koala"); - bindings.clear(); - Assert.assertFalse ("Invalid SCXML context", context.getVars().isEmpty()); - Assert.assertTrue ("Invalid Javascript bindings",bindings.isEmpty()); - Assert.assertFalse ("Invalid JSbindings", jsx.isEmpty()); - - context.reset (); - bindings.clear(); - bindings.put ("bear","grizzly"); - Assert.assertTrue ("Invalid SCXML context", context.getVars().isEmpty()); - Assert.assertFalse ("Invalid Javascript bindings",bindings.isEmpty()); - Assert.assertFalse ("Invalid JSbindings", jsx.isEmpty()); - - context.reset (); - bindings.clear(); - context.set ("bear","koala"); - bindings.put ("bear","grizzly"); - Assert.assertFalse ("Invalid SCXML context", context.getVars().isEmpty()); - Assert.assertFalse ("Invalid Javascript bindings",bindings.isEmpty()); - Assert.assertFalse ("Invalid JSbindings", jsx.isEmpty()); - - context.reset (); - bindings.clear(); - context.set ("cat","felix"); - bindings.put ("dog","rover"); - Assert.assertFalse ("Invalid SCXML context", context.getVars().isEmpty()); - Assert.assertFalse ("Invalid Javascript bindings",bindings.isEmpty()); - Assert.assertFalse ("Invalid JSbindings", jsx.isEmpty()); - } - - /** - * Tests the <code>get</code> method with items in the SCXML context as well as the - * JavaScript Bindings. - * - */ - @Test - public void testGet() { - Context context = new JSContext (); - Bindings bindings = new SimpleBindings(); - JSBindings jsx = new JSBindings (context,bindings); - - Assert.assertNull("Invalid SCXML context", context.get ("bear")); - Assert.assertNull("Invalid Javascript bindings",bindings.get("bear")); - Assert.assertNull("Invalid JSbindings", jsx.get ("bear")); - - context.reset (); - bindings.clear(); - context.set ("bear","koala"); - Assert.assertNotNull ("Invalid SCXML context", context.get ("bear")); - Assert.assertEquals ("Invalid SCXML context","koala",context.get ("bear")); - Assert.assertNull ("Invalid Javascript bindings", bindings.get("bear")); - Assert.assertNotNull ("Invalid JSbindings", jsx.get ("bear")); - Assert.assertEquals ("Invalid JSbindings","koala", jsx.get ("bear")); - - context.reset (); - bindings.clear(); - bindings.put ("bear","grizzly"); - Assert.assertNull ("Invalid SCXML context", context.get ("bear")); - Assert.assertNotNull ("Invalid Javascript bindings", bindings.get("bear")); - Assert.assertEquals ("Invalid Javascript bindings","grizzly",bindings.get("bear")); - Assert.assertNotNull ("Invalid JSbindings", jsx.get ("bear")); - Assert.assertEquals ("Invalid JSbindings","grizzly", jsx.get ("bear")); - - context.reset (); - bindings.clear(); - context.set ("bear","koala"); - bindings.put ("bear","grizzly"); - Assert.assertNotNull ("Invalid SCXML context", context.get ("bear")); - Assert.assertEquals ("Invalid SCXML context","koala",context.get ("bear")); - Assert.assertNotNull ("Invalid Javascript bindings", bindings.get("bear")); - Assert.assertEquals ("Invalid Javascript bindings","grizzly",bindings.get("bear")); - Assert.assertNotNull ("Invalid JSbindings", jsx.get ("bear")); - Assert.assertEquals ("Invalid JSbindings","grizzly", jsx.get ("bear")); - - context.reset (); - bindings.clear(); - context.set ("cat","felix"); - bindings.put ("dog","rover"); - Assert.assertNotNull ("Invalid SCXML context", context.get ("cat")); - Assert.assertEquals ("Invalid SCXML context","felix", context.get ("cat")); - Assert.assertNotNull ("Invalid Javascript bindings", bindings.get("dog")); - Assert.assertEquals ("Invalid Javascript bindings","rover",bindings.get("dog")); - Assert.assertNotNull ("Invalid JSbindings", jsx.get ("cat")); - Assert.assertEquals ("Invalid JSbindings","felix", jsx.get ("cat")); - Assert.assertNotNull ("Invalid JSbindings", jsx.get ("dog")); - Assert.assertEquals ("Invalid JSbindings","rover", jsx.get ("dog")); - } - - /** - * Tests the <code>put</code> method with items in the SCXML context as well as the - * JavaScript Bindings. - * - */ - @Test - public void testPut() { - Context context = new JSContext (); - Bindings bindings = new SimpleBindings(); - JSBindings jsx = new JSBindings (context,bindings); - - Assert.assertNull("Invalid SCXML context", context.get ("bear")); - Assert.assertNull("Invalid Javascript bindings",bindings.get("bear")); - Assert.assertNull("Invalid JSbindings", jsx.get ("bear")); - - jsx.put ("bear","koala"); - Assert.assertNotNull ("Invalid SCXML context", context.get ("bear")); - Assert.assertEquals ("Invalid SCXML context","koala",context.get("bear")); - Assert.assertNotNull ("Invalid JSbindings", jsx.get ("bear")); - Assert.assertNull ("Invalid Javascript bindings", bindings.get("bear")); - } - - /** - * Tests the <code>putAll</code> method with items in the SCXML context as well as the - * JavaScript Bindings. - * - */ - @Test - public void testPutAll() { - Context context = new JSContext (); - Bindings bindings = new SimpleBindings(); - JSBindings jsx = new JSBindings (context,bindings); - Map<String,Object> vars = new HashMap<String, Object>(); - - vars.put("bear","grizzly"); - vars.put("cat","felix"); - vars.put("dog","rover"); - - Assert.assertNull("Invalid SCXML context", context.get ("bear")); - Assert.assertNull("Invalid SCXML context", context.get ("cat")); - Assert.assertNull("Invalid SCXML context", context.get ("dog")); - - Assert.assertNull("Invalid Javascript bindings",bindings.get("bear")); - Assert.assertNull("Invalid Javascript bindings",bindings.get("cat")); - Assert.assertNull("Invalid Javascript bindings",bindings.get("dog")); - - Assert.assertNull("Invalid JSbindings", jsx.get ("bear")); - Assert.assertNull("Invalid JSbindings", jsx.get ("cat")); - Assert.assertNull("Invalid JSbindings", jsx.get ("dog")); - - context.set("bear","koala"); - jsx.putAll (vars); - - Assert.assertNotNull ("Invalid SCXML context", context.get ("bear")); - Assert.assertNull ("Invalid SCXML context", context.get ("cat")); - Assert.assertNull ("Invalid SCXML context", context.get ("dog")); - Assert.assertEquals ("Invalid SCXML context","koala",context.get ("bear")); - Assert.assertEquals ("Invalid SCXML context",1, context.getVars().size()); - - Assert.assertNotNull ("Invalid Javascript bindings", bindings.get("bear")); - Assert.assertNotNull ("Invalid Javascript bindings", bindings.get("cat")); - Assert.assertNotNull ("Invalid Javascript bindings", bindings.get("dog")); - Assert.assertEquals ("Invalid Javascript bindings","grizzly",bindings.get("bear")); - Assert.assertEquals ("Invalid Javascript bindings","felix", bindings.get("cat")); - Assert.assertEquals ("Invalid Javascript bindings","rover", bindings.get("dog")); - Assert.assertEquals ("Invalid Javascript bindings",3, bindings.size()); - } - - /** - * Tests the <code>remove</code> method with items in the SCXML context as well as the - * JavaScript Bindings. - * - */ - @Test - public void testRemove() { - Context context = new JSContext (); - Bindings bindings = new SimpleBindings(); - JSBindings jsx = new JSBindings (context,bindings); - - context.set ("bear","koala"); - bindings.put("bear","grizzly"); - bindings.put("cat", "felix"); - bindings.put("dog", "rover"); - - Assert.assertNotNull("Invalid SCXML context", context.get("bear")); - Assert.assertEquals ("Invalid SCXML context","koala",context.get("bear")); - Assert.assertEquals ("Invalid SCXML context",1, context.getVars().size()); - - Assert.assertNotNull("Invalid Javascript bindings", bindings.get("bear")); - Assert.assertNotNull("Invalid Javascript bindings", bindings.get("cat")); - Assert.assertNotNull("Invalid Javascript bindings", bindings.get("dog")); - Assert.assertEquals ("Invalid Javascript bindings","grizzly",bindings.get("bear")); - Assert.assertEquals ("Invalid Javascript bindings","felix", bindings.get("cat")); - Assert.assertEquals ("Invalid Javascript bindings","rover", bindings.get("dog")); - Assert.assertEquals ("Invalid Javascript bindings",3, bindings.size()); - - jsx.remove("cat"); - - Assert.assertNotNull("Invalid SCXML context", context.get("bear")); - Assert.assertEquals ("Invalid SCXML context","koala", context.get("bear")); - Assert.assertEquals ("Invalid SCXML context",1, context.getVars().size()); - Assert.assertNotNull("Invalid Javascript bindings", bindings.get("bear")); - Assert.assertNull ("Invalid Javascript bindings", bindings.get("cat")); - Assert.assertNotNull("Invalid Javascript bindings", bindings.get("dog")); - Assert.assertEquals ("Invalid Javascript bindings","grizzly",bindings.get("bear")); - Assert.assertEquals ("Invalid Javascript bindings","rover", bindings.get("dog")); - Assert.assertEquals ("Invalid Javascript bindings",2, bindings.size()); - - jsx.remove("dog"); - - Assert.assertNotNull("Invalid SCXML context", context.get("bear")); - Assert.assertEquals ("Invalid SCXML context","koala", context.get("bear")); - Assert.assertEquals ("Invalid SCXML context",1, context.getVars().size()); - Assert.assertNotNull("Invalid Javascript bindings", bindings.get("bear")); - Assert.assertNull ("Invalid Javascript bindings", bindings.get("cat")); - Assert.assertNull ("Invalid Javascript bindings", bindings.get("dog")); - Assert.assertEquals ("Invalid Javascript bindings","grizzly",bindings.get("bear")); - Assert.assertEquals ("Invalid Javascript bindings",1, bindings.size()); - - jsx.remove("bear"); - - Assert.assertNotNull("Invalid SCXML context", context.get("bear")); - Assert.assertEquals("Invalid SCXML context","koala",context.get("bear")); - Assert.assertEquals("Invalid SCXML context",1, context.getVars().size()); - Assert.assertNull ("Invalid Javascript bindings", bindings.get("bear")); - Assert.assertNull ("Invalid Javascript bindings", bindings.get("cat")); - Assert.assertNull ("Invalid Javascript bindings", bindings.get("dog")); - Assert.assertEquals("Invalid Javascript bindings",0,bindings.size()); - - jsx.remove("bear"); - - Assert.assertNull ("Invalid SCXML context", context.get("bear")); - Assert.assertEquals("Invalid SCXML context",0, context.getVars().size()); - Assert.assertNull ("Invalid Javascript bindings", bindings.get("bear")); - Assert.assertNull ("Invalid Javascript bindings", bindings.get("cat")); - Assert.assertNull ("Invalid Javascript bindings", bindings.get("dog")); - Assert.assertEquals("Invalid Javascript bindings",0,bindings.size()); - } - - /** - * Tests the <code>clear</code> method with items in the SCXML context as well as the - * JavaScript Bindings. - * - */ - @Test - public void testClear() { - Context context = new JSContext (); - Bindings bindings = new SimpleBindings(); - JSBindings jsx = new JSBindings (context,bindings); - - context.set ("bear","koala"); - bindings.put("bear","grizzly"); - bindings.put("cat", "felix"); - bindings.put("dog", "rover"); - - Assert.assertNotNull("Invalid SCXML context", context.get("bear")); - Assert.assertEquals ("Invalid SCXML context","koala",context.get("bear")); - Assert.assertEquals ("Invalid SCXML context",1, context.getVars().size()); - - Assert.assertNotNull("Invalid Javascript bindings", bindings.get("bear")); - Assert.assertNotNull("Invalid Javascript bindings", bindings.get("cat")); - Assert.assertNotNull("Invalid Javascript bindings", bindings.get("dog")); - Assert.assertEquals ("Invalid Javascript bindings","grizzly",bindings.get("bear")); - Assert.assertEquals ("Invalid Javascript bindings","felix", bindings.get("cat")); - Assert.assertEquals ("Invalid Javascript bindings","rover", bindings.get("dog")); - Assert.assertEquals ("Invalid Javascript bindings",3, bindings.size()); - - jsx.clear(); - - Assert.assertNotNull("Invalid SCXML context", context.get("bear")); - Assert.assertEquals("Invalid SCXML context","koala",context.get("bear")); - Assert.assertEquals("Invalid SCXML context",1, context.getVars().size()); - Assert.assertNull ("Invalid Javascript bindings", bindings.get("bear")); - Assert.assertNull ("Invalid Javascript bindings", bindings.get("cat")); - Assert.assertNull ("Invalid Javascript bindings", bindings.get("dog")); - Assert.assertEquals("Invalid Javascript bindings",0,bindings.size()); - } - -} - http://git-wip-us.apache.org/repos/asf/commons-scxml/blob/1b91a77c/src/test/java/org/apache/commons/scxml2/env/javascript/JavaScriptEngineTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/commons/scxml2/env/javascript/JavaScriptEngineTest.java b/src/test/java/org/apache/commons/scxml2/env/javascript/JavaScriptEngineTest.java index c1bafaf..4ee8f0e 100644 --- a/src/test/java/org/apache/commons/scxml2/env/javascript/JavaScriptEngineTest.java +++ b/src/test/java/org/apache/commons/scxml2/env/javascript/JavaScriptEngineTest.java @@ -16,79 +16,89 @@ */ package org.apache.commons.scxml2.env.javascript; -import static org.junit.Assert.assertEquals; - -import javax.script.Bindings; -import javax.script.ScriptContext; -import javax.script.ScriptEngine; -import javax.script.ScriptEngineManager; +import java.util.UUID; -import org.apache.commons.scxml2.Context; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.apache.commons.scxml2.SCXMLSystemContext; +import org.apache.commons.scxml2.StateConfiguration; +import org.apache.commons.scxml2.Status; +import org.apache.commons.scxml2.model.Final; +import org.apache.commons.scxml2.system.EventVariable; import org.junit.Before; import org.junit.Test; public class JavaScriptEngineTest { - private ScriptEngine engine; - - private Context context; + private JSEvaluator evaluator; + private StateConfiguration stateConfiguration; + private JSContext _systemContext; + private JSContext context; @Before public void before() throws Exception { - ScriptEngineManager factory = new ScriptEngineManager(); - engine = factory.getEngineByName("JavaScript"); - context = new JSContext(); + evaluator = new JSEvaluator(); + _systemContext = new JSContext(); + SCXMLSystemContext systemContext = new SCXMLSystemContext(_systemContext); + _systemContext.set(SCXMLSystemContext.SESSIONID_KEY, UUID.randomUUID().toString()); + _systemContext.set(SCXMLSystemContext.SCXML_NAME_KEY, "test"); + stateConfiguration = new StateConfiguration(); + systemContext.getPlatformVariables().put(SCXMLSystemContext.STATUS_KEY, new Status(stateConfiguration)); + context = new JSContext(systemContext); } @Test - public void testSimpleEvaluation() throws Exception { - Object ret = engine.eval("1.0 + 2.0"); - assertEquals(3.0, ret); + public void testInitScxmlSystemContext() throws Exception { + assertEquals("test", evaluator.eval(context, "_name")); } @Test - public void testBindingsInput() throws Exception { - Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); - bindings.put("x", 1.0); - bindings.put("y", 2.0); - - Object ret = engine.eval("x + y;", bindings); - assertEquals(3.0, ret); + public void testScxmlEvent() throws Exception { + assertTrue(evaluator.evalCond(context, "_event === undefined")); + EventVariable event = new EventVariable("myEvent", EventVariable.TYPE_INTERNAL, null, null, null, null,"myData"); + _systemContext.setLocal(SCXMLSystemContext.EVENT_KEY, event); + assertFalse(evaluator.evalCond(context, "_event === undefined")); + assertTrue(evaluator.evalCond(context, "_event.name == 'myEvent'")); + assertTrue(evaluator.evalCond(context, "_event.type == 'internal'")); + assertTrue(evaluator.evalCond(context, "_event.data == 'myData'")); + assertTrue(evaluator.evalCond(context, "_event.origin === undefined")); } @Test - public void testBindingsInput_WithJSBindings() throws Exception { - Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); - JSBindings jsBindings = new JSBindings(context, bindings); - jsBindings.put("x", 1.0); - jsBindings.put("y", 2.0); - - Object ret = engine.eval("x + y;", jsBindings); - assertEquals(3.0, ret); + public void testScxmlInPredicate() throws Exception { + assertFalse(evaluator.evalCond(context, "In('foo')")); + Final foo = new Final(); + foo.setId("foo"); + stateConfiguration.enterState(foo); + assertTrue(evaluator.evalCond(context, "In('foo')")); } @Test - public void testBindingsGlobal() throws Exception { - Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); - bindings.put("x", 1.0); - bindings.put("y", 2.0); - bindings.put("z", 0.0); - - engine.eval("z = x + y;", bindings); - assertEquals("z variable is expected to set to 3.0 in global, but it was " + bindings.get("z") + ".", - 3.0, bindings.get("z")); + public void testCopyJavscriptGlobalsToScxmlContext() throws Exception { + assertFalse(context.has("x")); + evaluator.eval(context, ("x=3")); + assertEquals(3, context.get("x")); } @Test - public void testBindingsGlobal_WithJSBindings() throws Exception { - Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); - JSBindings jsBindings = new JSBindings(context, bindings); - jsBindings.put("x", 1.0); - jsBindings.put("y", 2.0); - jsBindings.put("z", 0.0); + public void testSharedJavscriptGlobalsRetainedAcrossInvocation() throws Exception { + assertFalse(context.has("x")); + evaluator.eval(context, ("x=3")); + context.getVars().remove("x"); + assertFalse(context.has("x")); + assertTrue(evaluator.evalCond(context, "x===3")); + } - engine.eval("z = x + y;", jsBindings); - assertEquals("z variable is expected to set to 3.0 in global, but it was " + jsBindings.get("z") + ".", - 3.0, jsBindings.get("z")); + @Test + public void testJavscriptGlobalsNotRetainedAcrossEvaluatorInstances() throws Exception { + assertFalse(context.has("x")); + evaluator.eval(context, ("x=3")); + assertEquals(3, context.get("x")); + context.getVars().remove("x"); + assertFalse(context.has("x")); + evaluator = new JSEvaluator(); + assertTrue(evaluator.evalCond(context, "typeof x=='undefined'")); } } http://git-wip-us.apache.org/repos/asf/commons-scxml/blob/1b91a77c/src/test/java/org/apache/commons/scxml2/w3c/tests.xml ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/commons/scxml2/w3c/tests.xml b/src/test/java/org/apache/commons/scxml2/w3c/tests.xml index f3657a6..90ee864 100644 --- a/src/test/java/org/apache/commons/scxml2/w3c/tests.xml +++ b/src/test/java/org/apache/commons/scxml2/w3c/tests.xml @@ -89,15 +89,15 @@ <test id="325" mandatory="true" manual="false" enabled="false" ecma="fail"/> <test id="326" mandatory="true" manual="false" enabled="false" ecma="fail"/> <test id="329" mandatory="true" manual="false" enabled="true" ecma="pass"/> - <test id="330" mandatory="true" manual="false" enabled="false" ecma="fail"/> + <test id="330" mandatory="true" manual="false" enabled="true" ecma="pass"/> <test id="331" mandatory="true" manual="false" enabled="true" ecma="pass"/> <test id="332" mandatory="true" manual="false" enabled="false" ecma="fail"/> - <test id="333" mandatory="true" manual="false" enabled="false" ecma="fail"/> - <test id="335" mandatory="true" manual="false" enabled="false" ecma="fail"/> + <test id="333" mandatory="true" manual="false" enabled="true" ecma="pass"/> + <test id="335" mandatory="true" manual="false" enabled="true" ecma="pass"/> <test id="336" mandatory="true" manual="false" enabled="true" ecma="pass"/> - <test id="337" mandatory="true" manual="false" enabled="false" ecma="fail"/> + <test id="337" mandatory="true" manual="false" enabled="true" ecma="pass"/> <test id="338" mandatory="true" manual="false" enabled="false" ecma="" >Fails to complete</test> - <test id="339" mandatory="true" manual="false" enabled="false" ecma="fail"/> + <test id="339" mandatory="true" manual="false" enabled="true" ecma="pass"/> <test id="342" mandatory="true" manual="false" enabled="true" ecma="pass"/> <test id="346" mandatory="true" manual="false" enabled="true" ecma="pass"/> <test id="172" mandatory="true" manual="false" enabled="true" ecma="pass"/> @@ -118,7 +118,7 @@ <test id="201" mandatory="false" manual="false" enabled="false" ecma="fail"/> <test id="205" mandatory="true" manual="false" enabled="true" ecma="pass"/> <test id="521" mandatory="true" manual="false" enabled="false" ecma="fail"/> - <test id="553" mandatory="true" manual="false" enabled="false" ecma="pass"/> + <test id="553" mandatory="true" manual="false" enabled="true" ecma="pass"/> <test id="207" mandatory="true" manual="false" enabled="false" ecma="" >Fails to complete</test> <test id="208" mandatory="true" manual="false" enabled="false" ecma="fail"/> <test id="210" mandatory="true" manual="false" enabled="false" ecma="fail"/> @@ -155,7 +155,7 @@ <test id="278" mandatory="false" manual="false" enabled="false" ecma="fail"/> <test id="444" mandatory="false" manual="false" enabled="true" ecma="pass"/> <test id="445" mandatory="false" manual="false" enabled="true" ecma="pass"/> - <test id="448" mandatory="false" manual="false" enabled="false" ecma="fail"/> + <test id="448" mandatory="false" manual="false" enabled="true" ecma="pass"/> <test id="449" mandatory="false" manual="false" enabled="true" ecma="pass"/> <test id="451" mandatory="false" manual="false" enabled="true" ecma="pass"/> <test id="452" mandatory="false" manual="false" enabled="true" ecma="pass"/>
