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