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.jsp;
    18
    19  import java.io.Serializable;
    20  import java.lang.reflect.Method;
    21  import java.util.Map;
    22  import java.util.Set;
    23  import java.util.regex.Pattern;
    24
    25  import javax.servlet.jsp.el.ELException;
    26  import javax.servlet.jsp.el.ExpressionEvaluator;
    27  import javax.servlet.jsp.el.FunctionMapper;
    28  import javax.servlet.jsp.el.VariableResolver;
    29
    30  import org.apache.commons.el.ExpressionEvaluatorImpl;
    31  import org.apache.commons.logging.Log;
    32  import org.apache.commons.logging.LogFactory;
    33  import org.apache.commons.scxml.Builtin;
    34  import org.apache.commons.scxml.Context;
    35  import org.apache.commons.scxml.Evaluator;
    36  import org.apache.commons.scxml.SCXMLExpressionException;
    37  import org.w3c.dom.Node;
    38
    39  /***
    40   * Evaluator implementation enabling use of EL expressions in
    41   * SCXML documents.
    42   *
    43   */
    44  public class ELEvaluator implements Evaluator, Serializable {
    45
    46      /*** Serial version UID. */
    47      private static final long serialVersionUID = 1L;
    48      /*** Implementation independent log category. */
    49      private Log log = LogFactory.getLog(Evaluator.class);
    50      /*** Function Mapper for SCXML builtin functions. */
    51      private FunctionMapper builtinFnMapper = new BuiltinFunctionMapper();
    52      /*** User provided function mapper, we delegate to this mapper if
    53          we encounter a function that is not built into SCXML. */
    54      private FunctionMapper fnMapper;
    55      /*** Pattern for recognizing the SCXML In() special predicate. */
    56      private static Pattern inFct = Pattern.compile("In//(");
    57      /*** Pattern for recognizing the Commons SCXML Data() builtin function. */
    58      private static Pattern dataFct = Pattern.compile("Data//(");
    59
    60      /*** The expression evaluator implementation for the JSP/EL environment. */
    61      private transient ExpressionEvaluator ee = null;
    62
    63      /***
    64       * Constructor.
    65       */
    66      public ELEvaluator() {
    67          ee = new ExpressionEvaluatorImpl();
    68      }
    69
    70      /***
    71       * Constructor for EL evaluator that supports user-defined functions.
    72       *
    73       * @param fnMapper The function mapper for this Evaluator.
    74       * @see javax.servlet.jsp.el.FunctionMapper
    75       */
    76      public ELEvaluator(final FunctionMapper fnMapper) {
    77          ee = new ExpressionEvaluatorImpl();
    78          this.fnMapper = fnMapper;
    79      }
    80
    81      /***
    82       * Evaluate an expression.
    83       *
    84       * @param ctx variable context
    85       * @param expr expression
    86       * @return a result of the evaluation
    87       * @throws SCXMLExpressionException For a malformed expression
    88       * @see Evaluator#eval(Context, String)
    89       */
    90      public Object eval(final Context ctx, final String expr)
    91      throws SCXMLExpressionException {
    92          if (expr == null) {
    93              return null;
    94          }
    95          VariableResolver vr = null;
    96          if (ctx instanceof VariableResolver) {
    97              vr = (VariableResolver) ctx;
    98          } else {
    99              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(e);
    114         }
    115     }
    116
    117     /***
    118      * @see Evaluator#evalCond(Context, String)
    119      */
    120     public Boolean evalCond(final Context ctx, final String expr)
    121     throws SCXMLExpressionException {
    122         if (expr == null) {
    123             return null;
    124         }
    125         VariableResolver vr = null;
    126         if (ctx instanceof VariableResolver) {
    127             vr = (VariableResolver) ctx;
    128         } else {
    129             vr = new ContextWrapper(ctx);
    130         }
    131         try {
    132             String evalExpr = inFct.matcher(expr).
    133                 replaceAll("In(_ALL_STATES, ");
    134             evalExpr = dataFct.matcher(evalExpr).
    135                 replaceAll("Data(_ALL_NAMESPACES, ");
    136             Boolean rslt = (Boolean) getEvaluator().evaluate(evalExpr,
    137                 Boolean.class, vr, builtinFnMapper);
    138             if (log.isDebugEnabled()) {
    139                 log.debug(expr + " = " + String.valueOf(rslt));
    140             }
    141             return rslt;
    142         } catch (ELException e) {
    143             throw new SCXMLExpressionException(e);
    144         }
    145     }
    146
    147     /***
    148      * @see Evaluator#evalLocation(Context, String)
    149      */
    150     public Node evalLocation(final Context ctx, final String expr)
    151     throws SCXMLExpressionException {
    152         if (expr == null) {
    153             return null;
    154         }
    155         VariableResolver vr = null;
    156         if (ctx instanceof VariableResolver) {
    157             vr = (VariableResolver) ctx;
    158         } else {
    159             vr = new ContextWrapper(ctx);
    160         }
    161         try {
    162             String evalExpr = inFct.matcher(expr).
    163                 replaceAll("In(_ALL_STATES, ");
    164             evalExpr = dataFct.matcher(evalExpr).
    165                 replaceAll("Data(_ALL_NAMESPACES, ");
    166             evalExpr = dataFct.matcher(evalExpr).
    167                 replaceFirst("LData(");
    168             Node rslt = (Node) getEvaluator().evaluate(evalExpr, Node.class,
    169                 vr, builtinFnMapper);
    170             if (log.isDebugEnabled()) {
    171                 log.debug(expr + " = " + String.valueOf(rslt));
    172             }
    173             return rslt;
    174         } catch (ELException e) {
    175             throw new SCXMLExpressionException(e);
    176         }
    177     }
    178
    179     /***
    180      * Create a new child context.
    181      *
    182      * @param parent parent context
    183      * @return new child context
    184      * @see Evaluator#newContext(Context)
    185      */
    186     public Context newContext(final Context parent) {
    187         return new ELContext(parent);
    188     }
    189
    190     /***
    191      * Set the log used by this <code>Evaluator</code> instance.
    192      *
    193      * @param log The new log.
    194      */
    195     protected void setLog(final Log log) {
    196         this.log = log;
    197     }
    198
    199     /***
    200      * Get the log used by this <code>Evaluator</code> instance.
    201      *
    202      * @return Log The log being used.
    203      */
    204     protected Log getLog() {
    205         return log;
    206     }
    207
    208     /***
    209      * Get the <code>ExpressionEvaluator</code>, with lazy initialization.
    210      *
    211      * @return Log The log being used.
    212      */
    213     private ExpressionEvaluator getEvaluator() {
    214         if (ee == null) {
    215             ee = new ExpressionEvaluatorImpl();
    216         }
    217         return ee;
    218     }
    219
    220     /***
    221      * A Context wrapper that implements VariableResolver.
    222      */
    223     static class ContextWrapper implements VariableResolver, Serializable {
    224         /*** Serial version UID. */
    225         private static final long serialVersionUID = 1L;
    226         /*** Context to be wrapped. */
    227         private Context ctx = null;
    228         /*** The log. */
    229         private Log log = LogFactory.getLog(ContextWrapper.class);
    230         /***
    231          * Constructor.
    232          * @param ctx The Context to be wrapped.
    233          */
    234         ContextWrapper(final Context ctx) {
    235             this.ctx = ctx;
    236         }
    237         /*** @see VariableResolver#resolveVariable(String) */
    238         public Object resolveVariable(final String pName) throws ELException {
    239             Object rslt = ctx.get(pName);
    240             if (rslt == null) {
    241                 log.info("Variable \"" + pName + "\" does not exist!");
    242             }
    243             return rslt;
    244         }
    245     }
    246
    247     /***
    248      * A simple function mapper for SCXML defined functions.
    249      */
    250     class BuiltinFunctionMapper implements FunctionMapper, Serializable {
    251         /*** Serial version UID. */
    252         private static final long serialVersionUID = 1L;
    253         /*** The log. */
    254         private Log log = LogFactory.getLog(BuiltinFunctionMapper.class);
    255         /***
    256          * @see FunctionMapper#resolveFunction(String, String)
    257          */
    258         public Method resolveFunction(final String prefix,
    259                 final String localName) {
    260             if (localName.equals("In")) {
    261                 Class[] attrs = new Class[] {Set.class, String.class};
    262                 try {
    263                     return Builtin.class.getMethod("isMember", attrs);
    264                 } catch (SecurityException e) {
    265                     log.error("resolving isMember(Set, String)", e);
    266                 } catch (NoSuchMethodException e) {
    267                     log.error("resolving isMember(Set, String)", e);
    268                 }
    269             } else if (localName.equals("Data")) {
    270                 // rvalue in expressions, coerce to String
    271                 Class[] attrs =
    272                     new Class[] {Map.class, Object.class, String.class};
    273                 try {
    274                     return Builtin.class.getMethod("data", attrs);
    275                 } catch (SecurityException e) {
    276                     log.error("resolving data(Node, String)", e);
    277                 } catch (NoSuchMethodException e) {
    278                     log.error("resolving data(Node, String)", e);
    279                 }
    280             } else if (localName.equals("LData")) {
    281                 // lvalue in expressions, retain as Node
    282                 Class[] attrs =
    283                     new Class[] {Map.class, Object.class, String.class};
    284                 try {
    285                     return Builtin.class.getMethod("dataNode", attrs);
    286                 } catch (SecurityException e) {
    287                     log.error("resolving data(Node, String)", e);
    288                 } catch (NoSuchMethodException e) {
    289                     log.error("resolving data(Node, String)", e);
    290                 }
    291             } else if (fnMapper != null) {
    292                 return fnMapper.resolveFunction(prefix, localName);
    293             }
    294             return null;
    295         }
    296     }
    297
    298     /***
    299      * Get the FunctionMapper for builtin SCXML/Commons SCXML functions.
    300      *
    301      * @return builtinFnMapper The FunctionMapper
    302      */
    303     protected FunctionMapper getBuiltinFnMapper() {
    304         return builtinFnMapper;
    305     }
    306
    307 }
    308