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