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