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