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