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).
080 replaceAll("_builtin.isMember(_ALL_STATES, ");
081 evalExpr = dataFct.matcher(evalExpr).
082 replaceAll("_builtin.data(_ALL_NAMESPACES, ");
083 exp = ExpressionFactory.createExpression(evalExpr);
084 return exp.evaluate(getEffectiveContext(jexlCtx));
085 } catch (Exception e) {
086 throw new SCXMLExpressionException("eval('" + expr + "'):"
087 + e.getMessage(), e);
088 }
089 }
090
091 /**
092 * @see Evaluator#evalCond(Context, String)
093 */
094 public Boolean evalCond(final Context ctx, final String expr)
095 throws SCXMLExpressionException {
096 if (expr == null) {
097 return null;
098 }
099 JexlContext jexlCtx = null;
100 if (ctx instanceof JexlContext) {
101 jexlCtx = (JexlContext) ctx;
102 } else {
103 throw new SCXMLExpressionException(ERR_CTX_TYPE);
104 }
105 Expression exp = null;
106 try {
107 String evalExpr = inFct.matcher(expr).
108 replaceAll("_builtin.isMember(_ALL_STATES, ");
109 evalExpr = dataFct.matcher(evalExpr).
110 replaceAll("_builtin.data(_ALL_NAMESPACES, ");
111 exp = ExpressionFactory.createExpression(evalExpr);
112 return (Boolean) exp.evaluate(getEffectiveContext(jexlCtx));
113 } catch (Exception e) {
114 throw new SCXMLExpressionException("eval('" + expr + "'):"
115 + e.getMessage(), e);
116 }
117 }
118
119 /**
120 * @see Evaluator#evalLocation(Context, String)
121 */
122 public Node evalLocation(final Context ctx, final String expr)
123 throws SCXMLExpressionException {
124 if (expr == null) {
125 return null;
126 }
127 JexlContext jexlCtx = null;
128 if (ctx instanceof JexlContext) {
129 jexlCtx = (JexlContext) ctx;
130 } else {
131 throw new SCXMLExpressionException(ERR_CTX_TYPE);
132 }
133 Expression exp = null;
134 try {
135 String evalExpr = inFct.matcher(expr).
136 replaceAll("_builtin.isMember(_ALL_STATES, ");
137 evalExpr = dataFct.matcher(evalExpr).
138 replaceFirst("_builtin.dataNode(_ALL_NAMESPACES, ");
139 evalExpr = dataFct.matcher(evalExpr).
140 replaceAll("_builtin.data(_ALL_NAMESPACES, ");
141 exp = ExpressionFactory.createExpression(evalExpr);
142 return (Node) exp.evaluate(getEffectiveContext(jexlCtx));
143 } catch (Exception e) {
144 throw new SCXMLExpressionException("eval('" + expr + "'):"
145 + e.getMessage(), e);
146 }
147 }
148
149 /**
150 * Create a new child context.
151 *
152 * @param parent parent context
153 * @return new child context
154 * @see Evaluator#newContext(Context)
155 */
156 public Context newContext(final Context parent) {
157 return new JexlContext(parent);
158 }
159
160 /**
161 * Create a new context which is the summation of contexts from the
162 * current state to document root, child has priority over parent
163 * in scoping rules.
164 *
165 * @param nodeCtx The JexlContext for this state.
166 * @return The effective JexlContext for the path leading up to
167 * document root.
168 */
169 private JexlContext getEffectiveContext(final JexlContext nodeCtx) {
170 List contexts = new ArrayList();
171 // trace path to root
172 JexlContext currentCtx = nodeCtx;
173 while (currentCtx != null) {
174 contexts.add(currentCtx);
175 currentCtx = (JexlContext) currentCtx.getParent();
176 }
177 Map vars = new HashMap();
178 // summation of the contexts, parent first, child wins
179 for (int i = contexts.size() - 1; i > -1; i--) {
180 vars.putAll(((JexlContext) contexts.get(i)).getVars());
181 }
182 return new JexlContext(vars);
183 }
184
185 }
186