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 }