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