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 }