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 }