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