001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.hadoop.lib.server;
020
021import org.apache.hadoop.classification.InterfaceAudience;
022import org.apache.hadoop.conf.Configuration;
023import org.apache.hadoop.lib.util.Check;
024import org.apache.hadoop.lib.util.ConfigurationUtils;
025import org.apache.log4j.LogManager;
026import org.apache.log4j.PropertyConfigurator;
027import org.slf4j.Logger;
028import org.slf4j.LoggerFactory;
029
030import java.io.File;
031import java.io.FileInputStream;
032import java.io.IOException;
033import java.io.InputStream;
034import java.text.MessageFormat;
035import java.util.ArrayList;
036import java.util.Collections;
037import java.util.LinkedHashMap;
038import java.util.List;
039import java.util.Map;
040import java.util.Properties;
041
042/**
043 * A Server class provides standard configuration, logging and {@link Service}
044 * lifecyle management.
045 * <p/>
046 * A Server normally has a home directory, a configuration directory, a temp
047 * directory and logs directory.
048 * <p/>
049 * The Server configuration is loaded from 2 overlapped files,
050 * <code>#SERVER#-default.xml</code> and <code>#SERVER#-site.xml</code>. The
051 * default file is loaded from the classpath, the site file is laoded from the
052 * configuration directory.
053 * <p/>
054 * The Server collects all configuration properties prefixed with
055 * <code>#SERVER#</code>. The property names are then trimmed from the
056 * <code>#SERVER#</code> prefix.
057 * <p/>
058 * The Server log configuration is loaded from the
059 * <code>#SERVICE#-log4j.properties</code> file in the configuration directory.
060 * <p/>
061 * The lifecycle of server is defined in by {@link Server.Status} enum.
062 * When a server is create, its status is UNDEF, when being initialized it is
063 * BOOTING, once initialization is complete by default transitions to NORMAL.
064 * The <code>#SERVER#.startup.status</code> configuration property can be used
065 * to specify a different startup status (NORMAL, ADMIN or HALTED).
066 * <p/>
067 * Services classes are defined in the <code>#SERVER#.services</code> and
068 * <code>#SERVER#.services.ext</code> properties. They are loaded in order
069 * (services first, then services.ext).
070 * <p/>
071 * Before initializing the services, they are traversed and duplicate service
072 * interface are removed from the service list. The last service using a given
073 * interface wins (this enables a simple override mechanism).
074 * <p/>
075 * After the services have been resoloved by interface de-duplication they are
076 * initialized in order. Once all services are initialized they are
077 * post-initialized (this enables late/conditional service bindings).
078 * <p/>
079 */
080@InterfaceAudience.Private
081public class Server {
082  private Logger log;
083
084  /**
085   * Server property name that defines the service classes.
086   */
087  public static final String CONF_SERVICES = "services";
088
089  /**
090   * Server property name that defines the service extension classes.
091   */
092  public static final String CONF_SERVICES_EXT = "services.ext";
093
094  /**
095   * Server property name that defines server startup status.
096   */
097  public static final String CONF_STARTUP_STATUS = "startup.status";
098
099  /**
100   * Enumeration that defines the server status.
101   */
102  @InterfaceAudience.Private
103  public static enum Status {
104    UNDEF(false, false),
105    BOOTING(false, true),
106    HALTED(true, true),
107    ADMIN(true, true),
108    NORMAL(true, true),
109    SHUTTING_DOWN(false, true),
110    SHUTDOWN(false, false);
111
112    private boolean settable;
113    private boolean operational;
114
115    /**
116     * Status constructor.
117     *
118     * @param settable indicates if the status is settable.
119     * @param operational indicates if the server is operational
120     * when in this status.
121     */
122    private Status(boolean settable, boolean operational) {
123      this.settable = settable;
124      this.operational = operational;
125    }
126
127    /**
128     * Returns if this server status is operational.
129     *
130     * @return if this server status is operational.
131     */
132    public boolean isOperational() {
133      return operational;
134    }
135  }
136
137  /**
138   * Name of the log4j configuration file the Server will load from the
139   * classpath if the <code>#SERVER#-log4j.properties</code> is not defined
140   * in the server configuration directory.
141   */
142  public static final String DEFAULT_LOG4J_PROPERTIES = "default-log4j.properties";
143
144  private Status status;
145  private String name;
146  private String homeDir;
147  private String configDir;
148  private String logDir;
149  private String tempDir;
150  private Configuration config;
151  private Map<Class, Service> services = new LinkedHashMap<Class, Service>();
152
153  /**
154   * Creates a server instance.
155   * <p/>
156   * The config, log and temp directories are all under the specified home directory.
157   *
158   * @param name server name.
159   * @param homeDir server home directory.
160   */
161  public Server(String name, String homeDir) {
162    this(name, homeDir, null);
163  }
164
165  /**
166   * Creates a server instance.
167   *
168   * @param name server name.
169   * @param homeDir server home directory.
170   * @param configDir config directory.
171   * @param logDir log directory.
172   * @param tempDir temp directory.
173   */
174  public Server(String name, String homeDir, String configDir, String logDir, String tempDir) {
175    this(name, homeDir, configDir, logDir, tempDir, null);
176  }
177
178  /**
179   * Creates a server instance.
180   * <p/>
181   * The config, log and temp directories are all under the specified home directory.
182   * <p/>
183   * It uses the provided configuration instead loading it from the config dir.
184   *
185   * @param name server name.
186   * @param homeDir server home directory.
187   * @param config server configuration.
188   */
189  public Server(String name, String homeDir, Configuration config) {
190    this(name, homeDir, homeDir + "/conf", homeDir + "/log", homeDir + "/temp", config);
191  }
192
193  /**
194   * Creates a server instance.
195   * <p/>
196   * It uses the provided configuration instead loading it from the config dir.
197   *
198   * @param name server name.
199   * @param homeDir server home directory.
200   * @param configDir config directory.
201   * @param logDir log directory.
202   * @param tempDir temp directory.
203   * @param config server configuration.
204   */
205  public Server(String name, String homeDir, String configDir, String logDir, String tempDir, Configuration config) {
206    this.name = Check.notEmpty(name, "name").trim().toLowerCase();
207    this.homeDir = Check.notEmpty(homeDir, "homeDir");
208    this.configDir = Check.notEmpty(configDir, "configDir");
209    this.logDir = Check.notEmpty(logDir, "logDir");
210    this.tempDir = Check.notEmpty(tempDir, "tempDir");
211    checkAbsolutePath(homeDir, "homeDir");
212    checkAbsolutePath(configDir, "configDir");
213    checkAbsolutePath(logDir, "logDir");
214    checkAbsolutePath(tempDir, "tempDir");
215    if (config != null) {
216      this.config = new Configuration(false);
217      ConfigurationUtils.copy(config, this.config);
218    }
219    status = Status.UNDEF;
220  }
221
222  /**
223   * Validates that the specified value is an absolute path (starts with '/').
224   *
225   * @param value value to verify it is an absolute path.
226   * @param name name to use in the exception if the value is not an absolute
227   * path.
228   *
229   * @return the value.
230   *
231   * @throws IllegalArgumentException thrown if the value is not an absolute
232   * path.
233   */
234  private String checkAbsolutePath(String value, String name) {
235    if (!new File(value).isAbsolute()) {
236      throw new IllegalArgumentException(
237        MessageFormat.format("[{0}] must be an absolute path [{1}]", name, value));
238    }
239    return value;
240  }
241
242  /**
243   * Returns the current server status.
244   *
245   * @return the current server status.
246   */
247  public Status getStatus() {
248    return status;
249  }
250
251  /**
252   * Sets a new server status.
253   * <p/>
254   * The status must be settable.
255   * <p/>
256   * All services will be notified o the status change via the
257   * {@link Service#serverStatusChange(Server.Status, Server.Status)} method. If a service
258   * throws an exception during the notification, the server will be destroyed.
259   *
260   * @param status status to set.
261   *
262   * @throws ServerException thrown if the service has been destroy because of
263   * a failed notification to a service.
264   */
265  public void setStatus(Status status) throws ServerException {
266    Check.notNull(status, "status");
267    if (status.settable) {
268      if (status != this.status) {
269        Status oldStatus = this.status;
270        this.status = status;
271        for (Service service : services.values()) {
272          try {
273            service.serverStatusChange(oldStatus, status);
274          } catch (Exception ex) {
275            log.error("Service [{}] exception during status change to [{}] -server shutting down-,  {}",
276                      new Object[]{service.getInterface().getSimpleName(), status, ex.getMessage(), ex});
277            destroy();
278            throw new ServerException(ServerException.ERROR.S11, service.getInterface().getSimpleName(),
279                                      status, ex.getMessage(), ex);
280          }
281        }
282      }
283    } else {
284      throw new IllegalArgumentException("Status [" + status + " is not settable");
285    }
286  }
287
288  /**
289   * Verifies the server is operational.
290   *
291   * @throws IllegalStateException thrown if the server is not operational.
292   */
293  protected void ensureOperational() {
294    if (!getStatus().isOperational()) {
295      throw new IllegalStateException("Server is not running");
296    }
297  }
298
299  /**
300   * Convenience method that returns a resource as inputstream from the
301   * classpath.
302   * <p/>
303   * It first attempts to use the Thread's context classloader and if not
304   * set it uses the <code>ClassUtils</code> classloader.
305   *
306   * @param name resource to retrieve.
307   *
308   * @return inputstream with the resource, NULL if the resource does not
309   *         exist.
310   */
311  static InputStream getResource(String name) {
312    Check.notEmpty(name, "name");
313    ClassLoader cl = Thread.currentThread().getContextClassLoader();
314    if (cl == null) {
315      cl = Server.class.getClassLoader();
316    }
317    return cl.getResourceAsStream(name);
318  }
319
320  /**
321   * Initializes the Server.
322   * <p/>
323   * The initialization steps are:
324   * <ul>
325   * <li>It verifies the service home and temp directories exist</li>
326   * <li>Loads the Server <code>#SERVER#-default.xml</code>
327   * configuration file from the classpath</li>
328   * <li>Initializes log4j logging. If the
329   * <code>#SERVER#-log4j.properties</code> file does not exist in the config
330   * directory it load <code>default-log4j.properties</code> from the classpath
331   * </li>
332   * <li>Loads the <code>#SERVER#-site.xml</code> file from the server config
333   * directory and merges it with the default configuration.</li>
334   * <li>Loads the services</li>
335   * <li>Initializes the services</li>
336   * <li>Post-initializes the services</li>
337   * <li>Sets the server startup status</li>
338   *
339   * @throws ServerException thrown if the server could not be initialized.
340   */
341  public void init() throws ServerException {
342    if (status != Status.UNDEF) {
343      throw new IllegalStateException("Server already initialized");
344    }
345    status = Status.BOOTING;
346    verifyDir(homeDir);
347    verifyDir(tempDir);
348    Properties serverInfo = new Properties();
349    try {
350      InputStream is = getResource(name + ".properties");
351      serverInfo.load(is);
352      is.close();
353    } catch (IOException ex) {
354      throw new RuntimeException("Could not load server information file: " + name + ".properties");
355    }
356    initLog();
357    log.info("++++++++++++++++++++++++++++++++++++++++++++++++++++++");
358    log.info("Server [{}] starting", name);
359    log.info("  Built information:");
360    log.info("    Version           : {}", serverInfo.getProperty(name + ".version", "undef"));
361    log.info("    Source Repository : {}", serverInfo.getProperty(name + ".source.repository", "undef"));
362    log.info("    Source Revision   : {}", serverInfo.getProperty(name + ".source.revision", "undef"));
363    log.info("    Built by          : {}", serverInfo.getProperty(name + ".build.username", "undef"));
364    log.info("    Built timestamp   : {}", serverInfo.getProperty(name + ".build.timestamp", "undef"));
365    log.info("  Runtime information:");
366    log.info("    Home   dir: {}", homeDir);
367    log.info("    Config dir: {}", (config == null) ? configDir : "-");
368    log.info("    Log    dir: {}", logDir);
369    log.info("    Temp   dir: {}", tempDir);
370    initConfig();
371    log.debug("Loading services");
372    List<Service> list = loadServices();
373    try {
374      log.debug("Initializing services");
375      initServices(list);
376      log.info("Services initialized");
377    } catch (ServerException ex) {
378      log.error("Services initialization failure, destroying initialized services");
379      destroyServices();
380      throw ex;
381    }
382    Status status = Status.valueOf(getConfig().get(getPrefixedName(CONF_STARTUP_STATUS), Status.NORMAL.toString()));
383    setStatus(status);
384    log.info("Server [{}] started!, status [{}]", name, status);
385  }
386
387  /**
388   * Verifies the specified directory exists.
389   *
390   * @param dir directory to verify it exists.
391   *
392   * @throws ServerException thrown if the directory does not exist or it the
393   * path it is not a directory.
394   */
395  private void verifyDir(String dir) throws ServerException {
396    File file = new File(dir);
397    if (!file.exists()) {
398      throw new ServerException(ServerException.ERROR.S01, dir);
399    }
400    if (!file.isDirectory()) {
401      throw new ServerException(ServerException.ERROR.S02, dir);
402    }
403  }
404
405  /**
406   * Initializes Log4j logging.
407   *
408   * @throws ServerException thrown if Log4j could not be initialized.
409   */
410  protected void initLog() throws ServerException {
411    verifyDir(logDir);
412    LogManager.resetConfiguration();
413    File log4jFile = new File(configDir, name + "-log4j.properties");
414    if (log4jFile.exists()) {
415      PropertyConfigurator.configureAndWatch(log4jFile.toString(), 10 * 1000); //every 10 secs
416      log = LoggerFactory.getLogger(Server.class);
417    } else {
418      Properties props = new Properties();
419      try {
420        InputStream is = getResource(DEFAULT_LOG4J_PROPERTIES);
421        try {
422          props.load(is);
423        } finally {
424          is.close();
425        }
426      } catch (IOException ex) {
427        throw new ServerException(ServerException.ERROR.S03, DEFAULT_LOG4J_PROPERTIES, ex.getMessage(), ex);
428      }
429      PropertyConfigurator.configure(props);
430      log = LoggerFactory.getLogger(Server.class);
431      log.warn("Log4j [{}] configuration file not found, using default configuration from classpath", log4jFile);
432    }
433  }
434
435  /**
436   * Loads and inializes the server configuration.
437   *
438   * @throws ServerException thrown if the configuration could not be loaded/initialized.
439   */
440  protected void initConfig() throws ServerException {
441    verifyDir(configDir);
442    File file = new File(configDir);
443    Configuration defaultConf;
444    String defaultConfig = name + "-default.xml";
445    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
446    InputStream inputStream = classLoader.getResourceAsStream(defaultConfig);
447    if (inputStream == null) {
448      log.warn("Default configuration file not available in classpath [{}]", defaultConfig);
449      defaultConf = new Configuration(false);
450    } else {
451      try {
452        defaultConf = new Configuration(false);
453        ConfigurationUtils.load(defaultConf, inputStream);
454      } catch (Exception ex) {
455        throw new ServerException(ServerException.ERROR.S03, defaultConfig, ex.getMessage(), ex);
456      }
457    }
458
459    if (config == null) {
460      Configuration siteConf;
461      File siteFile = new File(file, name + "-site.xml");
462      if (!siteFile.exists()) {
463        log.warn("Site configuration file [{}] not found in config directory", siteFile);
464        siteConf = new Configuration(false);
465      } else {
466        if (!siteFile.isFile()) {
467          throw new ServerException(ServerException.ERROR.S05, siteFile.getAbsolutePath());
468        }
469        try {
470          log.debug("Loading site configuration from [{}]", siteFile);
471          inputStream = new FileInputStream(siteFile);
472          siteConf = new Configuration(false);
473          ConfigurationUtils.load(siteConf, inputStream);
474        } catch (IOException ex) {
475          throw new ServerException(ServerException.ERROR.S06, siteFile, ex.getMessage(), ex);
476        }
477      }
478
479      config = new Configuration(false);
480      ConfigurationUtils.copy(siteConf, config);
481    }
482
483    ConfigurationUtils.injectDefaults(defaultConf, config);
484
485    for (String name : System.getProperties().stringPropertyNames()) {
486      String value = System.getProperty(name);
487      if (name.startsWith(getPrefix() + ".")) {
488        config.set(name, value);
489        if (name.endsWith(".password") || name.endsWith(".secret")) {
490          value = "*MASKED*";
491        }
492        log.info("System property sets  {}: {}", name, value);
493      }
494    }
495
496    log.debug("Loaded Configuration:");
497    log.debug("------------------------------------------------------");
498    for (Map.Entry<String, String> entry : config) {
499      String name = entry.getKey();
500      String value = config.get(entry.getKey());
501      if (name.endsWith(".password") || name.endsWith(".secret")) {
502        value = "*MASKED*";
503      }
504      log.debug("  {}: {}", entry.getKey(), value);
505    }
506    log.debug("------------------------------------------------------");
507  }
508
509  /**
510   * Loads the specified services.
511   *
512   * @param classes services classes to load.
513   * @param list list of loaded service in order of appearance in the
514   * configuration.
515   *
516   * @throws ServerException thrown if a service class could not be loaded.
517   */
518  private void loadServices(Class[] classes, List<Service> list) throws ServerException {
519    for (Class klass : classes) {
520      try {
521        Service service = (Service) klass.newInstance();
522        log.debug("Loading service [{}] implementation [{}]", service.getInterface(),
523                  service.getClass());
524        if (!service.getInterface().isInstance(service)) {
525          throw new ServerException(ServerException.ERROR.S04, klass, service.getInterface().getName());
526        }
527        list.add(service);
528      } catch (ServerException ex) {
529        throw ex;
530      } catch (Exception ex) {
531        throw new ServerException(ServerException.ERROR.S07, klass, ex.getMessage(), ex);
532      }
533    }
534  }
535
536  /**
537   * Loads services defined in <code>services</code> and
538   * <code>services.ext</code> and de-dups them.
539   *
540   * @return List of final services to initialize.
541   *
542   * @throws ServerException throw if the services could not be loaded.
543   */
544  protected List<Service> loadServices() throws ServerException {
545    try {
546      Map<Class, Service> map = new LinkedHashMap<Class, Service>();
547      Class[] classes = getConfig().getClasses(getPrefixedName(CONF_SERVICES));
548      Class[] classesExt = getConfig().getClasses(getPrefixedName(CONF_SERVICES_EXT));
549      List<Service> list = new ArrayList<Service>();
550      loadServices(classes, list);
551      loadServices(classesExt, list);
552
553      //removing duplicate services, strategy: last one wins
554      for (Service service : list) {
555        if (map.containsKey(service.getInterface())) {
556          log.debug("Replacing service [{}] implementation [{}]", service.getInterface(),
557                    service.getClass());
558        }
559        map.put(service.getInterface(), service);
560      }
561      list = new ArrayList<Service>();
562      for (Map.Entry<Class, Service> entry : map.entrySet()) {
563        list.add(entry.getValue());
564      }
565      return list;
566    } catch (RuntimeException ex) {
567      throw new ServerException(ServerException.ERROR.S08, ex.getMessage(), ex);
568    }
569  }
570
571  /**
572   * Initializes the list of services.
573   *
574   * @param services services to initialized, it must be a de-dupped list of
575   * services.
576   *
577   * @throws ServerException thrown if the services could not be initialized.
578   */
579  protected void initServices(List<Service> services) throws ServerException {
580    for (Service service : services) {
581      log.debug("Initializing service [{}]", service.getInterface());
582      checkServiceDependencies(service);
583      service.init(this);
584      this.services.put(service.getInterface(), service);
585    }
586    for (Service service : services) {
587      service.postInit();
588    }
589  }
590
591  /**
592   * Checks if all service dependencies of a service are available.
593   *
594   * @param service service to check if all its dependencies are available.
595   *
596   * @throws ServerException thrown if a service dependency is missing.
597   */
598  protected void checkServiceDependencies(Service service) throws ServerException {
599    if (service.getServiceDependencies() != null) {
600      for (Class dependency : service.getServiceDependencies()) {
601        if (services.get(dependency) == null) {
602          throw new ServerException(ServerException.ERROR.S10, service.getClass(), dependency);
603        }
604      }
605    }
606  }
607
608  /**
609   * Destroys the server services.
610   */
611  protected void destroyServices() {
612    List<Service> list = new ArrayList<Service>(services.values());
613    Collections.reverse(list);
614    for (Service service : list) {
615      try {
616        log.debug("Destroying service [{}]", service.getInterface());
617        service.destroy();
618      } catch (Throwable ex) {
619        log.error("Could not destroy service [{}], {}",
620                  new Object[]{service.getInterface(), ex.getMessage(), ex});
621      }
622    }
623    log.info("Services destroyed");
624  }
625
626  /**
627   * Destroys the server.
628   * <p/>
629   * All services are destroyed in reverse order of initialization, then the
630   * Log4j framework is shutdown.
631   */
632  public void destroy() {
633    ensureOperational();
634    destroyServices();
635    log.info("Server [{}] shutdown!", name);
636    log.info("======================================================");
637    if (!Boolean.getBoolean("test.circus")) {
638      LogManager.shutdown();
639    }
640    status = Status.SHUTDOWN;
641  }
642
643  /**
644   * Returns the name of the server.
645   *
646   * @return the server name.
647   */
648  public String getName() {
649    return name;
650  }
651
652  /**
653   * Returns the server prefix for server configuration properties.
654   * <p/>
655   * By default it is the server name.
656   *
657   * @return the prefix for server configuration properties.
658   */
659  public String getPrefix() {
660    return getName();
661  }
662
663  /**
664   * Returns the prefixed name of a server property.
665   *
666   * @param name of the property.
667   *
668   * @return prefixed name of the property.
669   */
670  public String getPrefixedName(String name) {
671    return getPrefix() + "." + Check.notEmpty(name, "name");
672  }
673
674  /**
675   * Returns the server home dir.
676   *
677   * @return the server home dir.
678   */
679  public String getHomeDir() {
680    return homeDir;
681  }
682
683  /**
684   * Returns the server config dir.
685   *
686   * @return the server config dir.
687   */
688  public String getConfigDir() {
689    return configDir;
690  }
691
692  /**
693   * Returns the server log dir.
694   *
695   * @return the server log dir.
696   */
697  public String getLogDir() {
698    return logDir;
699  }
700
701  /**
702   * Returns the server temp dir.
703   *
704   * @return the server temp dir.
705   */
706  public String getTempDir() {
707    return tempDir;
708  }
709
710  /**
711   * Returns the server configuration.
712   *
713   * @return the server configuration.
714   */
715  public Configuration getConfig() {
716    return config;
717
718  }
719
720  /**
721   * Returns the {@link Service} associated to the specified interface.
722   *
723   * @param serviceKlass service interface.
724   *
725   * @return the service implementation.
726   */
727  @SuppressWarnings("unchecked")
728  public <T> T get(Class<T> serviceKlass) {
729    ensureOperational();
730    Check.notNull(serviceKlass, "serviceKlass");
731    return (T) services.get(serviceKlass);
732  }
733
734  /**
735   * Adds a service programmatically.
736   * <p/>
737   * If a service with the same interface exists, it will be destroyed and
738   * removed before the given one is initialized and added.
739   * <p/>
740   * If an exception is thrown the server is destroyed.
741   *
742   * @param klass service class to add.
743   *
744   * @throws ServerException throw if the service could not initialized/added
745   * to the server.
746   */
747  public void setService(Class<? extends Service> klass) throws ServerException {
748    ensureOperational();
749    Check.notNull(klass, "serviceKlass");
750    if (getStatus() == Status.SHUTTING_DOWN) {
751      throw new IllegalStateException("Server shutting down");
752    }
753    try {
754      Service newService = klass.newInstance();
755      Service oldService = services.get(newService.getInterface());
756      if (oldService != null) {
757        try {
758          oldService.destroy();
759        } catch (Throwable ex) {
760          log.error("Could not destroy service [{}], {}",
761                    new Object[]{oldService.getInterface(), ex.getMessage(), ex});
762        }
763      }
764      newService.init(this);
765      services.put(newService.getInterface(), newService);
766    } catch (Exception ex) {
767      log.error("Could not set service [{}] programmatically -server shutting down-, {}", klass, ex);
768      destroy();
769      throw new ServerException(ServerException.ERROR.S09, klass, ex.getMessage(), ex);
770    }
771  }
772
773}