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.jexl; 018 019 import java.io.Serializable; 020 import java.util.ArrayList; 021 import java.util.HashMap; 022 import java.util.List; 023 import java.util.Map; 024 import java.util.regex.Pattern; 025 026 import org.apache.commons.jexl.Expression; 027 import org.apache.commons.jexl.ExpressionFactory; 028 import org.apache.commons.scxml.Context; 029 import org.apache.commons.scxml.Evaluator; 030 import org.apache.commons.scxml.SCXMLExpressionException; 031 import org.w3c.dom.Node; 032 033 /** 034 * Evaluator implementation enabling use of JEXL expressions in 035 * SCXML documents. 036 * 037 */ 038 public class JexlEvaluator implements Evaluator, Serializable { 039 040 /** Serial version UID. */ 041 private static final long serialVersionUID = 1L; 042 043 /** Error message if evaluation context is not a JexlContext. */ 044 private static final String ERR_CTX_TYPE = "Error evaluating JEXL " 045 + "expression, Context must be a org.apache.commons.jexl.JexlContext"; 046 047 /** Pattern for recognizing the SCXML In() special predicate. */ 048 private static Pattern inFct = Pattern.compile("In\\("); 049 /** Pattern for recognizing the Commons SCXML Data() builtin function. */ 050 private static Pattern dataFct = Pattern.compile("Data\\("); 051 052 /** Constructor. */ 053 public JexlEvaluator() { 054 super(); 055 } 056 057 /** 058 * Evaluate an expression. 059 * 060 * @param ctx variable context 061 * @param expr expression 062 * @return a result of the evaluation 063 * @throws SCXMLExpressionException For a malformed expression 064 * @see Evaluator#eval(Context, String) 065 */ 066 public Object eval(final Context ctx, final String expr) 067 throws SCXMLExpressionException { 068 if (expr == null) { 069 return null; 070 } 071 JexlContext jexlCtx = null; 072 if (ctx instanceof JexlContext) { 073 jexlCtx = (JexlContext) ctx; 074 } else { 075 throw new SCXMLExpressionException(ERR_CTX_TYPE); 076 } 077 Expression exp = null; 078 try { 079 String evalExpr = inFct.matcher(expr). 080 replaceAll("_builtin.isMember(_ALL_STATES, "); 081 evalExpr = dataFct.matcher(evalExpr). 082 replaceAll("_builtin.data(_ALL_NAMESPACES, "); 083 exp = ExpressionFactory.createExpression(evalExpr); 084 return exp.evaluate(getEffectiveContext(jexlCtx)); 085 } catch (Exception e) { 086 throw new SCXMLExpressionException("eval('" + expr + "'):" 087 + e.getMessage(), e); 088 } 089 } 090 091 /** 092 * @see Evaluator#evalCond(Context, String) 093 */ 094 public Boolean evalCond(final Context ctx, final String expr) 095 throws SCXMLExpressionException { 096 if (expr == null) { 097 return null; 098 } 099 JexlContext jexlCtx = null; 100 if (ctx instanceof JexlContext) { 101 jexlCtx = (JexlContext) ctx; 102 } else { 103 throw new SCXMLExpressionException(ERR_CTX_TYPE); 104 } 105 Expression exp = null; 106 try { 107 String evalExpr = inFct.matcher(expr). 108 replaceAll("_builtin.isMember(_ALL_STATES, "); 109 evalExpr = dataFct.matcher(evalExpr). 110 replaceAll("_builtin.data(_ALL_NAMESPACES, "); 111 exp = ExpressionFactory.createExpression(evalExpr); 112 return (Boolean) exp.evaluate(getEffectiveContext(jexlCtx)); 113 } catch (Exception e) { 114 throw new SCXMLExpressionException("eval('" + expr + "'):" 115 + e.getMessage(), e); 116 } 117 } 118 119 /** 120 * @see Evaluator#evalLocation(Context, String) 121 */ 122 public Node evalLocation(final Context ctx, final String expr) 123 throws SCXMLExpressionException { 124 if (expr == null) { 125 return null; 126 } 127 JexlContext jexlCtx = null; 128 if (ctx instanceof JexlContext) { 129 jexlCtx = (JexlContext) ctx; 130 } else { 131 throw new SCXMLExpressionException(ERR_CTX_TYPE); 132 } 133 Expression exp = null; 134 try { 135 String evalExpr = inFct.matcher(expr). 136 replaceAll("_builtin.isMember(_ALL_STATES, "); 137 evalExpr = dataFct.matcher(evalExpr). 138 replaceFirst("_builtin.dataNode(_ALL_NAMESPACES, "); 139 evalExpr = dataFct.matcher(evalExpr). 140 replaceAll("_builtin.data(_ALL_NAMESPACES, "); 141 exp = ExpressionFactory.createExpression(evalExpr); 142 return (Node) exp.evaluate(getEffectiveContext(jexlCtx)); 143 } catch (Exception e) { 144 throw new SCXMLExpressionException("eval('" + expr + "'):" 145 + e.getMessage(), e); 146 } 147 } 148 149 /** 150 * Create a new child context. 151 * 152 * @param parent parent context 153 * @return new child context 154 * @see Evaluator#newContext(Context) 155 */ 156 public Context newContext(final Context parent) { 157 return new JexlContext(parent); 158 } 159 160 /** 161 * Create a new context which is the summation of contexts from the 162 * current state to document root, child has priority over parent 163 * in scoping rules. 164 * 165 * @param nodeCtx The JexlContext for this state. 166 * @return The effective JexlContext for the path leading up to 167 * document root. 168 */ 169 private JexlContext getEffectiveContext(final JexlContext nodeCtx) { 170 List contexts = new ArrayList(); 171 // trace path to root 172 JexlContext currentCtx = nodeCtx; 173 while (currentCtx != null) { 174 contexts.add(currentCtx); 175 currentCtx = (JexlContext) currentCtx.getParent(); 176 } 177 Map vars = new HashMap(); 178 // summation of the contexts, parent first, child wins 179 for (int i = contexts.size() - 1; i > -1; i--) { 180 vars.putAll(((JexlContext) contexts.get(i)).getVars()); 181 } 182 return new JexlContext(vars); 183 } 184 185 } 186