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    }