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.hadoop.util.StringUtils;
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 */
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 = StringUtils.toLowerCase(Check.notEmpty(name, "name").trim());
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   * </ul>
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
486    for (String name : System.getProperties().stringPropertyNames()) {
487      String value = System.getProperty(name);
488      if (name.startsWith(getPrefix() + ".")) {
489        config.set(name, value);
490        if (name.endsWith(".password") || name.endsWith(".secret")) {
491          value = "*MASKED*";
492        }
493        log.info("System property sets  {}: {}", name, value);
494      }
495    }
496
497    log.debug("Loaded Configuration:");
498    log.debug("------------------------------------------------------");
499    for (Map.Entry<String, String> entry : config) {
500      String name = entry.getKey();
501      String value = config.get(entry.getKey());
502      if (name.endsWith(".password") || name.endsWith(".secret")) {
503        value = "*MASKED*";
504      }
505      log.debug("  {}: {}", entry.getKey(), value);
506    }
507    log.debug("------------------------------------------------------");
508  }
509
510  /**
511   * Loads the specified services.
512   *
513   * @param classes services classes to load.
514   * @param list list of loaded service in order of appearance in the
515   * configuration.
516   *
517   * @throws ServerException thrown if a service class could not be loaded.
518   */
519  private void loadServices(Class[] classes, List<Service> list) throws ServerException {
520    for (Class klass : classes) {
521      try {
522        Service service = (Service) klass.newInstance();
523        log.debug("Loading service [{}] implementation [{}]", service.getInterface(),
524                  service.getClass());
525        if (!service.getInterface().isInstance(service)) {
526          throw new ServerException(ServerException.ERROR.S04, klass, service.getInterface().getName());
527        }
528        list.add(service);
529      } catch (ServerException ex) {
530        throw ex;
531      } catch (Exception ex) {
532        throw new ServerException(ServerException.ERROR.S07, klass, ex.getMessage(), ex);
533      }
534    }
535  }
536
537  /**
538   * Loads services defined in <code>services</code> and
539   * <code>services.ext</code> and de-dups them.
540   *
541   * @return List of final services to initialize.
542   *
543   * @throws ServerException throw if the services could not be loaded.
544   */
545  protected List<Service> loadServices() throws ServerException {
546    try {
547      Map<Class, Service> map = new LinkedHashMap<Class, Service>();
548      Class[] classes = getConfig().getClasses(getPrefixedName(CONF_SERVICES));
549      Class[] classesExt = getConfig().getClasses(getPrefixedName(CONF_SERVICES_EXT));
550      List<Service> list = new ArrayList<Service>();
551      loadServices(classes, list);
552      loadServices(classesExt, list);
553
554      //removing duplicate services, strategy: last one wins
555      for (Service service : list) {
556        if (map.containsKey(service.getInterface())) {
557          log.debug("Replacing service [{}] implementation [{}]", service.getInterface(),
558                    service.getClass());
559        }
560        map.put(service.getInterface(), service);
561      }
562      list = new ArrayList<Service>();
563      for (Map.Entry<Class, Service> entry : map.entrySet()) {
564        list.add(entry.getValue());
565      }
566      return list;
567    } catch (RuntimeException ex) {
568      throw new ServerException(ServerException.ERROR.S08, ex.getMessage(), ex);
569    }
570  }
571
572  /**
573   * Initializes the list of services.
574   *
575   * @param services services to initialized, it must be a de-dupped list of
576   * services.
577   *
578   * @throws ServerException thrown if the services could not be initialized.
579   */
580  protected void initServices(List<Service> services) throws ServerException {
581    for (Service service : services) {
582      log.debug("Initializing service [{}]", service.getInterface());
583      checkServiceDependencies(service);
584      service.init(this);
585      this.services.put(service.getInterface(), service);
586    }
587    for (Service service : services) {
588      service.postInit();
589    }
590  }
591
592  /**
593   * Checks if all service dependencies of a service are available.
594   *
595   * @param service service to check if all its dependencies are available.
596   *
597   * @throws ServerException thrown if a service dependency is missing.
598   */
599  protected void checkServiceDependencies(Service service) throws ServerException {
600    if (service.getServiceDependencies() != null) {
601      for (Class dependency : service.getServiceDependencies()) {
602        if (services.get(dependency) == null) {
603          throw new ServerException(ServerException.ERROR.S10, service.getClass(), dependency);
604        }
605      }
606    }
607  }
608
609  /**
610   * Destroys the server services.
611   */
612  protected void destroyServices() {
613    List<Service> list = new ArrayList<Service>(services.values());
614    Collections.reverse(list);
615    for (Service service : list) {
616      try {
617        log.debug("Destroying service [{}]", service.getInterface());
618        service.destroy();
619      } catch (Throwable ex) {
620        log.error("Could not destroy service [{}], {}",
621                  new Object[]{service.getInterface(), ex.getMessage(), ex});
622      }
623    }
624    log.info("Services destroyed");
625  }
626
627  /**
628   * Destroys the server.
629   * <p>
630   * All services are destroyed in reverse order of initialization, then the
631   * Log4j framework is shutdown.
632   */
633  public void destroy() {
634    ensureOperational();
635    destroyServices();
636    log.info("Server [{}] shutdown!", name);
637    log.info("======================================================");
638    if (!Boolean.getBoolean("test.circus")) {
639      LogManager.shutdown();
640    }
641    status = Status.SHUTDOWN;
642  }
643
644  /**
645   * Returns the name of the server.
646   *
647   * @return the server name.
648   */
649  public String getName() {
650    return name;
651  }
652
653  /**
654   * Returns the server prefix for server configuration properties.
655   * <p>
656   * By default it is the server name.
657   *
658   * @return the prefix for server configuration properties.
659   */
660  public String getPrefix() {
661    return getName();
662  }
663
664  /**
665   * Returns the prefixed name of a server property.
666   *
667   * @param name of the property.
668   *
669   * @return prefixed name of the property.
670   */
671  public String getPrefixedName(String name) {
672    return getPrefix() + "." + Check.notEmpty(name, "name");
673  }
674
675  /**
676   * Returns the server home dir.
677   *
678   * @return the server home dir.
679   */
680  public String getHomeDir() {
681    return homeDir;
682  }
683
684  /**
685   * Returns the server config dir.
686   *
687   * @return the server config dir.
688   */
689  public String getConfigDir() {
690    return configDir;
691  }
692
693  /**
694   * Returns the server log dir.
695   *
696   * @return the server log dir.
697   */
698  public String getLogDir() {
699    return logDir;
700  }
701
702  /**
703   * Returns the server temp dir.
704   *
705   * @return the server temp dir.
706   */
707  public String getTempDir() {
708    return tempDir;
709  }
710
711  /**
712   * Returns the server configuration.
713   *
714   * @return the server configuration.
715   */
716  public Configuration getConfig() {
717    return config;
718
719  }
720
721  /**
722   * Returns the {@link Service} associated to the specified interface.
723   *
724   * @param serviceKlass service interface.
725   *
726   * @return the service implementation.
727   */
728  @SuppressWarnings("unchecked")
729  public <T> T get(Class<T> serviceKlass) {
730    ensureOperational();
731    Check.notNull(serviceKlass, "serviceKlass");
732    return (T) services.get(serviceKlass);
733  }
734
735  /**
736   * Adds a service programmatically.
737   * <p>
738   * If a service with the same interface exists, it will be destroyed and
739   * removed before the given one is initialized and added.
740   * <p>
741   * If an exception is thrown the server is destroyed.
742   *
743   * @param klass service class to add.
744   *
745   * @throws ServerException throw if the service could not initialized/added
746   * to the server.
747   */
748  public void setService(Class<? extends Service> klass) throws ServerException {
749    ensureOperational();
750    Check.notNull(klass, "serviceKlass");
751    if (getStatus() == Status.SHUTTING_DOWN) {
752      throw new IllegalStateException("Server shutting down");
753    }
754    try {
755      Service newService = klass.newInstance();
756      Service oldService = services.get(newService.getInterface());
757      if (oldService != null) {
758        try {
759          oldService.destroy();
760        } catch (Throwable ex) {
761          log.error("Could not destroy service [{}], {}",
762                    new Object[]{oldService.getInterface(), ex.getMessage(), ex});
763        }
764      }
765      newService.init(this);
766      services.put(newService.getInterface(), newService);
767    } catch (Exception ex) {
768      log.error("Could not set service [{}] programmatically -server shutting down-, {}", klass, ex);
769      destroy();
770      throw new ServerException(ServerException.ERROR.S09, klass, ex.getMessage(), ex);
771    }
772  }
773
774}