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.commons.logging.LogFactory; 018 import org.apache.hadoop.conf.Configuration; 019 import org.apache.hadoop.util.ReflectionUtils; 020 import org.apache.oozie.client.OozieClient.SYSTEM_MODE; 021 import org.apache.oozie.util.XLog; 022 import org.apache.oozie.util.Instrumentable; 023 import org.apache.oozie.util.IOUtils; 024 import org.apache.oozie.ErrorCode; 025 026 import java.util.ArrayList; 027 import java.util.Collections; 028 import java.util.LinkedHashMap; 029 import java.util.List; 030 import java.util.Map; 031 import java.io.IOException; 032 import java.io.File; 033 034 /** 035 * Services is a singleton that manages the lifecycle of all registered {@link Services}. <p/> It has 2 built in 036 * services: {@link XLogService} and {@link ConfigurationService}. <p/> The rest of the services are loaded from the 037 * {@link #CONF_SERVICE_CLASSES} configuration property. The services class names must be separated by commas (spaces 038 * and enters are allowed). <p/> The {@link #CONF_SYSTEM_MODE} configuration property is any of 039 * NORMAL/SAFEMODE/NOWEBSERVICE. <p/> Services are loaded and initialized in the order they are defined in the in 040 * configuration property. <p/> After all services are initialized, if the Instrumentation service is present, all 041 * services that implement the {@link Instrumentable} are instrumented. <p/> Services are destroyed in reverse order. 042 * <p/> If services initialization fail, initialized services are immediatly destroyed. 043 */ 044 public class Services { 045 private static final int MAX_SYSTEM_ID_LEN = 10; 046 047 /** 048 * Environment variable that indicates the location of the Oozie home directory. 049 * The Oozie home directory is used to pick up the conf/ directory from 050 */ 051 public static final String OOZIE_HOME_ENV = "OOZIE_HOME"; 052 053 public static final String CONF_SYSTEM_ID = "oozie.system.id"; 054 055 public static final String CONF_SERVICE_CLASSES = "oozie.services"; 056 057 public static final String CONF_SERVICE_EXT_CLASSES = "oozie.services.ext"; 058 059 public static final String CONF_SYSTEM_MODE = "oozie.systemmode"; 060 061 public static final String CONF_DELETE_RUNTIME_DIR = "oozie.delete.runtime.dir.on.shutdown"; 062 063 private static Services SERVICES; 064 065 private SYSTEM_MODE systemMode; 066 private String runtimeDir; 067 private Configuration conf; 068 private Map<Class<? extends Service>, Service> services = new LinkedHashMap<Class<? extends Service>, Service>(); 069 private String systemId; 070 071 public static String getOozieHome() throws ServiceException { 072 String oozieHome = System.getProperty(OOZIE_HOME_ENV); 073 if (oozieHome == null) { 074 oozieHome = System.getenv(OOZIE_HOME_ENV); 075 } 076 if (oozieHome == null) { 077 throw new ServiceException(ErrorCode.E0000); 078 } 079 if (!oozieHome.startsWith("/")) { 080 throw new ServiceException(ErrorCode.E0003, oozieHome); 081 } 082 File file = new File(oozieHome); 083 if (!file.exists()) { 084 throw new ServiceException(ErrorCode.E0004, oozieHome); 085 } 086 // This value is used by log4j default configuration to point the logs to ${OOZIE_HOME}/logs 087 System.setProperty("oozie.home", oozieHome); 088 return oozieHome; 089 } 090 091 /** 092 * Create a services. <p/> The built in services are initialized. 093 * 094 * @throws ServiceException thrown if any of the built in services could not initialize. 095 */ 096 public Services() throws ServiceException { 097 getOozieHome(); 098 if (SERVICES != null) { 099 XLog log = XLog.getLog(getClass()); 100 log.warn(XLog.OPS, "Previous services singleton active, destroying it"); 101 SERVICES.destroy(); 102 SERVICES = null; 103 } 104 setServiceInternal(XLogService.class, false); 105 setServiceInternal(ConfigurationService.class, true); 106 conf = get(ConfigurationService.class).getConf(); 107 systemId = conf.get(CONF_SYSTEM_ID, ("oozie-" + System.getProperty("user.name"))); 108 if (systemId.length() > MAX_SYSTEM_ID_LEN) { 109 systemId = systemId.substring(0, MAX_SYSTEM_ID_LEN); 110 XLog.getLog(getClass()).warn("System ID [{0}] exceeds maximun lenght [{1}], trimming", systemId, 111 MAX_SYSTEM_ID_LEN); 112 } 113 setSystemMode(SYSTEM_MODE.valueOf(conf.get(CONF_SYSTEM_MODE, SYSTEM_MODE.NORMAL.toString()))); 114 runtimeDir = createRuntimeDir(); 115 } 116 117 private String createRuntimeDir() throws ServiceException { 118 try { 119 File file = File.createTempFile(getSystemId(), ".dir"); 120 file.delete(); 121 if (!file.mkdir()) { 122 ServiceException ex = new ServiceException(ErrorCode.E0001, file.getAbsolutePath()); 123 XLog.getLog(getClass()).fatal(ex); 124 throw ex; 125 } 126 XLog.getLog(getClass()).info("Initialized runtime directory [{0}]", file.getAbsolutePath()); 127 return file.getAbsolutePath(); 128 } 129 catch (IOException ex) { 130 ServiceException sex = new ServiceException(ErrorCode.E0001, ex); 131 XLog.getLog(getClass()).fatal(ex); 132 throw sex; 133 } 134 } 135 136 /** 137 * Return active system mode. <p/> . 138 * 139 * @return 140 */ 141 142 public SYSTEM_MODE getSystemMode() { 143 return systemMode; 144 } 145 146 /** 147 * Return the runtime directory of the Oozie instance. <p/> The directory is created under TMP and it is always a 148 * new directory per Services initialization. 149 * 150 * @return the runtime directory of the Oozie instance. 151 */ 152 public String getRuntimeDir() { 153 return runtimeDir; 154 } 155 156 /** 157 * Return the system ID, the value defined in the {@link #CONF_SYSTEM_ID} configuration property. 158 * 159 * @return the system ID, the value defined in the {@link #CONF_SYSTEM_ID} configuration property. 160 */ 161 public String getSystemId() { 162 return systemId; 163 } 164 165 /** 166 * Set and set system mode. 167 * 168 * @param . 169 */ 170 171 public synchronized void setSystemMode(SYSTEM_MODE sysMode) { 172 if (this.systemMode != sysMode) { 173 XLog log = XLog.getLog(getClass()); 174 log.info(XLog.OPS, "Exiting " + this.systemMode + " Entering " + sysMode); 175 } 176 this.systemMode = sysMode; 177 } 178 179 /** 180 * Return the services configuration. 181 * 182 * @return services configuraiton. 183 */ 184 public Configuration getConf() { 185 return conf; 186 } 187 188 /** 189 * Initialize all services define in the {@link #CONF_SERVICE_CLASSES} configuration property. 190 * 191 * @throws ServiceException thrown if any of the services could not initialize. 192 */ 193 @SuppressWarnings("unchecked") 194 public void init() throws ServiceException { 195 XLog log = new XLog(LogFactory.getLog(getClass())); 196 log.trace("Initializing"); 197 SERVICES = this; 198 try { 199 Class<? extends Service>[] serviceClasses = (Class<? extends Service>[]) conf.getClasses( 200 CONF_SERVICE_CLASSES); 201 if (serviceClasses != null) { 202 for (Class<? extends Service> serviceClass : serviceClasses) { 203 setService(serviceClass); 204 } 205 } 206 serviceClasses = (Class<? extends Service>[]) conf.getClasses(CONF_SERVICE_EXT_CLASSES); 207 if (serviceClasses != null) { 208 for (Class<? extends Service> serviceClass : serviceClasses) { 209 setService(serviceClass); 210 } 211 } 212 } 213 catch (RuntimeException ex) { 214 XLog.getLog(getClass()).fatal(ex.getMessage(), ex); 215 throw ex; 216 } 217 catch (ServiceException ex) { 218 SERVICES = null; 219 throw ex; 220 } 221 InstrumentationService instrService = get(InstrumentationService.class); 222 if (instrService != null) { 223 for (Service service : services.values()) { 224 if (service instanceof Instrumentable) { 225 ((Instrumentable) service).instrument(instrService.get()); 226 } 227 } 228 } 229 log.info("Initialized"); 230 log.info("Oozie System ID [{0}] started!", getSystemId()); 231 } 232 233 /** 234 * Destroy all services. 235 */ 236 public void destroy() { 237 XLog log = new XLog(LogFactory.getLog(getClass())); 238 log.trace("Shutting down"); 239 boolean deleteRuntimeDir = false; 240 if (conf != null) { 241 deleteRuntimeDir = conf.getBoolean(CONF_DELETE_RUNTIME_DIR, false); 242 } 243 if (services != null) { 244 List<Service> list = new ArrayList<Service>(services.values()); 245 Collections.reverse(list); 246 for (Service service : list) { 247 try { 248 log.trace("Destroying service[{0}]", service.getInterface()); 249 if (service.getInterface() == XLogService.class) { 250 log.info("Shutdown"); 251 } 252 service.destroy(); 253 } 254 catch (Throwable ex) { 255 log.error("Error destroying service[{0}], {1}", service.getInterface(), ex.getMessage(), ex); 256 } 257 } 258 } 259 if (deleteRuntimeDir) { 260 try { 261 IOUtils.delete(new File(runtimeDir)); 262 } 263 catch (IOException ex) { 264 log.error("Error deleting runtime directory [{0}], {1}", runtimeDir, ex.getMessage(), ex); 265 } 266 } 267 services = null; 268 conf = null; 269 SERVICES = null; 270 } 271 272 /** 273 * Return a service by its public interface. 274 * 275 * @param serviceKlass service public interface. 276 * @return the associated service, or <code>null</code> if not define. 277 */ 278 @SuppressWarnings("unchecked") 279 public <T extends Service> T get(Class<T> serviceKlass) { 280 return (T) services.get(serviceKlass); 281 } 282 283 /** 284 * Set a service programmatically. <p/> The service will be initialized by the services. <p/> If a service is 285 * already defined with the same public interface it will be destroyed. 286 * 287 * @param klass service klass 288 * @throws ServiceException if the service could not be initialized, at this point all services have been 289 * destroyed. 290 */ 291 public void setService(Class<? extends Service> klass) throws ServiceException { 292 setServiceInternal(klass, true); 293 } 294 295 private void setServiceInternal(Class<? extends Service> klass, boolean logging) throws ServiceException { 296 try { 297 Service newService = (Service) ReflectionUtils.newInstance(klass, null); 298 Service oldService = services.get(newService.getInterface()); 299 if (oldService != null) { 300 oldService.destroy(); 301 } 302 if (logging) { 303 XLog log = new XLog(LogFactory.getLog(getClass())); 304 log.trace("Initializing service[{0}] class[{1}]", newService.getInterface(), newService.getClass()); 305 } 306 newService.init(this); 307 services.put(newService.getInterface(), newService); 308 } 309 catch (ServiceException ex) { 310 XLog.getLog(getClass()).fatal(ex.getMessage(), ex); 311 destroy(); 312 throw ex; 313 } 314 } 315 316 /** 317 * Return the services singleton. 318 * 319 * @return services singleton, <code>null</code> if not initialized. 320 */ 321 public static Services get() { 322 return SERVICES; 323 } 324 325 }