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("eval('" + expr + "'):"
    87                  + e.getMessage(), e);
    88          }
    89      }
    90
    91      /***
    92       * @see Evaluator#evalCond(Context, String)
    93       */
    94      public Boolean evalCond(final Context ctx, final String expr)
    95      throws SCXMLExpressionException {
    96          if (expr == null) {
    97              return null;
    98          }
    99          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