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 }