001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.apache.oozie.util; 020 021import org.apache.commons.el.ExpressionEvaluatorImpl; 022import org.apache.commons.el.ExpressionString; 023 024import javax.servlet.jsp.el.ELException; 025import javax.servlet.jsp.el.ExpressionEvaluator; 026import javax.servlet.jsp.el.FunctionMapper; 027import javax.servlet.jsp.el.VariableResolver; 028import java.lang.reflect.Method; 029import java.lang.reflect.Modifier; 030import java.util.HashMap; 031import java.util.Map; 032 033/** 034 * JSP Expression Language Evaluator. <p> It provides a more convenient way of using the JSP EL Evaluator. 035 */ 036public class ELEvaluator { 037 038 /** 039 * Provides functions and variables for the EL evaluator. <p> All functions and variables in the context of an EL 040 * evaluator are accessible from EL expressions. 041 */ 042 public static class Context implements VariableResolver, FunctionMapper { 043 private Map<String, Object> vars; 044 private Map<String, Method> functions; 045 046 /** 047 * Create an empty context. 048 */ 049 public Context() { 050 vars = new HashMap<String, Object>(); 051 functions = new HashMap<String, Method>(); 052 } 053 054 /** 055 * Add variables to the context. <p> 056 * 057 * @param vars variables to add to the context. 058 */ 059 public void setVariables(Map<String, Object> vars) { 060 this.vars.putAll(vars); 061 } 062 063 /** 064 * Add a variable to the context. <p> 065 * 066 * @param name variable name. 067 * @param value variable value. 068 */ 069 public void setVariable(String name, Object value) { 070 vars.put(name, value); 071 } 072 073 /** 074 * Return a variable from the context. <p> 075 * 076 * @param name variable name. 077 * @return the variable value. 078 */ 079 public Object getVariable(String name) { 080 return vars.get(name); 081 } 082 083 /** 084 * Add a function to the context. <p> 085 * 086 * @param prefix function prefix. 087 * @param functionName function name. 088 * @param method method that will be invoked for the function, it must be a static and public method. 089 */ 090 public void addFunction(String prefix, String functionName, Method method) { 091 if ((method.getModifiers() & (Modifier.PUBLIC | Modifier.STATIC)) != (Modifier.PUBLIC | Modifier.STATIC)) { 092 throw new IllegalArgumentException(XLog.format("Method[{0}] must be public and static", method)); 093 } 094 prefix = (prefix.length() > 0) ? prefix + ":" : ""; 095 functions.put(prefix + functionName, method); 096 } 097 098 /** 099 * Resolve a variable name. Used by the EL evaluator implemenation. <p> 100 * 101 * @param name variable name. 102 * @return the variable value. 103 * @throws ELException thrown if the variable is not defined in the context. 104 */ 105 public Object resolveVariable(String name) throws ELException { 106 if (!vars.containsKey(name)) { 107 throw new ELException(XLog.format("variable [{0}] cannot be resolved", name)); 108 } 109 return vars.get(name); 110 } 111 112 /** 113 * Resolve a function prefix:name. Used by the EL evaluator implementation. <p> 114 * 115 * @param prefix function prefix. 116 * @param name function name. 117 * @return the method associated to the function. 118 */ 119 public Method resolveFunction(String prefix, String name) { 120 if (prefix.length() > 0) { 121 name = prefix + ":" + name; 122 } 123 return functions.get(name); 124 } 125 } 126 127 private static ThreadLocal<ELEvaluator> current = new ThreadLocal<ELEvaluator>(); 128 129 /** 130 * If within the scope of a EL evaluation call, it gives access to the ELEvaluator instance performing the EL 131 * evaluation. <p> This is useful for EL function methods to get access to the variables of the Evaluator. Because 132 * of this, ELEvaluator variables can be used to pass context to EL function methods (which must be static methods). 133 * <p> 134 * 135 * @return the ELEvaluator in scope, or <code>null</code> if none. 136 */ 137 public static ELEvaluator getCurrent() { 138 return current.get(); 139 } 140 141 private Context context; 142 143 private ExpressionEvaluatorImpl evaluator = new ExpressionEvaluatorImpl(); 144 145 /** 146 * Creates an ELEvaluator with no functions and no variables defined. 147 */ 148 public ELEvaluator() { 149 this(new Context()); 150 } 151 152 /** 153 * Creates an ELEvaluator with the functions and variables defined in the given {@link ELEvaluator.Context}. <p> 154 * 155 * @param context the ELSupport with functions and variables to be available for EL evalution. 156 */ 157 public ELEvaluator(Context context) { 158 this.context = context; 159 } 160 161 /** 162 * Return the context with the functions and variables of the EL evaluator. <p> 163 * 164 * @return the context. 165 */ 166 public Context getContext() { 167 return context; 168 } 169 170 /** 171 * Convenience method that sets a variable in the EL evaluator context. <p> 172 * 173 * @param name variable name. 174 * @param value variable value. 175 */ 176 public void setVariable(String name, Object value) { 177 context.setVariable(name, value); 178 } 179 180 /** 181 * Convenience method that returns a variable from the EL evaluator context. <p> 182 * 183 * @param name variable name. 184 * @return the variable value, <code>null</code> if not defined. 185 */ 186 public Object getVariable(String name) { 187 return context.getVariable(name); 188 } 189 190 /** 191 * Evaluate an EL expression. <p> 192 * 193 * @param <T> the return type of the expression 194 * @param expr EL expression to evaluate. 195 * @param clazz return type of the EL expression. 196 * @return the object the EL expression evaluated to. 197 * @throws Exception thrown if an EL function failed due to a transient error or EL expression could not be 198 * evaluated. 199 */ 200 @SuppressWarnings({"unchecked", "deprecation"}) 201 public <T> T evaluate(String expr, Class<T> clazz) throws Exception { 202 ELEvaluator existing = current.get(); 203 try { 204 current.set(this); 205 return (T) evaluator.evaluate(expr, clazz, context, context); 206 } 207 catch (ELException ex) { 208 if (ex.getRootCause() instanceof Exception) { 209 throw (Exception) ex.getRootCause(); 210 } 211 else { 212 throw ex; 213 } 214 } 215 finally { 216 current.set(existing); 217 } 218 } 219 220 /** 221 * Check if the input expression contains sequence statically. for example 222 * identify if "," is present outside of a function invocation in the given 223 * expression. Ex "${func('abc')},${func('def'}", 224 * 225 * @param expr - Expression string 226 * @param sequence - char sequence to check in the input expression 227 * @return true if present 228 * @throws Exception Exception thrown if an EL function failed due to a 229 * transient error or EL expression could not be parsed 230 */ 231 public boolean checkForExistence(String expr, String sequence) 232 throws Exception { 233 try { 234 Object exprString = evaluator.parseExpressionString(expr); 235 if (exprString instanceof ExpressionString) { 236 for (Object element : ((ExpressionString)exprString).getElements()) { 237 if (element instanceof String && 238 element.toString().contains(sequence)) { 239 return true; 240 } 241 } 242 } else if (exprString instanceof String) { 243 if (((String)exprString).contains(sequence)) { 244 return true; 245 } 246 } 247 return false; 248 } catch (ELException ex) { 249 if (ex.getRootCause() instanceof Exception) { 250 throw (Exception) ex.getRootCause(); 251 } 252 else { 253 throw ex; 254 } 255 } 256 } 257}