1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
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
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