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.service;
016    
017    import org.apache.oozie.util.XLog;
018    import org.apache.oozie.util.ELEvaluator;
019    import org.apache.oozie.ErrorCode;
020    import org.apache.hadoop.conf.Configuration;
021    
022    import java.lang.reflect.Field;
023    import java.lang.reflect.Method;
024    import java.lang.reflect.Modifier;
025    import java.util.ArrayList;
026    import java.util.HashMap;
027    import java.util.List;
028    
029    /**
030     * The ELService creates {@link ELEvaluator} instances preconfigured with constants and functions defined in the
031     * configuration. <p/> The following configuration parameters control the EL service: <p/> {@link #CONF_CONSTANTS} list
032     * of constant definitions to be available for EL evaluations. <p/> {@link #CONF_FUNCTIONS} list of function definitions
033     * to be available for EL evalations. <p/> Definitions must be separated by a comma, definitions are trimmed. <p/> The
034     * syntax for a constant definition is <code>PREFIX:NAME=CLASS_NAME#CONSTANT_NAME</code>. <p/> The syntax for a constant
035     * definition is <code>PREFIX:NAME=CLASS_NAME#METHOD_NAME</code>.
036     */
037    public class ELService implements Service {
038    
039        public static final String CONF_PREFIX = Service.CONF_PREFIX + "ELService.";
040    
041        public static final String CONF_CONSTANTS = CONF_PREFIX + "constants.";
042    
043        public static final String CONF_EXT_CONSTANTS = CONF_PREFIX + "ext.constants.";
044    
045        public static final String CONF_FUNCTIONS = CONF_PREFIX + "functions.";
046    
047        public static final String CONF_EXT_FUNCTIONS = CONF_PREFIX + "ext.functions.";
048    
049        public static final String CONF_GROUPS = CONF_PREFIX + "groups";
050    
051        private final XLog log = XLog.getLog(getClass());
052    
053        //<Group Name>, <List of constants>
054        private HashMap<String, List<ELConstant>> constants;
055        //<Group Name>, <List of functions>
056        private HashMap<String, List<ELFunction>> functions;
057    
058        private static class ELConstant {
059            private String name;
060            private Object value;
061    
062            private ELConstant(String prefix, String name, Object value) {
063                if (prefix.length() > 0) {
064                    name = prefix + ":" + name;
065                }
066                this.name = name;
067                this.value = value;
068            }
069        }
070    
071        private static class ELFunction {
072            private String prefix;
073            private String name;
074            private Method method;
075    
076            private ELFunction(String prefix, String name, Method method) {
077                this.prefix = prefix;
078                this.name = name;
079                this.method = method;
080            }
081        }
082    
083        private List<ELService.ELConstant> extractConstants(Configuration conf, String key) throws ServiceException {
084            List<ELService.ELConstant> list = new ArrayList<ELService.ELConstant>();
085            if (conf.get(key, "").trim().length() > 0) {
086                for (String function : conf.getStrings(key)) {
087                    String[] parts = parseDefinition(function);
088                    list.add(new ELConstant(parts[0], parts[1], findConstant(parts[2], parts[3])));
089                    log.trace("Registered prefix:constant[{0}:{1}] for class#field[{2}#{3}]", (Object[]) parts);
090                }
091            }
092            return list;
093        }
094    
095        private List<ELService.ELFunction> extractFunctions(Configuration conf, String key) throws ServiceException {
096            List<ELService.ELFunction> list = new ArrayList<ELService.ELFunction>();
097            if (conf.get(key, "").trim().length() > 0) {
098                for (String function : conf.getStrings(key)) {
099                    String[] parts = parseDefinition(function);
100                    list.add(new ELFunction(parts[0], parts[1], findMethod(parts[2], parts[3])));
101                    log.trace("Registered prefix:constant[{0}:{1}] for class#field[{2}#{3}]", (Object[]) parts);
102                }
103            }
104            return list;
105        }
106    
107        /**
108         * Initialize the EL service.
109         *
110         * @param services services instance.
111         * @throws ServiceException thrown if the EL service could not be initialized.
112         */
113        @Override
114        public synchronized void init(Services services) throws ServiceException {
115            log.trace("Constants and functions registration");
116            constants = new HashMap<String, List<ELConstant>>();
117            functions = new HashMap<String, List<ELFunction>>();
118            //Get the list of group names from configuration file
119            // defined in the property tag: oozie.service.ELSerice.groups
120            //String []groupList = services.getConf().get(CONF_GROUPS, "").trim().split(",");
121            String[] groupList = services.getConf().getStrings(CONF_GROUPS, "");
122            //For each group, collect the required functions and constants
123            // and store it into HashMap
124            for (String group : groupList) {
125                List<ELConstant> tmpConstants = new ArrayList<ELConstant>();
126                tmpConstants.addAll(extractConstants(services.getConf(), CONF_CONSTANTS + group));
127                tmpConstants.addAll(extractConstants(services.getConf(), CONF_EXT_CONSTANTS + group));
128                constants.put(group, tmpConstants);
129                List<ELFunction> tmpFunctions = new ArrayList<ELFunction>();
130                tmpFunctions.addAll(extractFunctions(services.getConf(), CONF_FUNCTIONS + group));
131                tmpFunctions.addAll(extractFunctions(services.getConf(), CONF_EXT_FUNCTIONS + group));
132                functions.put(group, tmpFunctions);
133            }
134        }
135    
136        /**
137         * Destroy the EL service.
138         */
139        @Override
140        public void destroy() {
141            constants = null;
142            functions = null;
143        }
144    
145        /**
146         * Return the public interface for EL service.
147         *
148         * @return {@link ELService}.
149         */
150        @Override
151        public Class<? extends Service> getInterface() {
152            return ELService.class;
153        }
154    
155        /**
156         * Return an {@link ELEvaluator} pre-configured with the constants and functions for the specific group of
157         * EL-functions and variables defined in the configuration. If the group name doesn't exist,
158         * IllegalArgumentException is thrown
159         *
160         * @param group: Name of the group of required EL Evaluator.
161         * @return a preconfigured {@link ELEvaluator}.
162         */
163        public ELEvaluator createEvaluator(String group) {
164            ELEvaluator.Context context = new ELEvaluator.Context();
165            boolean groupDefined = false;
166            if (constants.containsKey(group)) {
167                for (ELConstant constant : constants.get(group)) {
168                    context.setVariable(constant.name, constant.value);
169                }
170                groupDefined = true;
171            }
172            if (functions.containsKey(group)) {
173                for (ELFunction function : functions.get(group)) {
174                    context.addFunction(function.prefix, function.name, function.method);
175                }
176                groupDefined = true;
177            }
178            if (groupDefined == false) {
179                throw new IllegalArgumentException("Group " + group + " is not defined");
180            }
181            return new ELEvaluator(context);
182        }
183    
184        private static String[] parseDefinition(String str) throws ServiceException {
185            try {
186                str = str.trim();
187                if (!str.contains(":")) {
188                    str = ":" + str;
189                }
190                String[] parts = str.split(":");
191                String prefix = parts[0];
192                parts = parts[1].split("=");
193                String name = parts[0];
194                parts = parts[1].split("#");
195                String klass = parts[0];
196                String method = parts[1];
197                return new String[]{prefix, name, klass, method};
198            }
199            catch (Exception ex) {
200                throw new ServiceException(ErrorCode.E0110, str, ex.getMessage(), ex);
201            }
202        }
203    
204        public static Method findMethod(String className, String methodName) throws ServiceException {
205            Method method = null;
206            try {
207                Class klass = Thread.currentThread().getContextClassLoader().loadClass(className);
208                for (Method m : klass.getMethods()) {
209                    if (m.getName().equals(methodName)) {
210                        method = m;
211                        break;
212                    }
213                }
214                if (method == null) {
215                    throw new ServiceException(ErrorCode.E0111, className, methodName);
216                }
217                if ((method.getModifiers() & (Modifier.PUBLIC | Modifier.STATIC)) != (Modifier.PUBLIC | Modifier.STATIC)) {
218                    throw new ServiceException(ErrorCode.E0112, className, methodName);
219                }
220            }
221            catch (ClassNotFoundException ex) {
222                throw new ServiceException(ErrorCode.E0113, className);
223            }
224            return method;
225        }
226    
227        public static Object findConstant(String className, String constantName) throws ServiceException {
228            try {
229                Class klass = Thread.currentThread().getContextClassLoader().loadClass(className);
230                Field field = klass.getField(constantName);
231                if ((field.getModifiers() & (Modifier.PUBLIC | Modifier.STATIC)) != (Modifier.PUBLIC | Modifier.STATIC)) {
232                    throw new ServiceException(ErrorCode.E0114, className, constantName);
233                }
234                return field.get(null);
235            }
236            catch (IllegalAccessException ex) {
237                throw new IllegalArgumentException(ex);
238            }
239            catch (NoSuchFieldException ex) {
240                throw new ServiceException(ErrorCode.E0115, className, constantName);
241            }
242            catch (ClassNotFoundException ex) {
243                throw new ServiceException(ErrorCode.E0113, className);
244            }
245        }
246    
247    }