001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.commons.scxml.env.jsp;
018:
019: import java.io.Serializable;
020: import java.lang.reflect.Method;
021: import java.util.Map;
022: import java.util.Set;
023: import java.util.regex.Pattern;
024:
025: import javax.servlet.jsp.el.ELException;
026: import javax.servlet.jsp.el.ExpressionEvaluator;
027: import javax.servlet.jsp.el.FunctionMapper;
028: import javax.servlet.jsp.el.VariableResolver;
029:
030: import org.apache.commons.el.ExpressionEvaluatorImpl;
031: import org.apache.commons.logging.Log;
032: import org.apache.commons.logging.LogFactory;
033: import org.apache.commons.scxml.Builtin;
034: import org.apache.commons.scxml.Context;
035: import org.apache.commons.scxml.Evaluator;
036: import org.apache.commons.scxml.SCXMLExpressionException;
037: import org.w3c.dom.Node;
038:
039: /**
040: * Evaluator implementation enabling use of EL expressions in
041: * SCXML documents.
042: *
043: */
044: public class ELEvaluator implements Evaluator, Serializable {
045:
046: /** Serial version UID. */
047: private static final long serialVersionUID = 1L;
048: /** Implementation independent log category. */
049: private Log log = LogFactory.getLog(Evaluator.class);
050: /** Function Mapper for SCXML builtin functions. */
051: private FunctionMapper builtinFnMapper = new BuiltinFunctionMapper();
052: /** User provided function mapper, we delegate to this mapper if
053: we encounter a function that is not built into SCXML. */
054: private FunctionMapper fnMapper;
055: /** Pattern for recognizing the SCXML In() special predicate. */
056: private static Pattern inFct = Pattern.compile("In\\(");
057: /** Pattern for recognizing the Commons SCXML Data() builtin function. */
058: private static Pattern dataFct = Pattern.compile("Data\\(");
059:
060: /** The expression evaluator implementation for the JSP/EL environment. */
061: private transient ExpressionEvaluator ee = null;
062:
063: /**
064: * Constructor.
065: */
066: public ELEvaluator() {
067: ee = new ExpressionEvaluatorImpl();
068: }
069:
070: /**
071: * Constructor for EL evaluator that supports user-defined functions.
072: *
073: * @param fnMapper The function mapper for this Evaluator.
074: * @see javax.servlet.jsp.el.FunctionMapper
075: */
076: public ELEvaluator(final FunctionMapper fnMapper) {
077: ee = new ExpressionEvaluatorImpl();
078: this .fnMapper = fnMapper;
079: }
080:
081: /**
082: * Evaluate an expression.
083: *
084: * @param ctx variable context
085: * @param expr expression
086: * @return a result of the evaluation
087: * @throws SCXMLExpressionException For a malformed expression
088: * @see Evaluator#eval(Context, String)
089: */
090: public Object eval(final Context ctx, final String expr)
091: throws SCXMLExpressionException {
092: if (expr == null) {
093: return null;
094: }
095: VariableResolver vr = null;
096: if (ctx instanceof VariableResolver) {
097: vr = (VariableResolver) ctx;
098: } else {
099: vr = new ContextWrapper(ctx);
100: }
101: try {
102: String evalExpr = inFct.matcher(expr).replaceAll(
103: "In(_ALL_STATES, ");
104: evalExpr = dataFct.matcher(evalExpr).replaceAll(
105: "Data(_ALL_NAMESPACES, ");
106: Object rslt = getEvaluator().evaluate(evalExpr,
107: Object.class, vr, 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).replaceAll(
133: "In(_ALL_STATES, ");
134: evalExpr = dataFct.matcher(evalExpr).replaceAll(
135: "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).replaceAll(
163: "In(_ALL_STATES, ");
164: evalExpr = dataFct.matcher(evalExpr).replaceAll(
165: "Data(_ALL_NAMESPACES, ");
166: evalExpr = dataFct.matcher(evalExpr).replaceFirst("LData(");
167: Node rslt = (Node) getEvaluator().evaluate(evalExpr,
168: Node.class, vr, builtinFnMapper);
169: if (log.isDebugEnabled()) {
170: log.debug(expr + " = " + String.valueOf(rslt));
171: }
172: return rslt;
173: } catch (ELException e) {
174: throw new SCXMLExpressionException(e);
175: }
176: }
177:
178: /**
179: * Create a new child context.
180: *
181: * @param parent parent context
182: * @return new child context
183: * @see Evaluator#newContext(Context)
184: */
185: public Context newContext(final Context parent) {
186: return new ELContext(parent);
187: }
188:
189: /**
190: * Set the log used by this <code>Evaluator</code> instance.
191: *
192: * @param log The new log.
193: */
194: protected void setLog(final Log log) {
195: this .log = log;
196: }
197:
198: /**
199: * Get the log used by this <code>Evaluator</code> instance.
200: *
201: * @return Log The log being used.
202: */
203: protected Log getLog() {
204: return log;
205: }
206:
207: /**
208: * Get the <code>ExpressionEvaluator</code>, with lazy initialization.
209: *
210: * @return Log The log being used.
211: */
212: private ExpressionEvaluator getEvaluator() {
213: if (ee == null) {
214: ee = new ExpressionEvaluatorImpl();
215: }
216: return ee;
217: }
218:
219: /**
220: * A Context wrapper that implements VariableResolver.
221: */
222: static class ContextWrapper implements VariableResolver,
223: 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: /**
232: * Constructor.
233: * @param ctx The Context to be wrapped.
234: */
235: ContextWrapper(final Context ctx) {
236: this .ctx = ctx;
237: }
238:
239: /** @see VariableResolver#resolveVariable(String) */
240: public Object resolveVariable(final String pName)
241: 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
258: .getLog(BuiltinFunctionMapper.class);
259:
260: /**
261: * @see FunctionMapper#resolveFunction(String, String)
262: */
263: public Method resolveFunction(final String prefix,
264: final String localName) {
265: if (localName.equals("In")) {
266: Class[] attrs = new Class[] { Set.class, String.class };
267: try {
268: return Builtin.class.getMethod("isMember", attrs);
269: } catch (SecurityException e) {
270: log.error("resolving isMember(Set, String)", e);
271: } catch (NoSuchMethodException e) {
272: log.error("resolving isMember(Set, String)", e);
273: }
274: } else if (localName.equals("Data")) {
275: // rvalue in expressions, coerce to String
276: Class[] attrs = new Class[] { Map.class, Object.class,
277: String.class };
278: try {
279: return Builtin.class.getMethod("data", attrs);
280: } catch (SecurityException e) {
281: log.error("resolving data(Node, String)", e);
282: } catch (NoSuchMethodException e) {
283: log.error("resolving data(Node, String)", e);
284: }
285: } else if (localName.equals("LData")) {
286: // lvalue in expressions, retain as Node
287: Class[] attrs = new Class[] { Map.class, Object.class,
288: String.class };
289: try {
290: return Builtin.class.getMethod("dataNode", attrs);
291: } catch (SecurityException e) {
292: log.error("resolving data(Node, String)", e);
293: } catch (NoSuchMethodException e) {
294: log.error("resolving data(Node, String)", e);
295: }
296: } else if (fnMapper != null) {
297: return fnMapper.resolveFunction(prefix, localName);
298: }
299: return null;
300: }
301: }
302:
303: /**
304: * Get the FunctionMapper for builtin SCXML/Commons SCXML functions.
305: *
306: * @return builtinFnMapper The FunctionMapper
307: */
308: protected FunctionMapper getBuiltinFnMapper() {
309: return builtinFnMapper;
310: }
311:
312: }
|