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