    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