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