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.hadoop.util.VersionInfo;
021    import org.apache.oozie.client.OozieClient.SYSTEM_MODE;
022    import org.apache.oozie.util.XLog;
023    import org.apache.oozie.util.Instrumentable;
024    import org.apache.oozie.util.IOUtils;
025    import org.apache.oozie.ErrorCode;
026    
027    import java.util.ArrayList;
028    import java.util.Collections;
029    import java.util.LinkedHashMap;
030    import java.util.List;
031    import java.util.Map;
032    import java.io.IOException;
033    import java.io.File;
034    
035    /**
036     * Services is a singleton that manages the lifecycle of all registered {@link Services}. <p/> It has 2 built in
037     * services: {@link XLogService} and {@link ConfigurationService}. <p/> The rest of the services are loaded from the
038     * {@link #CONF_SERVICE_CLASSES} configuration property. The services class names must be separated by commas (spaces
039     * and enters are allowed). <p/> The {@link #CONF_SYSTEM_MODE} configuration property is any of
040     * NORMAL/SAFEMODE/NOWEBSERVICE. <p/> Services are loaded and initialized in the order they are defined in the in
041     * configuration property. <p/> After all services are initialized, if the Instrumentation service is present, all
042     * services that implement the {@link Instrumentable} are instrumented. <p/> Services are destroyed in reverse order.
043     * <p/> If services initialization fail, initialized services are immediatly destroyed.
044     */
045    public class Services {
046        private static final int MAX_SYSTEM_ID_LEN = 10;
047    
048        /**
049         * Environment variable that indicates the location of the Oozie home directory.
050         * The Oozie home directory is used to pick up the conf/ directory from
051         */
052        public static final String OOZIE_HOME_DIR = "oozie.home.dir";
053    
054        public static final String CONF_SYSTEM_ID = "oozie.system.id";
055    
056        public static final String CONF_SERVICE_CLASSES = "oozie.services";
057    
058        public static final String CONF_SERVICE_EXT_CLASSES = "oozie.services.ext";
059    
060        public static final String CONF_SYSTEM_MODE = "oozie.systemmode";
061    
062        public static final String CONF_DELETE_RUNTIME_DIR = "oozie.delete.runtime.dir.on.shutdown";
063    
064        private static Services SERVICES;
065    
066        private SYSTEM_MODE systemMode;
067        private String runtimeDir;
068        private Configuration conf;
069        private Map<Class<? extends Service>, Service> services = new LinkedHashMap<Class<? extends Service>, Service>();
070        private String systemId;
071        private static String oozieHome;
072    
073        public static void setOozieHome() throws ServiceException {
074            oozieHome = System.getProperty(OOZIE_HOME_DIR);
075            if (oozieHome == null) {
076                throw new ServiceException(ErrorCode.E0000);
077            }
078            if (!oozieHome.startsWith("/")) {
079                throw new ServiceException(ErrorCode.E0003, oozieHome);
080            }
081            File file = new File(oozieHome);
082            if (!file.exists()) {
083                throw new ServiceException(ErrorCode.E0004, oozieHome);
084            }
085        }
086    
087        public static String getOozieHome() throws ServiceException {
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            setOozieHome();
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 maximum length [{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 sysMode system mode
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("Running with JARs for Hadoop version [{0}]", VersionInfo.getVersion());
231            log.info("Oozie System ID [{0}] started!", getSystemId());
232        }
233    
234        /**
235         * Destroy all services.
236         */
237        public void destroy() {
238            XLog log = new XLog(LogFactory.getLog(getClass()));
239            log.trace("Shutting down");
240            boolean deleteRuntimeDir = false;
241            if (conf != null) {
242                deleteRuntimeDir = conf.getBoolean(CONF_DELETE_RUNTIME_DIR, false);
243            }
244            if (services != null) {
245                List<Service> list = new ArrayList<Service>(services.values());
246                Collections.reverse(list);
247                for (Service service : list) {
248                    try {
249                        log.trace("Destroying service[{0}]", service.getInterface());
250                        if (service.getInterface() == XLogService.class) {
251                            log.info("Shutdown");
252                        }
253                        service.destroy();
254                    }
255                    catch (Throwable ex) {
256                        log.error("Error destroying service[{0}], {1}", service.getInterface(), ex.getMessage(), ex);
257                    }
258                }
259            }
260            if (deleteRuntimeDir) {
261                try {
262                    IOUtils.delete(new File(runtimeDir));
263                }
264                catch (IOException ex) {
265                    log.error("Error deleting runtime directory [{0}], {1}", runtimeDir, ex.getMessage(), ex);
266                }
267            }
268            services = null;
269            conf = null;
270            SERVICES = null;
271        }
272    
273        /**
274         * Return a service by its public interface.
275         *
276         * @param serviceKlass service public interface.
277         * @return the associated service, or <code>null</code> if not define.
278         */
279        @SuppressWarnings("unchecked")
280        public <T extends Service> T get(Class<T> serviceKlass) {
281            return (T) services.get(serviceKlass);
282        }
283    
284        /**
285         * Set a service programmatically. <p/> The service will be initialized by the services. <p/> If a service is
286         * already defined with the same public interface it will be destroyed.
287         *
288         * @param klass service klass
289         * @throws ServiceException if the service could not be initialized, at this point all services have been
290         * destroyed.
291         */
292        public void setService(Class<? extends Service> klass) throws ServiceException {
293            setServiceInternal(klass, true);
294        }
295    
296        private void setServiceInternal(Class<? extends Service> klass, boolean logging) throws ServiceException {
297            try {
298                Service newService = (Service) ReflectionUtils.newInstance(klass, null);
299                Service oldService = services.get(newService.getInterface());
300                if (oldService != null) {
301                    oldService.destroy();
302                }
303                if (logging) {
304                    XLog log = new XLog(LogFactory.getLog(getClass()));
305                    log.trace("Initializing service[{0}] class[{1}]", newService.getInterface(), newService.getClass());
306                }
307                newService.init(this);
308                services.put(newService.getInterface(), newService);
309            }
310            catch (ServiceException ex) {
311                XLog.getLog(getClass()).fatal(ex.getMessage(), ex);
312                destroy();
313                throw ex;
314            }
315        }
316    
317        /**
318         * Return the services singleton.
319         *
320         * @return services singleton, <code>null</code> if not initialized.
321         */
322        public static Services get() {
323            return SERVICES;
324        }
325    
326    }