| /* |
| * 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.jexl; |
| |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.UUID; |
| |
| import org.apache.commons.jexl2.Expression; |
| import org.apache.commons.jexl2.JexlEngine; |
| import org.apache.commons.jexl2.Script; |
| import org.apache.commons.scxml2.Context; |
| import org.apache.commons.scxml2.Evaluator; |
| import org.apache.commons.scxml2.EvaluatorProvider; |
| import org.apache.commons.scxml2.SCXMLExpressionException; |
| import org.apache.commons.scxml2.env.AbstractBaseEvaluator; |
| import org.apache.commons.scxml2.env.EffectiveContextMap; |
| import org.apache.commons.scxml2.model.SCXML; |
| |
| /** |
| * Evaluator implementation enabling use of JEXL expressions in |
| * SCXML documents. |
| * <P> |
| * This implementation itself is thread-safe, so you can keep singleton |
| * for efficiency of the internal <code>JexlEngine</code> member. |
| * </P> |
| */ |
| public class JexlEvaluator extends AbstractBaseEvaluator { |
| |
| /** Serial version UID. */ |
| private static final long serialVersionUID = 1L; |
| |
| /** |
| * Unique context variable name used for temporary reference to assign data (thus must be a valid variable name) |
| */ |
| private static final String ASSIGN_VARIABLE_NAME = "a"+UUID.randomUUID().toString().replace('-','x'); |
| |
| public static final String SUPPORTED_DATA_MODEL = "jexl"; |
| |
| public static class JexlEvaluatorProvider implements EvaluatorProvider { |
| |
| @Override |
| public String getSupportedDatamodel() { |
| return SUPPORTED_DATA_MODEL; |
| } |
| |
| @Override |
| public Evaluator getEvaluator() { |
| return new JexlEvaluator(); |
| } |
| |
| @Override |
| public Evaluator getEvaluator(final SCXML document) { |
| return new JexlEvaluator(); |
| } |
| } |
| |
| /** Error message if evaluation context is not a JexlContext. */ |
| private static final String ERR_CTX_TYPE = "Error evaluating JEXL " |
| + "expression, Context must be a org.apache.commons.scxml2.env.jexl.JexlContext"; |
| |
| |
| |
| /** The internal JexlEngine instance to use. */ |
| private transient volatile JexlEngine jexlEngine; |
| |
| /** The current JexlEngine silent mode, stored locally to be reapplied after deserialization of the engine */ |
| private boolean jexlEngineSilent; |
| /** The current JexlEngine strict mode, stored locally to be reapplied after deserialization of the engine */ |
| private boolean jexlEngineStrict; |
| |
| /** Constructor. */ |
| public JexlEvaluator() { |
| super(); |
| // create the internal JexlEngine initially |
| jexlEngine = createJexlEngine(); |
| jexlEngineSilent = jexlEngine.isSilent(); |
| jexlEngineStrict = jexlEngine.isStrict(); |
| } |
| |
| /** |
| * Checks whether the internal Jexl engine throws JexlException during evaluation. |
| * @return true if silent, false (default) otherwise |
| */ |
| public boolean isJexlEngineSilent() { |
| return jexlEngineSilent; |
| } |
| |
| /** |
| * Delegate method for {@link JexlEngine#setSilent(boolean)} to set whether the engine throws JexlException during |
| * evaluation when an error is triggered. |
| * <p>This method should be called as an optional step of the JexlEngine |
| * initialization code before expression creation & evaluation.</p> |
| * @param silent true means no JexlException will occur, false allows them |
| */ |
| public void setJexlEngineSilent(boolean silent) { |
| synchronized (this) { |
| JexlEngine engine = getJexlEngine(); |
| engine.setSilent(silent); |
| this.jexlEngineSilent = silent; |
| } |
| } |
| |
| /** |
| * Checks whether the internal Jexl engine behaves in strict or lenient mode. |
| * @return true for strict, false for lenient |
| */ |
| public boolean isJexlEngineStrict() { |
| return jexlEngineStrict; |
| } |
| |
| /** |
| * Delegate method for {@link JexlEngine#setStrict(boolean)} to set whether it behaves in strict or lenient mode. |
| * <p>This method is should be called as an optional step of the JexlEngine |
| * initialization code before expression creation & evaluation.</p> |
| * @param strict true for strict, false for lenient |
| */ |
| public void setJexlEngineStrict(boolean strict) { |
| synchronized (this) { |
| JexlEngine engine = getJexlEngine(); |
| engine.setStrict(strict); |
| this.jexlEngineStrict = strict; |
| } |
| } |
| |
| @Override |
| public String getSupportedDatamodel() { |
| return SUPPORTED_DATA_MODEL; |
| } |
| |
| /** |
| * Evaluate an expression. |
| * |
| * @param ctx variable context |
| * @param expr expression |
| * @return a result of the evaluation |
| * @throws SCXMLExpressionException For a malformed expression |
| * @see Evaluator#eval(Context, String) |
| */ |
| public Object eval(final Context ctx, final String expr) |
| throws SCXMLExpressionException { |
| if (expr == null) { |
| return null; |
| } |
| if (!(ctx instanceof JexlContext)) { |
| throw new SCXMLExpressionException(ERR_CTX_TYPE); |
| } |
| try { |
| final JexlContext effective = getEffectiveContext((JexlContext)ctx); |
| Expression exp = getJexlEngine().createExpression(expr); |
| return exp.evaluate(effective); |
| } catch (Exception e) { |
| String exMessage = e.getMessage() != null ? e.getMessage() : e.getClass().getCanonicalName(); |
| throw new SCXMLExpressionException("eval('" + expr + "'): " + exMessage, e); |
| } |
| } |
| |
| /** |
| * @see Evaluator#evalCond(Context, String) |
| */ |
| public Boolean evalCond(final Context ctx, final String expr) |
| throws SCXMLExpressionException { |
| if (expr == null) { |
| return null; |
| } |
| if (!(ctx instanceof JexlContext)) { |
| throw new SCXMLExpressionException(ERR_CTX_TYPE); |
| } |
| try { |
| final JexlContext effective = getEffectiveContext((JexlContext)ctx); |
| Expression exp = getJexlEngine().createExpression(expr); |
| final Object result = exp.evaluate(effective); |
| return result == null ? Boolean.FALSE : (Boolean)result; |
| } catch (Exception e) { |
| String exMessage = e.getMessage() != null ? e.getMessage() : e.getClass().getCanonicalName(); |
| throw new SCXMLExpressionException("evalCond('" + expr + "'): " + exMessage, e); |
| } |
| } |
| |
| /** |
| * @see Evaluator#evalAssign(Context, String, Object, AssignType, String) |
| */ |
| public void evalAssign(final Context ctx, final String location, final Object data, final AssignType type, |
| final String attr) throws SCXMLExpressionException { |
| StringBuilder sb = new StringBuilder(location).append("=").append(ASSIGN_VARIABLE_NAME); |
| try { |
| ctx.getVars().put(ASSIGN_VARIABLE_NAME, data); |
| eval(ctx, sb.toString()); |
| } |
| finally { |
| ctx.getVars().remove(ASSIGN_VARIABLE_NAME); |
| } |
| } |
| |
| /** |
| * @see Evaluator#evalScript(Context, String) |
| */ |
| public Object evalScript(final Context ctx, final String script) |
| throws SCXMLExpressionException { |
| if (script == null) { |
| return null; |
| } |
| if (!(ctx instanceof JexlContext)) { |
| throw new SCXMLExpressionException(ERR_CTX_TYPE); |
| } |
| try { |
| final JexlContext effective = getEffectiveContext((JexlContext) ctx); |
| final Script jexlScript = getJexlEngine().createScript(script); |
| return jexlScript.execute(effective); |
| } catch (Exception e) { |
| String exMessage = e.getMessage() != null ? e.getMessage() : e.getClass().getCanonicalName(); |
| throw new SCXMLExpressionException("evalScript('" + script + "'): " + exMessage, e); |
| } |
| } |
| |
| /** |
| * Create a new child context. |
| * |
| * @param parent parent context |
| * @return new child context |
| * @see Evaluator#newContext(Context) |
| */ |
| public Context newContext(final Context parent) { |
| return new JexlContext(parent); |
| } |
| |
| /** |
| * Create the internal JexlEngine member during the initialization. |
| * This method can be overriden to specify more detailed options |
| * into the JexlEngine. |
| * @return new JexlEngine instance |
| */ |
| protected JexlEngine createJexlEngine() { |
| JexlEngine engine = new JexlEngine(); |
| // With null prefix, define top-level user defined functions. |
| // See javadoc of org.apache.commons.jexl2.JexlEngine#setFunctions(Map<String,Object> funcs) for detail. |
| Map<String, Object> funcs = new HashMap<String, Object>(); |
| funcs.put(null, JexlBuiltin.class); |
| engine.setFunctions(funcs); |
| engine.setCache(256); |
| return engine; |
| } |
| |
| /** |
| * Returns the internal JexlEngine if existing. |
| * Otherwise, it creates a new engine by invoking {@link #createJexlEngine()}. |
| * <P> |
| * <EM>NOTE: The internal JexlEngine instance can be null when this is deserialized.</EM> |
| * </P> |
| * @return the current JexlEngine |
| */ |
| private JexlEngine getJexlEngine() { |
| JexlEngine engine = jexlEngine; |
| if (engine == null) { |
| synchronized (this) { |
| engine = jexlEngine; |
| if (engine == null) { |
| jexlEngine = engine = createJexlEngine(); |
| jexlEngine.setSilent(jexlEngineSilent); |
| jexlEngine.setStrict(jexlEngineStrict); |
| } |
| } |
| } |
| return engine; |
| } |
| |
| /** |
| * Create a new context which is the summation of contexts from the |
| * 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 |
| * document root. |
| */ |
| protected JexlContext getEffectiveContext(final JexlContext nodeCtx) { |
| return new JexlContext(nodeCtx, new EffectiveContextMap(nodeCtx)); |
| } |
| } |
| |