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 }