View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.scxml.env.jexl;
18  
19  import java.io.Serializable;
20  import java.util.ArrayList;
21  import java.util.HashMap;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.regex.Pattern;
25  
26  import org.apache.commons.jexl.Expression;
27  import org.apache.commons.jexl.ExpressionFactory;
28  import org.apache.commons.scxml.Context;
29  import org.apache.commons.scxml.Evaluator;
30  import org.apache.commons.scxml.SCXMLExpressionException;
31  import org.w3c.dom.Node;
32  
33  /***
34   * Evaluator implementation enabling use of JEXL expressions in
35   * SCXML documents.
36   *
37   */
38  public class JexlEvaluator implements Evaluator, Serializable {
39  
40      /*** Serial version UID. */
41      private static final long serialVersionUID = 1L;
42  
43      /*** Error message if evaluation context is not a JexlContext. */
44      private static final String ERR_CTX_TYPE = "Error evaluating JEXL "
45          + "expression, Context must be a org.apache.commons.jexl.JexlContext";
46  
47      /*** Pattern for recognizing the SCXML In() special predicate. */
48      private static Pattern inFct = Pattern.compile("In//(");
49      /*** Pattern for recognizing the Commons SCXML Data() builtin function. */
50      private static Pattern dataFct = Pattern.compile("Data//(");
51  
52      /*** Constructor. */
53      public JexlEvaluator() {
54          super();
55      }
56  
57      /***
58       * Evaluate an expression.
59       *
60       * @param ctx variable context
61       * @param expr expression
62       * @return a result of the evaluation
63       * @throws SCXMLExpressionException For a malformed expression
64       * @see Evaluator#eval(Context, String)
65       */
66      public Object eval(final Context ctx, final String expr)
67      throws SCXMLExpressionException {
68          if (expr == null) {
69              return null;
70          }
71          JexlContext jexlCtx = null;
72          if (ctx instanceof JexlContext) {
73              jexlCtx = (JexlContext) ctx;
74          } else {
75              throw new SCXMLExpressionException(ERR_CTX_TYPE);
76          }
77          Expression exp = null;
78          try {
79              String evalExpr = inFct.matcher(expr).
80                  replaceAll("_builtin.isMember(_ALL_STATES, ");
81              evalExpr = dataFct.matcher(evalExpr).
82                  replaceAll("_builtin.data(_ALL_NAMESPACES, ");
83              exp = ExpressionFactory.createExpression(evalExpr);
84              return exp.evaluate(getEffectiveContext(jexlCtx));
85          } catch (Exception e) {
86              throw new SCXMLExpressionException(e);
87          }
88      }
89  
90      /***
91       * @see Evaluator#evalCond(Context, String)
92       */
93      public Boolean evalCond(final Context ctx, final String expr)
94      throws SCXMLExpressionException {
95          if (expr == null) {
96              return null;
97          }
98          JexlContext jexlCtx = null;
99          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).
107                 replaceAll("_builtin.isMember(_ALL_STATES, ");
108             evalExpr = dataFct.matcher(evalExpr).
109                 replaceAll("_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).
134                 replaceAll("_builtin.isMember(_ALL_STATES, ");
135             evalExpr = dataFct.matcher(evalExpr).
136                 replaceFirst("_builtin.dataNode(_ALL_NAMESPACES, ");
137             evalExpr = dataFct.matcher(evalExpr).
138                 replaceAll("_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 }
183