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 }