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