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("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