    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.jsp;
    018
    019    import java.io.Serializable;
    020    import java.lang.reflect.Method;
    021    import java.util.Map;
    022    import java.util.Set;
    023    import java.util.regex.Pattern;
    024
    025    import javax.servlet.jsp.el.ELException;
    026    import javax.servlet.jsp.el.ExpressionEvaluator;
    027    import javax.servlet.jsp.el.FunctionMapper;
    028    import javax.servlet.jsp.el.VariableResolver;
    029
    030    import org.apache.commons.el.ExpressionEvaluatorImpl;
    031    import org.apache.commons.logging.Log;
    032    import org.apache.commons.logging.LogFactory;
    033    import org.apache.commons.scxml.Builtin;
    034    import org.apache.commons.scxml.Context;
    035    import org.apache.commons.scxml.Evaluator;
    036    import org.apache.commons.scxml.SCXMLExpressionException;
    037    import org.w3c.dom.Node;
    038
    039    /**
    040     * Evaluator implementation enabling use of EL expressions in
    041     * SCXML documents.
    042     *
    043     */
    044    public class ELEvaluator implements Evaluator, Serializable {
    045
    046        /** Serial version UID. */
    047        private static final long serialVersionUID = 1L;
    048        /** Implementation independent log category. */
    049        private Log log = LogFactory.getLog(Evaluator.class);
    050        /** Function Mapper for SCXML builtin functions. */
    051        private FunctionMapper builtinFnMapper = new BuiltinFunctionMapper();
    052        /** User provided function mapper, we delegate to this mapper if
    053            we encounter a function that is not built into SCXML. */
    054        private FunctionMapper fnMapper;
    055        /** Pattern for recognizing the SCXML In() special predicate. */
    056        private static Pattern inFct = Pattern.compile("In\\(");
    057        /** Pattern for recognizing the Commons SCXML Data() builtin function. */
    058        private static Pattern dataFct = Pattern.compile("Data\\(");
    059
    060        /** The expression evaluator implementation for the JSP/EL environment. */
    061        private transient ExpressionEvaluator ee = null;
    062
    063        /**
    064         * Constructor.
    065         */
    066        public ELEvaluator() {
    067            ee = new ExpressionEvaluatorImpl();
    068        }
    069
    070        /**
    071         * Constructor for EL evaluator that supports user-defined functions.
    072         *
    073         * @param fnMapper The function mapper for this Evaluator.
    074         * @see javax.servlet.jsp.el.FunctionMapper
    075         */
    076        public ELEvaluator(final FunctionMapper fnMapper) {
    077            ee = new ExpressionEvaluatorImpl();
    078            this.fnMapper = fnMapper;
    079        }
    080
    081        /**
    082         * Evaluate an expression.
    083         *
    084         * @param ctx variable context
    085         * @param expr expression
    086         * @return a result of the evaluation
    087         * @throws SCXMLExpressionException For a malformed expression
    088         * @see Evaluator#eval(Context, String)
    089         */
    090        public Object eval(final Context ctx, final String expr)
    091        throws SCXMLExpressionException {
    092            if (expr == null) {
    093                return null;
    094            }
    095            VariableResolver vr = null;
    096            if (ctx instanceof VariableResolver) {
    097                vr = (VariableResolver) ctx;
    098            } else {
    099                vr = new ContextWrapper(ctx);
    100            }
    101            try {
    102                String evalExpr = inFct.matcher(expr).
    103                    replaceAll("In(_ALL_STATES, ");
    104                evalExpr = dataFct.matcher(evalExpr).
    105                    replaceAll("Data(_ALL_NAMESPACES, ");
    106                Object rslt = getEvaluator().evaluate(evalExpr, Object.class, vr,
    107                    builtinFnMapper);
    108                if (log.isTraceEnabled()) {
    109                    log.trace(expr + " = " + String.valueOf(rslt));
    110                }
    111                return rslt;
    112            } catch (ELException e) {
    113                throw new SCXMLExpressionException("eval('" + expr + "'):"
    114                    + e.getMessage(), e);
    115            }
    116        }
    117
    118        /**
    119         * @see Evaluator#evalCond(Context, String)
    120         */
    121        public Boolean evalCond(final Context ctx, final String expr)
    122        throws SCXMLExpressionException {
    123            if (expr == null) {
    124                return null;
    125            }
    126            VariableResolver vr = null;
    127            if (ctx instanceof VariableResolver) {
    128                vr = (VariableResolver) ctx;
    129            } else {
    130                vr = new ContextWrapper(ctx);
    131            }
    132            try {
    133                String evalExpr = inFct.matcher(expr).
    134                    replaceAll("In(_ALL_STATES, ");
    135                evalExpr = dataFct.matcher(evalExpr).
    136                    replaceAll("Data(_ALL_NAMESPACES, ");
    137                Boolean rslt = (Boolean) getEvaluator().evaluate(evalExpr,
    138                    Boolean.class, vr, builtinFnMapper);
    139                if (log.isDebugEnabled()) {
    140                    log.debug(expr + " = " + String.valueOf(rslt));
    141                }
    142                return rslt;
    143            } catch (ELException e) {
    144                throw new SCXMLExpressionException("eval('" + expr + "'):"
    145                    + e.getMessage(), e);
    146            }
    147        }
    148
    149        /**
    150         * @see Evaluator#evalLocation(Context, String)
    151         */
    152        public Node evalLocation(final Context ctx, final String expr)
    153        throws SCXMLExpressionException {
    154            if (expr == null) {
    155                return null;
    156            }
    157            VariableResolver vr = null;
    158            if (ctx instanceof VariableResolver) {
    159                vr = (VariableResolver) ctx;
    160            } else {
    161                vr = new ContextWrapper(ctx);
    162            }
    163            try {
    164                String evalExpr = inFct.matcher(expr).
    165                    replaceAll("In(_ALL_STATES, ");
    166                evalExpr = dataFct.matcher(evalExpr).
    167                    replaceAll("Data(_ALL_NAMESPACES, ");
    168                evalExpr = dataFct.matcher(evalExpr).
    169                    replaceFirst("LData(");
    170                Node rslt = (Node) getEvaluator().evaluate(evalExpr, Node.class,
    171                    vr, builtinFnMapper);
    172                if (log.isDebugEnabled()) {
    173                    log.debug(expr + " = " + String.valueOf(rslt));
    174                }
    175                return rslt;
    176            } catch (ELException e) {
    177                throw new SCXMLExpressionException("eval('" + expr + "'):"
    178                    + e.getMessage(), e);
    179            }
    180        }
    181
    182        /**
    183         * Create a new child context.
    184         *
    185         * @param parent parent context
    186         * @return new child context
    187         * @see Evaluator#newContext(Context)
    188         */
    189        public Context newContext(final Context parent) {
    190            return new ELContext(parent);
    191        }
    192
    193        /**
    194         * Set the log used by this <code>Evaluator</code> instance.
    195         *
    196         * @param log The new log.
    197         */
    198        protected void setLog(final Log log) {
    199            this.log = log;
    200        }
    201
    202        /**
    203         * Get the log used by this <code>Evaluator</code> instance.
    204         *
    205         * @return Log The log being used.
    206         */
    207        protected Log getLog() {
    208            return log;
    209        }
    210
    211        /**
    212         * Get the <code>ExpressionEvaluator</code>, with lazy initialization.
    213         *
    214         * @return Log The log being used.
    215         */
    216        private ExpressionEvaluator getEvaluator() {
    217            if (ee == null) {
    218                ee = new ExpressionEvaluatorImpl();
    219            }
    220            return ee;
    221        }
    222
    223        /**
    224         * A Context wrapper that implements VariableResolver.
    225         */
    226        static class ContextWrapper implements VariableResolver, Serializable {
    227            /** Serial version UID. */
    228            private static final long serialVersionUID = 1L;
    229            /** Context to be wrapped. */
    230            private Context ctx = null;
    231            /** The log. */
    232            private Log log = LogFactory.getLog(ContextWrapper.class);
    233            /**
    234             * Constructor.
    235             * @param ctx The Context to be wrapped.
    236             */
    237            ContextWrapper(final Context ctx) {
    238                this.ctx = ctx;
    239            }
    240            /** @see VariableResolver#resolveVariable(String) */
    241            public Object resolveVariable(final String pName) throws ELException {
    242                Object rslt = ctx.get(pName);
    243                if (rslt == null) {
    244                    log.info("Variable \"" + pName + "\" does not exist!");
    245                }
    246                return rslt;
    247            }
    248        }
    249
    250        /**
    251         * A simple function mapper for SCXML defined functions.
    252         */
    253        class BuiltinFunctionMapper implements FunctionMapper, Serializable {
    254            /** Serial version UID. */
    255            private static final long serialVersionUID = 1L;
    256            /** The log. */
    257            private Log log = LogFactory.getLog(BuiltinFunctionMapper.class);
    258            /**
    259             * @see FunctionMapper#resolveFunction(String, String)
    260             */
    261            public Method resolveFunction(final String prefix,
    262                    final String localName) {
    263                if (localName.equals("In")) {
    264                    Class[] attrs = new Class[] {Set.class, String.class};
    265                    try {
    266                        return Builtin.class.getMethod("isMember", attrs);
    267                    } catch (SecurityException e) {
    268                        log.error("resolving isMember(Set, String)", e);
    269                    } catch (NoSuchMethodException e) {
    270                        log.error("resolving isMember(Set, String)", e);
    271                    }
    272                } else if (localName.equals("Data")) {
    273                    // rvalue in expressions, coerce to String
    274                    Class[] attrs =
    275                        new Class[] {Map.class, Object.class, String.class};
    276                    try {
    277                        return Builtin.class.getMethod("data", attrs);
    278                    } catch (SecurityException e) {
    279                        log.error("resolving data(Node, String)", e);
    280                    } catch (NoSuchMethodException e) {
    281                        log.error("resolving data(Node, String)", e);
    282                    }
    283                } else if (localName.equals("LData")) {
    284                    // lvalue in expressions, retain as Node
    285                    Class[] attrs =
    286                        new Class[] {Map.class, Object.class, String.class};
    287                    try {
    288                        return Builtin.class.getMethod("dataNode", attrs);
    289                    } catch (SecurityException e) {
    290                        log.error("resolving data(Node, String)", e);
    291                    } catch (NoSuchMethodException e) {
    292                        log.error("resolving data(Node, String)", e);
    293                    }
    294                } else if (fnMapper != null) {
    295                    return fnMapper.resolveFunction(prefix, localName);
    296                }
    297                return null;
    298            }
    299        }
    300
    301        /**
    302         * Get the FunctionMapper for builtin SCXML/Commons SCXML functions.
    303         *
    304         * @return builtinFnMapper The FunctionMapper
    305         */
    306        protected FunctionMapper getBuiltinFnMapper() {
    307            return builtinFnMapper;
    308        }
    309
    310    }
    311