blob: 0f11d0feaafd81ad7d0aadb1453f67b4d213803a [file] [log] [blame]
/*
* 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 org.apache.commons.jexl3.JexlBuilder;
import org.apache.commons.jexl3.JexlExpression;
import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlScript;
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;
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;
/** Constructor. */
public JexlEvaluator() {
jexlEngine = getJexlEngine();
}
@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);
JexlExpression 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);
JexlExpression 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#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 JexlScript 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() {
// 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<>();
funcs.put(null, JexlBuiltin.class);
return new JexlBuilder().namespaces(funcs).cache(256).create();
}
/**
* 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();
}
}
}
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));
}
}