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