001 package org.apache.turbine.services; 002 003 /* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022 023 import java.util.ArrayList; 024 import java.util.Enumeration; 025 import java.util.Hashtable; 026 import java.util.Iterator; 027 028 import org.apache.commons.configuration.BaseConfiguration; 029 import org.apache.commons.configuration.Configuration; 030 import org.apache.commons.lang.StringUtils; 031 import org.apache.commons.logging.Log; 032 import org.apache.commons.logging.LogFactory; 033 034 /** 035 * A generic implementation of a <code>ServiceBroker</code> which 036 * provides: 037 * 038 * <ul> 039 * <li>Maintaining service name to class name mapping, allowing 040 * plugable service implementations.</li> 041 * <li>Providing <code>Services</code> with a configuration based on 042 * system wide configuration mechanism.</li> 043 * </ul> 044 * <li>Integration of TurbineServiceProviders for looking up 045 * non-local services 046 * </ul> 047 * 048 * @author <a href="mailto:burton@apache.org">Kevin Burton</a> 049 * @author <a href="mailto:krzewski@e-point.pl">Rafal Krzewski</a> 050 * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a> 051 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> 052 * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a> 053 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a> 054 * @version $Id: BaseServiceBroker.java 1066945 2011-02-03 20:27:59Z ludwig $ 055 */ 056 public abstract class BaseServiceBroker implements ServiceBroker 057 { 058 /** 059 * Mapping of Service names to class names. 060 */ 061 private Configuration mapping = new BaseConfiguration(); 062 063 /** 064 * A repository of Service instances. 065 */ 066 private Hashtable<String, Service> services = new Hashtable<String, Service>(); 067 068 /** 069 * Configuration for the services broker. 070 * The configuration should be set by the application 071 * in which the services framework is running. 072 */ 073 private Configuration configuration; 074 075 /** 076 * A prefix for <code>Service</code> properties in 077 * TurbineResource.properties. 078 */ 079 public static final String SERVICE_PREFIX = "services."; 080 081 /** 082 * A <code>Service</code> property determining its implementing 083 * class name . 084 */ 085 public static final String CLASSNAME_SUFFIX = ".classname"; 086 087 /** 088 * These are objects that the parent application 089 * can provide so that application specific 090 * services have a mechanism to retrieve specialized 091 * information. For example, in Turbine there are services 092 * that require the RunData object: these services can 093 * retrieve the RunData object that Turbine has placed 094 * in the service manager. This alleviates us of 095 * the requirement of having init(Object) all 096 * together. 097 */ 098 private Hashtable<String, Object> serviceObjects = new Hashtable<String, Object>(); 099 100 /** Logging */ 101 private static Log log = LogFactory.getLog(BaseServiceBroker.class); 102 103 /** 104 * Application root path as set by the 105 * parent application. 106 */ 107 private String applicationRoot; 108 109 /** 110 * mapping from service names to instances of TurbineServiceProviders 111 */ 112 private Hashtable<String, Service> serviceProviderInstanceMap = new Hashtable<String, Service>(); 113 114 /** 115 * Default constructor, protected as to only be useable by subclasses. 116 * 117 * This constructor does nothing. 118 */ 119 protected BaseServiceBroker() 120 { 121 // nothing to do 122 } 123 124 /** 125 * Set the configuration object for the services broker. 126 * This is the configuration that contains information 127 * about all services in the care of this service 128 * manager. 129 * 130 * @param configuration Broker configuration. 131 */ 132 public void setConfiguration(Configuration configuration) 133 { 134 this.configuration = configuration; 135 } 136 137 /** 138 * Get the configuration for this service manager. 139 * 140 * @return Broker configuration. 141 */ 142 public Configuration getConfiguration() 143 { 144 return configuration; 145 } 146 147 /** 148 * Initialize this service manager. 149 */ 150 public void init() throws InitializationException 151 { 152 // Check: 153 // 154 // 1. The configuration has been set. 155 // 2. Make sure the application root has been set. 156 157 // FIXME: Make some service framework exceptions to throw in 158 // the event these requirements aren't satisfied. 159 160 // Create the mapping between service names 161 // and their classes. 162 initMapping(); 163 164 // Start services that have their 'earlyInit' 165 // property set to 'true'. 166 initServices(false); 167 } 168 169 /** 170 * Set an application specific service object 171 * that can be used by application specific 172 * services. 173 * 174 * @param name name of service object 175 * @param value value of service object 176 */ 177 public void setServiceObject(String name, Object value) 178 { 179 serviceObjects.put(name, value); 180 } 181 182 /** 183 * Get an application specific service object. 184 * 185 * @param name the name of the service object 186 * @return Object application specific service object 187 */ 188 public Object getServiceObject(String name) 189 { 190 return serviceObjects.get(name); 191 } 192 193 /** 194 * Creates a mapping between Service names and class names. 195 * 196 * The mapping is built according to settings present in 197 * TurbineResources.properties. The entries should have the 198 * following form: 199 * 200 * <pre> 201 * services.MyService.classname=com.mycompany.MyServiceImpl 202 * services.MyOtherService.classname=com.mycompany.MyOtherServiceImpl 203 * </pre> 204 * 205 * <br> 206 * 207 * Generic ServiceBroker provides no Services. 208 */ 209 @SuppressWarnings("unchecked") 210 protected void initMapping() 211 { 212 /* 213 * These keys returned in an order that corresponds 214 * to the order the services are listed in 215 * the TR.props. 216 * 217 * When the mapping is created we use a Configuration 218 * object to ensure that the we retain the order 219 * in which the order the keys are returned. 220 * 221 * There's no point in retrieving an ordered set 222 * of keys if they aren't kept in order :-) 223 */ 224 for (Iterator<String> keys = configuration.getKeys(); keys.hasNext();) 225 { 226 String key = keys.next(); 227 String[] keyParts = StringUtils.split(key, "."); 228 229 if ((keyParts.length == 3) 230 && (keyParts[0] + ".").equals(SERVICE_PREFIX) 231 && ("." + keyParts[2]).equals(CLASSNAME_SUFFIX)) 232 { 233 String serviceKey = keyParts[1]; 234 log.info("Added Mapping for Service: " + serviceKey); 235 236 if (!mapping.containsKey(serviceKey)) 237 { 238 mapping.setProperty(serviceKey, 239 configuration.getString(key)); 240 } 241 } 242 } 243 } 244 245 /** 246 * Determines whether a service is registered in the configured 247 * <code>TurbineResources.properties</code>. 248 * 249 * @param serviceName The name of the service whose existance to check. 250 * @return Registration predicate for the desired services. 251 */ 252 public boolean isRegistered(String serviceName) 253 { 254 return (services.get(serviceName) != null); 255 } 256 257 /** 258 * Returns an Iterator over all known service names. 259 * 260 * @return An Iterator of service names. 261 */ 262 @SuppressWarnings("unchecked") 263 public Iterator<String> getServiceNames() 264 { 265 return mapping.getKeys(); 266 } 267 268 /** 269 * Returns an Iterator over all known service names beginning with 270 * the provided prefix. 271 * 272 * @param prefix The prefix against which to test. 273 * @return An Iterator of service names which match the prefix. 274 */ 275 @SuppressWarnings("unchecked") 276 public Iterator<String> getServiceNames(String prefix) 277 { 278 return mapping.getKeys(prefix); 279 } 280 281 /** 282 * Performs early initialization of specified service. 283 * 284 * @param name The name of the service (generally the 285 * <code>SERVICE_NAME</code> constant of the service's interface 286 * definition). 287 * @exception InitializationException Initialization of this 288 * service was not successful. 289 */ 290 public synchronized void initService(String name) 291 throws InitializationException 292 { 293 // Calling getServiceInstance(name) assures that the Service 294 // implementation has its name and broker reference set before 295 // initialization. 296 Service instance = getServiceInstance(name); 297 298 if (!instance.getInit()) 299 { 300 // this call might result in an indirect recursion 301 instance.init(); 302 } 303 } 304 305 /** 306 * Performs early initialization of all services. Failed early 307 * initialization of a Service may be non-fatal to the system, 308 * thus any exceptions are logged and the initialization process 309 * continues. 310 */ 311 public void initServices() 312 { 313 try 314 { 315 initServices(false); 316 } 317 catch (InstantiationException notThrown) 318 { 319 log.debug("Caught non fatal exception", notThrown); 320 } 321 catch (InitializationException notThrown) 322 { 323 log.debug("Caught non fatal exception", notThrown); 324 } 325 } 326 327 /** 328 * Performs early initialization of all services. You can decide 329 * to handle failed initializations if you wish, but then 330 * after one service fails, the other will not have the chance 331 * to initialize. 332 * 333 * @param report <code>true</code> if you want exceptions thrown. 334 */ 335 public void initServices(boolean report) 336 throws InstantiationException, InitializationException 337 { 338 if (report) 339 { 340 // Throw exceptions 341 for (Iterator<String> names = getServiceNames(); names.hasNext();) 342 { 343 doInitService(names.next()); 344 } 345 } 346 else 347 { 348 // Eat exceptions 349 for (Iterator<String> names = getServiceNames(); names.hasNext();) 350 { 351 try 352 { 353 doInitService(names.next()); 354 } 355 // In case of an exception, file an error message; the 356 // system may be still functional, though. 357 catch (InstantiationException e) 358 { 359 log.error(e); 360 } 361 catch (InitializationException e) 362 { 363 log.error(e); 364 } 365 } 366 } 367 log.info("Finished initializing all services!"); 368 } 369 370 /** 371 * Internal utility method for use in {@link #initServices(boolean)} 372 * to prevent duplication of code. 373 */ 374 private void doInitService(String name) 375 throws InstantiationException, InitializationException 376 { 377 // Only start up services that have their earlyInit flag set. 378 if (getConfiguration(name).getBoolean("earlyInit", false)) 379 { 380 log.info("Start Initializing service (early): " + name); 381 initService(name); 382 log.info("Finish Initializing service (early): " + name); 383 } 384 } 385 386 /** 387 * Shuts down a <code>Service</code>, releasing resources 388 * allocated by an <code>Service</code>, and returns it to its 389 * initial (uninitialized) state. 390 * 391 * @param name The name of the <code>Service</code> to be 392 * uninitialized. 393 */ 394 public synchronized void shutdownService(String name) 395 { 396 try 397 { 398 Service service = getServiceInstance(name); 399 if (service != null && service.getInit()) 400 { 401 service.shutdown(); 402 403 if (service.getInit() && service instanceof BaseService) 404 { 405 // BaseService::shutdown() does this by default, 406 // but could've been overriden poorly. 407 ((BaseService) service).setInit(false); 408 } 409 } 410 } 411 catch (InstantiationException e) 412 { 413 // Assuming harmless -- log the error and continue. 414 log.error("Shutdown of a nonexistent Service '" 415 + name + "' was requested", e); 416 } 417 } 418 419 /** 420 * Shuts down all Turbine services, releasing allocated resources and 421 * returning them to their initial (uninitialized) state. 422 */ 423 public void shutdownServices() 424 { 425 log.info("Shutting down all services!"); 426 427 String serviceName = null; 428 429 /* 430 * Now we want to reverse the order of 431 * this list. This functionality should be added to 432 * the ExtendedProperties in the commons but 433 * this will fix the problem for now. 434 */ 435 436 ArrayList<String> reverseServicesList = new ArrayList<String>(); 437 438 for (Iterator<String> serviceNames = getServiceNames(); serviceNames.hasNext();) 439 { 440 serviceName = serviceNames.next(); 441 reverseServicesList.add(0, serviceName); 442 } 443 444 for (Iterator<String> serviceNames = reverseServicesList.iterator(); serviceNames.hasNext();) 445 { 446 serviceName = serviceNames.next(); 447 log.info("Shutting down service: " + serviceName); 448 shutdownService(serviceName); 449 } 450 } 451 452 /** 453 * Returns an instance of requested Service. 454 * 455 * @param name The name of the Service requested. 456 * @return An instance of requested Service. 457 * @exception InstantiationException if the service is unknown or 458 * can't be initialized. 459 */ 460 public Object getService(String name) throws InstantiationException 461 { 462 Service service; 463 464 if (this.isLocalService(name)) 465 { 466 try 467 { 468 service = getServiceInstance(name); 469 if (!service.getInit()) 470 { 471 synchronized (service.getClass()) 472 { 473 if (!service.getInit()) 474 { 475 log.info("Start Initializing service (late): " + name); 476 service.init(); 477 log.info("Finish Initializing service (late): " + name); 478 } 479 } 480 } 481 if (!service.getInit()) 482 { 483 // this exception will be caught & rethrown by this very method. 484 // getInit() returning false indicates some initialization issue, 485 // which in turn prevents the InitableBroker from passing a 486 // reference to a working instance of the initable to the client. 487 throw new InitializationException( 488 "init() failed to initialize service " + name); 489 } 490 return service; 491 } 492 catch (InitializationException e) 493 { 494 throw new InstantiationException("Service " + name + 495 " failed to initialize", e); 496 } 497 } 498 else if (this.isNonLocalService(name)) 499 { 500 return this.getNonLocalService(name); 501 } 502 else 503 { 504 throw new InstantiationException( 505 "ServiceBroker: unknown service " + name 506 + " requested"); 507 } 508 } 509 510 /** 511 * Retrieves an instance of a Service without triggering late 512 * initialization. 513 * 514 * Early initialization of a Service can require access to Service 515 * properties. The Service must have its name and serviceBroker 516 * set by then. Therefore, before calling 517 * Initable.initClass(Object), the class must be instantiated with 518 * InitableBroker.getInitableInstance(), and 519 * Service.setServiceBroker() and Service.setName() must be 520 * called. This calls for two - level accessing the Services 521 * instances. 522 * 523 * @param name The name of the service requested. 524 * @exception InstantiationException The service is unknown or 525 * can't be initialized. 526 */ 527 protected Service getServiceInstance(String name) 528 throws InstantiationException 529 { 530 Service service = services.get(name); 531 532 if (service == null) 533 { 534 535 String className=null; 536 if (!this.isLocalService(name)) 537 { 538 throw new InstantiationException( 539 "ServiceBroker: unknown service " + name 540 + " requested"); 541 } 542 try 543 { 544 className = mapping.getString(name); 545 service = services.get(className); 546 547 if (service == null) 548 { 549 try 550 { 551 service = (Service) Class.forName(className).newInstance(); 552 553 // check if the newly created service is also a 554 // service provider - if so then remember it 555 if (service instanceof TurbineServiceProvider) 556 { 557 this.serviceProviderInstanceMap.put(name,service); 558 } 559 560 } 561 // those two errors must be passed to the VM 562 catch (ThreadDeath t) 563 { 564 throw t; 565 } 566 catch (OutOfMemoryError t) 567 { 568 throw t; 569 } 570 catch (Throwable t) 571 { 572 // Used to indicate error condition. 573 String msg = null; 574 575 if (t instanceof NoClassDefFoundError) 576 { 577 msg = "A class referenced by " + className + 578 " is unavailable. Check your jars and classes."; 579 } 580 else if (t instanceof ClassNotFoundException) 581 { 582 msg = "Class " + className + 583 " is unavailable. Check your jars and classes."; 584 } 585 else if (t instanceof ClassCastException) 586 { 587 msg = "Class " + className + 588 " doesn't implement the Service interface"; 589 } 590 else 591 { 592 msg = "Failed to instantiate " + className; 593 } 594 595 throw new InstantiationException(msg, t); 596 } 597 } 598 } 599 catch (ClassCastException e) 600 { 601 throw new InstantiationException("ServiceBroker: Class " 602 + className 603 + " does not implement Service interface.", e); 604 } 605 catch (InstantiationException e) 606 { 607 throw new InstantiationException( 608 "Failed to instantiate service " + name, e); 609 } 610 service.setServiceBroker(this); 611 service.setName(name); 612 services.put(name, service); 613 } 614 615 return service; 616 } 617 618 /** 619 * Returns the configuration for the specified service. 620 * 621 * @param name The name of the service. 622 * @return Configuration of requested Service. 623 */ 624 public Configuration getConfiguration(String name) 625 { 626 return configuration.subset(SERVICE_PREFIX + name); 627 } 628 629 /** 630 * Set the application root. 631 * 632 * @param applicationRoot application root 633 */ 634 public void setApplicationRoot(String applicationRoot) 635 { 636 this.applicationRoot = applicationRoot; 637 } 638 639 /** 640 * Get the application root as set by 641 * the parent application. 642 * 643 * @return String application root 644 */ 645 public String getApplicationRoot() 646 { 647 return applicationRoot; 648 } 649 650 /** 651 * Determines if the requested service is managed by this 652 * ServiceBroker. 653 * 654 * @param name The name of the Service requested. 655 * @return true if the service is managed by the this ServiceBroker 656 */ 657 protected boolean isLocalService(String name) 658 { 659 return this.mapping.containsKey(name); 660 } 661 662 /** 663 * Determines if the requested service is managed by an initialized 664 * TurbineServiceProvider. We use the service names to lookup 665 * the TurbineServiceProvider to ensure that we get a fully 666 * inititialized service. 667 * 668 * @param name The name of the Service requested. 669 * @return true if the service is managed by a TurbineServiceProvider 670 */ 671 protected boolean isNonLocalService(String name) 672 { 673 String serviceName = null; 674 TurbineServiceProvider turbineServiceProvider = null; 675 Enumeration<String> list = this.serviceProviderInstanceMap.keys(); 676 677 while (list.hasMoreElements()) 678 { 679 serviceName = list.nextElement(); 680 turbineServiceProvider = (TurbineServiceProvider) this.getService(serviceName); 681 682 if (turbineServiceProvider.exists(name)) 683 { 684 return true; 685 } 686 } 687 688 return false; 689 } 690 691 /** 692 * Get a non-local service managed by a TurbineServiceProvider. 693 * 694 * @param name The name of the Service requested. 695 * @return the requested service 696 * @throws InstantiationException the service couldn't be instantiated 697 */ 698 protected Object getNonLocalService(String name) 699 throws InstantiationException 700 { 701 String serviceName = null; 702 TurbineServiceProvider turbineServiceProvider = null; 703 Enumeration<String> list = this.serviceProviderInstanceMap.keys(); 704 705 while (list.hasMoreElements()) 706 { 707 serviceName = list.nextElement(); 708 turbineServiceProvider = (TurbineServiceProvider) this.getService(serviceName); 709 710 if (turbineServiceProvider.exists(name)) 711 { 712 return turbineServiceProvider.get(name); 713 } 714 } 715 716 throw new InstantiationException( 717 "ServiceBroker: unknown non-local service " + name 718 + " requested"); 719 } 720 }