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