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    }