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