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