| /** |
| * 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.Configuration; |
| import org.apache.hadoop.lib.util.Check; |
| import org.apache.hadoop.lib.util.ConfigurationUtils; |
| 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.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| 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. |
| * <p/> |
| * A Server normally has a home directory, a configuration directory, a temp |
| * directory and logs directory. |
| * <p/> |
| * The Server configuration is loaded from 2 overlapped files, |
| * <code>#SERVER#-default.xml</code> and <code>#SERVER#-site.xml</code>. The |
| * default file is loaded from the classpath, the site file is laoded from the |
| * configuration directory. |
| * <p/> |
| * The Server collects all configuration properties prefixed with |
| * <code>#SERVER#</code>. The property names are then trimmed from the |
| * <code>#SERVER#</code> prefix. |
| * <p/> |
| * The Server log configuration is loaded from the |
| * <code>#SERVICE#-log4j.properties</code> file in the configuration directory. |
| * <p/> |
| * 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 <code>#SERVER#.startup.status</code> configuration property can be used |
| * to specify a different startup status (NORMAL, ADMIN or HALTED). |
| * <p/> |
| * Services classes are defined in the <code>#SERVER#.services</code> and |
| * <code>#SERVER#.services.ext</code> properties. They are loaded in order |
| * (services first, then services.ext). |
| * <p/> |
| * 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). |
| * <p/> |
| * 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). |
| * <p/> |
| */ |
| @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 static 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 <code>#SERVER#-log4j.properties</code> 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<Class, Service> services = new LinkedHashMap<Class, Service>(); |
| |
| /** |
| * Creates a server instance. |
| * <p/> |
| * 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. |
| * <p/> |
| * The config, log and temp directories are all under the specified home directory. |
| * <p/> |
| * 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. |
| * <p/> |
| * 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 = Check.notEmpty(name, "name").trim().toLowerCase(); |
| 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 (!value.startsWith("/")) { |
| 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. |
| * <p/> |
| * The status must be settable. |
| * <p/> |
| * 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. |
| * <p/> |
| * It first attempts to use the Thread's context classloader and if not |
| * set it uses the <code>ClassUtils</code> 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. |
| * <p/> |
| * The initialization steps are: |
| * <ul> |
| * <li>It verifies the service home and temp directories exist</li> |
| * <li>Loads the Server <code>#SERVER#-default.xml</code> |
| * configuration file from the classpath</li> |
| * <li>Initializes log4j logging. If the |
| * <code>#SERVER#-log4j.properties</code> file does not exist in the config |
| * directory it load <code>default-log4j.properties</code> from the classpath |
| * </li> |
| * <li>Loads the <code>#SERVER#-site.xml</code> file from the server config |
| * directory and merges it with the default configuration.</li> |
| * <li>Loads the services</li> |
| * <li>Initializes the services</li> |
| * <li>Post-initializes the services</li> |
| * <li>Sets the server startup status</li> |
| * |
| * @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<Service> 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); |
| props.load(is); |
| } 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 = new FileInputStream(siteFile); |
| 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); |
| |
| for (String name : System.getProperties().stringPropertyNames()) { |
| String value = System.getProperty(name); |
| if (name.startsWith(getPrefix() + ".")) { |
| config.set(name, value); |
| if (name.endsWith(".password") || name.endsWith(".secret")) { |
| value = "*MASKED*"; |
| } |
| log.info("System property sets {}: {}", name, value); |
| } |
| } |
| |
| log.debug("Loaded Configuration:"); |
| log.debug("------------------------------------------------------"); |
| for (Map.Entry<String, String> entry : config) { |
| String name = entry.getKey(); |
| String value = config.get(entry.getKey()); |
| if (name.endsWith(".password") || name.endsWith(".secret")) { |
| value = "*MASKED*"; |
| } |
| log.debug(" {}: {}", entry.getKey(), value); |
| } |
| 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<Service> 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 <code>services</code> and |
| * <code>services.ext</code> and de-dups them. |
| * |
| * @return List of final services to initialize. |
| * |
| * @throws ServerException throw if the services could not be loaded. |
| */ |
| protected List<Service> loadServices() throws ServerException { |
| try { |
| Map<Class, Service> map = new LinkedHashMap<Class, Service>(); |
| Class[] classes = getConfig().getClasses(getPrefixedName(CONF_SERVICES)); |
| Class[] classesExt = getConfig().getClasses(getPrefixedName(CONF_SERVICES_EXT)); |
| List<Service> list = new ArrayList<Service>(); |
| 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<Service>(); |
| for (Map.Entry<Class, Service> 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<Service> 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<Service> list = new ArrayList<Service>(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. |
| * <p/> |
| * 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. |
| * <p/> |
| * 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> T get(Class<T> serviceKlass) { |
| ensureOperational(); |
| Check.notNull(serviceKlass, "serviceKlass"); |
| return (T) services.get(serviceKlass); |
| } |
| |
| /** |
| * Adds a service programmatically. |
| * <p/> |
| * If a service with the same interface exists, it will be destroyed and |
| * removed before the given one is initialized and added. |
| * <p/> |
| * 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<? extends Service> 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); |
| } |
| } |
| |
| } |