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    }