All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.hadoop.lib.server.Server Maven / Gradle / Ivy

The newest version!
/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.hadoop.lib.server;

import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.conf.ConfigRedactor;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.lib.util.Check;
import org.apache.hadoop.lib.util.ConfigurationUtils;
import org.apache.hadoop.util.StringUtils;
import org.apache.log4j.LogManager;
import org.apache.log4j.PropertyConfigurator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * A Server class provides standard configuration, logging and {@link Service}
 * lifecyle management.
 * 

* A Server normally has a home directory, a configuration directory, a temp * directory and logs directory. *

* The Server configuration is loaded from 2 overlapped files, * #SERVER#-default.xml and #SERVER#-site.xml. The * default file is loaded from the classpath, the site file is laoded from the * configuration directory. *

* The Server collects all configuration properties prefixed with * #SERVER#. The property names are then trimmed from the * #SERVER# prefix. *

* The Server log configuration is loaded from the * #SERVICE#-log4j.properties file in the configuration directory. *

* The lifecycle of server is defined in by {@link Server.Status} enum. * When a server is create, its status is UNDEF, when being initialized it is * BOOTING, once initialization is complete by default transitions to NORMAL. * The #SERVER#.startup.status configuration property can be used * to specify a different startup status (NORMAL, ADMIN or HALTED). *

* Services classes are defined in the #SERVER#.services and * #SERVER#.services.ext properties. They are loaded in order * (services first, then services.ext). *

* Before initializing the services, they are traversed and duplicate service * interface are removed from the service list. The last service using a given * interface wins (this enables a simple override mechanism). *

* After the services have been resoloved by interface de-duplication they are * initialized in order. Once all services are initialized they are * post-initialized (this enables late/conditional service bindings). */ @InterfaceAudience.Private public class Server { private Logger log; /** * Server property name that defines the service classes. */ public static final String CONF_SERVICES = "services"; /** * Server property name that defines the service extension classes. */ public static final String CONF_SERVICES_EXT = "services.ext"; /** * Server property name that defines server startup status. */ public static final String CONF_STARTUP_STATUS = "startup.status"; /** * Enumeration that defines the server status. */ @InterfaceAudience.Private public enum Status { UNDEF(false, false), BOOTING(false, true), HALTED(true, true), ADMIN(true, true), NORMAL(true, true), SHUTTING_DOWN(false, true), SHUTDOWN(false, false); private boolean settable; private boolean operational; /** * Status constructor. * * @param settable indicates if the status is settable. * @param operational indicates if the server is operational * when in this status. */ private Status(boolean settable, boolean operational) { this.settable = settable; this.operational = operational; } /** * Returns if this server status is operational. * * @return if this server status is operational. */ public boolean isOperational() { return operational; } } /** * Name of the log4j configuration file the Server will load from the * classpath if the #SERVER#-log4j.properties is not defined * in the server configuration directory. */ public static final String DEFAULT_LOG4J_PROPERTIES = "default-log4j.properties"; private Status status; private String name; private String homeDir; private String configDir; private String logDir; private String tempDir; private Configuration config; private Map services = new LinkedHashMap(); /** * Creates a server instance. *

* The config, log and temp directories are all under the specified home directory. * * @param name server name. * @param homeDir server home directory. */ public Server(String name, String homeDir) { this(name, homeDir, null); } /** * Creates a server instance. * * @param name server name. * @param homeDir server home directory. * @param configDir config directory. * @param logDir log directory. * @param tempDir temp directory. */ public Server(String name, String homeDir, String configDir, String logDir, String tempDir) { this(name, homeDir, configDir, logDir, tempDir, null); } /** * Creates a server instance. *

* The config, log and temp directories are all under the specified home directory. *

* It uses the provided configuration instead loading it from the config dir. * * @param name server name. * @param homeDir server home directory. * @param config server configuration. */ public Server(String name, String homeDir, Configuration config) { this(name, homeDir, homeDir + "/conf", homeDir + "/log", homeDir + "/temp", config); } /** * Creates a server instance. *

* It uses the provided configuration instead loading it from the config dir. * * @param name server name. * @param homeDir server home directory. * @param configDir config directory. * @param logDir log directory. * @param tempDir temp directory. * @param config server configuration. */ public Server(String name, String homeDir, String configDir, String logDir, String tempDir, Configuration config) { this.name = StringUtils.toLowerCase(Check.notEmpty(name, "name").trim()); this.homeDir = Check.notEmpty(homeDir, "homeDir"); this.configDir = Check.notEmpty(configDir, "configDir"); this.logDir = Check.notEmpty(logDir, "logDir"); this.tempDir = Check.notEmpty(tempDir, "tempDir"); checkAbsolutePath(homeDir, "homeDir"); checkAbsolutePath(configDir, "configDir"); checkAbsolutePath(logDir, "logDir"); checkAbsolutePath(tempDir, "tempDir"); if (config != null) { this.config = new Configuration(false); ConfigurationUtils.copy(config, this.config); } status = Status.UNDEF; } /** * Validates that the specified value is an absolute path (starts with '/'). * * @param value value to verify it is an absolute path. * @param name name to use in the exception if the value is not an absolute * path. * * @return the value. * * @throws IllegalArgumentException thrown if the value is not an absolute * path. */ private String checkAbsolutePath(String value, String name) { if (!new File(value).isAbsolute()) { throw new IllegalArgumentException( MessageFormat.format("[{0}] must be an absolute path [{1}]", name, value)); } return value; } /** * Returns the current server status. * * @return the current server status. */ public Status getStatus() { return status; } /** * Sets a new server status. *

* The status must be settable. *

* All services will be notified o the status change via the * {@link Service#serverStatusChange(Server.Status, Server.Status)} method. If a service * throws an exception during the notification, the server will be destroyed. * * @param status status to set. * * @throws ServerException thrown if the service has been destroy because of * a failed notification to a service. */ public void setStatus(Status status) throws ServerException { Check.notNull(status, "status"); if (status.settable) { if (status != this.status) { Status oldStatus = this.status; this.status = status; for (Service service : services.values()) { try { service.serverStatusChange(oldStatus, status); } catch (Exception ex) { log.error("Service [{}] exception during status change to [{}] -server shutting down-, {}", new Object[]{service.getInterface().getSimpleName(), status, ex.getMessage(), ex}); destroy(); throw new ServerException(ServerException.ERROR.S11, service.getInterface().getSimpleName(), status, ex.getMessage(), ex); } } } } else { throw new IllegalArgumentException("Status [" + status + " is not settable"); } } /** * Verifies the server is operational. * * @throws IllegalStateException thrown if the server is not operational. */ protected void ensureOperational() { if (!getStatus().isOperational()) { throw new IllegalStateException("Server is not running"); } } /** * Convenience method that returns a resource as inputstream from the * classpath. *

* It first attempts to use the Thread's context classloader and if not * set it uses the ClassUtils classloader. * * @param name resource to retrieve. * * @return inputstream with the resource, NULL if the resource does not * exist. */ static InputStream getResource(String name) { Check.notEmpty(name, "name"); ClassLoader cl = Thread.currentThread().getContextClassLoader(); if (cl == null) { cl = Server.class.getClassLoader(); } return cl.getResourceAsStream(name); } /** * Initializes the Server. *

* The initialization steps are: *

    *
  • It verifies the service home and temp directories exist
  • *
  • Loads the Server #SERVER#-default.xml * configuration file from the classpath
  • *
  • Initializes log4j logging. If the * #SERVER#-log4j.properties file does not exist in the config * directory it load default-log4j.properties from the classpath *
  • *
  • Loads the #SERVER#-site.xml file from the server config * directory and merges it with the default configuration.
  • *
  • Loads the services
  • *
  • Initializes the services
  • *
  • Post-initializes the services
  • *
  • Sets the server startup status
  • *
* * @throws ServerException thrown if the server could not be initialized. */ public void init() throws ServerException { if (status != Status.UNDEF) { throw new IllegalStateException("Server already initialized"); } status = Status.BOOTING; verifyDir(homeDir); verifyDir(tempDir); Properties serverInfo = new Properties(); try { InputStream is = getResource(name + ".properties"); serverInfo.load(is); is.close(); } catch (IOException ex) { throw new RuntimeException("Could not load server information file: " + name + ".properties"); } initLog(); log.info("++++++++++++++++++++++++++++++++++++++++++++++++++++++"); log.info("Server [{}] starting", name); log.info(" Built information:"); log.info(" Version : {}", serverInfo.getProperty(name + ".version", "undef")); log.info(" Source Repository : {}", serverInfo.getProperty(name + ".source.repository", "undef")); log.info(" Source Revision : {}", serverInfo.getProperty(name + ".source.revision", "undef")); log.info(" Built by : {}", serverInfo.getProperty(name + ".build.username", "undef")); log.info(" Built timestamp : {}", serverInfo.getProperty(name + ".build.timestamp", "undef")); log.info(" Runtime information:"); log.info(" Home dir: {}", homeDir); log.info(" Config dir: {}", (config == null) ? configDir : "-"); log.info(" Log dir: {}", logDir); log.info(" Temp dir: {}", tempDir); initConfig(); log.debug("Loading services"); List list = loadServices(); try { log.debug("Initializing services"); initServices(list); log.info("Services initialized"); } catch (ServerException ex) { log.error("Services initialization failure, destroying initialized services"); destroyServices(); throw ex; } Status status = Status.valueOf(getConfig().get(getPrefixedName(CONF_STARTUP_STATUS), Status.NORMAL.toString())); setStatus(status); log.info("Server [{}] started!, status [{}]", name, status); } /** * Verifies the specified directory exists. * * @param dir directory to verify it exists. * * @throws ServerException thrown if the directory does not exist or it the * path it is not a directory. */ private void verifyDir(String dir) throws ServerException { File file = new File(dir); if (!file.exists()) { throw new ServerException(ServerException.ERROR.S01, dir); } if (!file.isDirectory()) { throw new ServerException(ServerException.ERROR.S02, dir); } } /** * Initializes Log4j logging. * * @throws ServerException thrown if Log4j could not be initialized. */ protected void initLog() throws ServerException { verifyDir(logDir); LogManager.resetConfiguration(); File log4jFile = new File(configDir, name + "-log4j.properties"); if (log4jFile.exists()) { PropertyConfigurator.configureAndWatch(log4jFile.toString(), 10 * 1000); //every 10 secs log = LoggerFactory.getLogger(Server.class); } else { Properties props = new Properties(); try { InputStream is = getResource(DEFAULT_LOG4J_PROPERTIES); try { props.load(is); } finally { is.close(); } } catch (IOException ex) { throw new ServerException(ServerException.ERROR.S03, DEFAULT_LOG4J_PROPERTIES, ex.getMessage(), ex); } PropertyConfigurator.configure(props); log = LoggerFactory.getLogger(Server.class); log.warn("Log4j [{}] configuration file not found, using default configuration from classpath", log4jFile); } } /** * Loads and inializes the server configuration. * * @throws ServerException thrown if the configuration could not be loaded/initialized. */ protected void initConfig() throws ServerException { verifyDir(configDir); File file = new File(configDir); Configuration defaultConf; String defaultConfig = name + "-default.xml"; ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); InputStream inputStream = classLoader.getResourceAsStream(defaultConfig); if (inputStream == null) { log.warn("Default configuration file not available in classpath [{}]", defaultConfig); defaultConf = new Configuration(false); } else { try { defaultConf = new Configuration(false); ConfigurationUtils.load(defaultConf, inputStream); } catch (Exception ex) { throw new ServerException(ServerException.ERROR.S03, defaultConfig, ex.getMessage(), ex); } } if (config == null) { Configuration siteConf; File siteFile = new File(file, name + "-site.xml"); if (!siteFile.exists()) { log.warn("Site configuration file [{}] not found in config directory", siteFile); siteConf = new Configuration(false); } else { if (!siteFile.isFile()) { throw new ServerException(ServerException.ERROR.S05, siteFile.getAbsolutePath()); } try { log.debug("Loading site configuration from [{}]", siteFile); inputStream = Files.newInputStream(siteFile.toPath()); siteConf = new Configuration(false); ConfigurationUtils.load(siteConf, inputStream); } catch (IOException ex) { throw new ServerException(ServerException.ERROR.S06, siteFile, ex.getMessage(), ex); } } config = new Configuration(false); ConfigurationUtils.copy(siteConf, config); } ConfigurationUtils.injectDefaults(defaultConf, config); ConfigRedactor redactor = new ConfigRedactor(config); for (String name : System.getProperties().stringPropertyNames()) { String value = System.getProperty(name); if (name.startsWith(getPrefix() + ".")) { config.set(name, value); String redacted = redactor.redact(name, value); log.info("System property sets {}: {}", name, redacted); } } log.debug("Loaded Configuration:"); log.debug("------------------------------------------------------"); for (Map.Entry entry : config) { String name = entry.getKey(); String value = config.get(entry.getKey()); String redacted = redactor.redact(name, value); log.debug(" {}: {}", entry.getKey(), redacted); } log.debug("------------------------------------------------------"); } /** * Loads the specified services. * * @param classes services classes to load. * @param list list of loaded service in order of appearance in the * configuration. * * @throws ServerException thrown if a service class could not be loaded. */ private void loadServices(Class[] classes, List list) throws ServerException { for (Class klass : classes) { try { Service service = (Service) klass.newInstance(); log.debug("Loading service [{}] implementation [{}]", service.getInterface(), service.getClass()); if (!service.getInterface().isInstance(service)) { throw new ServerException(ServerException.ERROR.S04, klass, service.getInterface().getName()); } list.add(service); } catch (ServerException ex) { throw ex; } catch (Exception ex) { throw new ServerException(ServerException.ERROR.S07, klass, ex.getMessage(), ex); } } } /** * Loads services defined in services and * services.ext and de-dups them. * * @return List of final services to initialize. * * @throws ServerException throw if the services could not be loaded. */ protected List loadServices() throws ServerException { try { Map map = new LinkedHashMap(); Class[] classes = getConfig().getClasses(getPrefixedName(CONF_SERVICES)); Class[] classesExt = getConfig().getClasses(getPrefixedName(CONF_SERVICES_EXT)); List list = new ArrayList(); loadServices(classes, list); loadServices(classesExt, list); //removing duplicate services, strategy: last one wins for (Service service : list) { if (map.containsKey(service.getInterface())) { log.debug("Replacing service [{}] implementation [{}]", service.getInterface(), service.getClass()); } map.put(service.getInterface(), service); } list = new ArrayList(); for (Map.Entry entry : map.entrySet()) { list.add(entry.getValue()); } return list; } catch (RuntimeException ex) { throw new ServerException(ServerException.ERROR.S08, ex.getMessage(), ex); } } /** * Initializes the list of services. * * @param services services to initialized, it must be a de-dupped list of * services. * * @throws ServerException thrown if the services could not be initialized. */ protected void initServices(List services) throws ServerException { for (Service service : services) { log.debug("Initializing service [{}]", service.getInterface()); checkServiceDependencies(service); service.init(this); this.services.put(service.getInterface(), service); } for (Service service : services) { service.postInit(); } } /** * Checks if all service dependencies of a service are available. * * @param service service to check if all its dependencies are available. * * @throws ServerException thrown if a service dependency is missing. */ protected void checkServiceDependencies(Service service) throws ServerException { if (service.getServiceDependencies() != null) { for (Class dependency : service.getServiceDependencies()) { if (services.get(dependency) == null) { throw new ServerException(ServerException.ERROR.S10, service.getClass(), dependency); } } } } /** * Destroys the server services. */ protected void destroyServices() { List list = new ArrayList(services.values()); Collections.reverse(list); for (Service service : list) { try { log.debug("Destroying service [{}]", service.getInterface()); service.destroy(); } catch (Throwable ex) { log.error("Could not destroy service [{}], {}", new Object[]{service.getInterface(), ex.getMessage(), ex}); } } log.info("Services destroyed"); } /** * Destroys the server. *

* All services are destroyed in reverse order of initialization, then the * Log4j framework is shutdown. */ public void destroy() { ensureOperational(); destroyServices(); log.info("Server [{}] shutdown!", name); log.info("======================================================"); if (!Boolean.getBoolean("test.circus")) { LogManager.shutdown(); } status = Status.SHUTDOWN; } /** * Returns the name of the server. * * @return the server name. */ public String getName() { return name; } /** * Returns the server prefix for server configuration properties. *

* By default it is the server name. * * @return the prefix for server configuration properties. */ public String getPrefix() { return getName(); } /** * Returns the prefixed name of a server property. * * @param name of the property. * * @return prefixed name of the property. */ public String getPrefixedName(String name) { return getPrefix() + "." + Check.notEmpty(name, "name"); } /** * Returns the server home dir. * * @return the server home dir. */ public String getHomeDir() { return homeDir; } /** * Returns the server config dir. * * @return the server config dir. */ public String getConfigDir() { return configDir; } /** * Returns the server log dir. * * @return the server log dir. */ public String getLogDir() { return logDir; } /** * Returns the server temp dir. * * @return the server temp dir. */ public String getTempDir() { return tempDir; } /** * Returns the server configuration. * * @return the server configuration. */ public Configuration getConfig() { return config; } /** * Returns the {@link Service} associated to the specified interface. * * @param serviceKlass service interface. * * @return the service implementation. */ @SuppressWarnings("unchecked") public T get(Class serviceKlass) { ensureOperational(); Check.notNull(serviceKlass, "serviceKlass"); return (T) services.get(serviceKlass); } /** * Adds a service programmatically. *

* If a service with the same interface exists, it will be destroyed and * removed before the given one is initialized and added. *

* If an exception is thrown the server is destroyed. * * @param klass service class to add. * * @throws ServerException throw if the service could not initialized/added * to the server. */ public void setService(Class klass) throws ServerException { ensureOperational(); Check.notNull(klass, "serviceKlass"); if (getStatus() == Status.SHUTTING_DOWN) { throw new IllegalStateException("Server shutting down"); } try { Service newService = klass.newInstance(); Service oldService = services.get(newService.getInterface()); if (oldService != null) { try { oldService.destroy(); } catch (Throwable ex) { log.error("Could not destroy service [{}], {}", new Object[]{oldService.getInterface(), ex.getMessage(), ex}); } } newService.init(this); services.put(newService.getInterface(), newService); } catch (Exception ex) { log.error("Could not set service [{}] programmatically -server shutting down-, {}", klass, ex); destroy(); throw new ServerException(ServerException.ERROR.S09, klass, ex.getMessage(), ex); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy