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). 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