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