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.classification.InterfaceAudience;
022    import org.apache.hadoop.conf.Configuration;
023    import org.apache.hadoop.lib.util.Check;
024    import org.apache.hadoop.lib.util.ConfigurationUtils;
025    import org.apache.log4j.LogManager;
026    import org.apache.log4j.PropertyConfigurator;
027    import org.slf4j.Logger;
028    import org.slf4j.LoggerFactory;
029    
030    import java.io.File;
031    import java.io.FileInputStream;
032    import java.io.IOException;
033    import java.io.InputStream;
034    import java.text.MessageFormat;
035    import java.util.ArrayList;
036    import java.util.Collections;
037    import java.util.LinkedHashMap;
038    import java.util.List;
039    import java.util.Map;
040    import 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
081    public 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    }