Java tutorial
/* * 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; } @Override public boolean requiresGlobalContext() { return false; } /** * 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)); } }