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