001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.commons.scxml.env.jexl;
018:
019: import java.io.Serializable;
020: import java.util.ArrayList;
021: import java.util.HashMap;
022: import java.util.List;
023: import java.util.Map;
024: import java.util.regex.Pattern;
025:
026: import org.apache.commons.jexl.Expression;
027: import org.apache.commons.jexl.ExpressionFactory;
028: import org.apache.commons.scxml.Context;
029: import org.apache.commons.scxml.Evaluator;
030: import org.apache.commons.scxml.SCXMLExpressionException;
031: import org.w3c.dom.Node;
032:
033: /**
034: * Evaluator implementation enabling use of JEXL expressions in
035: * SCXML documents.
036: *
037: */
038: public class JexlEvaluator implements Evaluator, Serializable {
039:
040: /** Serial version UID. */
041: private static final long serialVersionUID = 1L;
042:
043: /** Error message if evaluation context is not a JexlContext. */
044: private static final String ERR_CTX_TYPE = "Error evaluating JEXL "
045: + "expression, Context must be a org.apache.commons.jexl.JexlContext";
046:
047: /** Pattern for recognizing the SCXML In() special predicate. */
048: private static Pattern inFct = Pattern.compile("In\\(");
049: /** Pattern for recognizing the Commons SCXML Data() builtin function. */
050: private static Pattern dataFct = Pattern.compile("Data\\(");
051:
052: /** Constructor. */
053: public JexlEvaluator() {
054: super ();
055: }
056:
057: /**
058: * Evaluate an expression.
059: *
060: * @param ctx variable context
061: * @param expr expression
062: * @return a result of the evaluation
063: * @throws SCXMLExpressionException For a malformed expression
064: * @see Evaluator#eval(Context, String)
065: */
066: public Object eval(final Context ctx, final String expr)
067: throws SCXMLExpressionException {
068: if (expr == null) {
069: return null;
070: }
071: JexlContext jexlCtx = null;
072: if (ctx instanceof JexlContext) {
073: jexlCtx = (JexlContext) ctx;
074: } else {
075: throw new SCXMLExpressionException(ERR_CTX_TYPE);
076: }
077: Expression exp = null;
078: try {
079: String evalExpr = inFct.matcher(expr).replaceAll(
080: "_builtin.isMember(_ALL_STATES, ");
081: evalExpr = dataFct.matcher(evalExpr).replaceAll(
082: "_builtin.data(_ALL_NAMESPACES, ");
083: exp = ExpressionFactory.createExpression(evalExpr);
084: return exp.evaluate(getEffectiveContext(jexlCtx));
085: } catch (Exception e) {
086: throw new SCXMLExpressionException(e);
087: }
088: }
089:
090: /**
091: * @see Evaluator#evalCond(Context, String)
092: */
093: public Boolean evalCond(final Context ctx, final String expr)
094: throws SCXMLExpressionException {
095: if (expr == null) {
096: return null;
097: }
098: JexlContext jexlCtx = null;
099: if (ctx instanceof JexlContext) {
100: jexlCtx = (JexlContext) ctx;
101: } else {
102: throw new SCXMLExpressionException(ERR_CTX_TYPE);
103: }
104: Expression exp = null;
105: try {
106: String evalExpr = inFct.matcher(expr).replaceAll(
107: "_builtin.isMember(_ALL_STATES, ");
108: evalExpr = dataFct.matcher(evalExpr).replaceAll(
109: "_builtin.data(_ALL_NAMESPACES, ");
110: exp = ExpressionFactory.createExpression(evalExpr);
111: return (Boolean) exp.evaluate(getEffectiveContext(jexlCtx));
112: } catch (Exception e) {
113: throw new SCXMLExpressionException(e);
114: }
115: }
116:
117: /**
118: * @see Evaluator#evalLocation(Context, String)
119: */
120: public Node evalLocation(final Context ctx, final String expr)
121: throws SCXMLExpressionException {
122: if (expr == null) {
123: return null;
124: }
125: JexlContext jexlCtx = null;
126: if (ctx instanceof JexlContext) {
127: jexlCtx = (JexlContext) ctx;
128: } else {
129: throw new SCXMLExpressionException(ERR_CTX_TYPE);
130: }
131: Expression exp = null;
132: try {
133: String evalExpr = inFct.matcher(expr).replaceAll(
134: "_builtin.isMember(_ALL_STATES, ");
135: evalExpr = dataFct.matcher(evalExpr).replaceFirst(
136: "_builtin.dataNode(_ALL_NAMESPACES, ");
137: evalExpr = dataFct.matcher(evalExpr).replaceAll(
138: "_builtin.data(_ALL_NAMESPACES, ");
139: exp = ExpressionFactory.createExpression(evalExpr);
140: return (Node) exp.evaluate(getEffectiveContext(jexlCtx));
141: } catch (Exception e) {
142: throw new SCXMLExpressionException(e);
143: }
144: }
145:
146: /**
147: * Create a new child context.
148: *
149: * @param parent parent context
150: * @return new child context
151: * @see Evaluator#newContext(Context)
152: */
153: public Context newContext(final Context parent) {
154: return new JexlContext(parent);
155: }
156:
157: /**
158: * Create a new context which is the summation of contexts from the
159: * current state to document root, child has priority over parent
160: * in scoping rules.
161: *
162: * @param nodeCtx The JexlContext for this state.
163: * @return The effective JexlContext for the path leading up to
164: * document root.
165: */
166: private JexlContext getEffectiveContext(final JexlContext nodeCtx) {
167: List contexts = new ArrayList();
168: // trace path to root
169: JexlContext currentCtx = nodeCtx;
170: while (currentCtx != null) {
171: contexts.add(currentCtx);
172: currentCtx = (JexlContext) currentCtx.getParent();
173: }
174: Map vars = new HashMap();
175: // summation of the contexts, parent first, child wins
176: for (int i = contexts.size() - 1; i > -1; i--) {
177: vars.putAll(((JexlContext) contexts.get(i)).getVars());
178: }
179: return new JexlContext(vars);
180: }
181:
182: }
|