001// Copyright 2006-2014 The Apache Software Foundation 002// 003// Licensed under the Apache License, Version 2.0 (the "License"); 004// you may not use this file except in compliance with the License. 005// You may obtain a copy of the License at 006// 007// http://www.apache.org/licenses/LICENSE-2.0 008// 009// Unless required by applicable law or agreed to in writing, software 010// distributed under the License is distributed on an "AS IS" BASIS, 011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012// See the License for the specific language governing permissions and 013// limitations under the License. 014 015package org.apache.tapestry5.ioc.internal; 016 017import org.apache.tapestry5.func.F; 018import org.apache.tapestry5.func.Flow; 019import org.apache.tapestry5.func.Mapper; 020import org.apache.tapestry5.func.Predicate; 021import org.apache.tapestry5.ioc.*; 022import org.apache.tapestry5.ioc.annotations.Local; 023import org.apache.tapestry5.ioc.def.*; 024import org.apache.tapestry5.ioc.internal.services.PerthreadManagerImpl; 025import org.apache.tapestry5.ioc.internal.services.RegistryShutdownHubImpl; 026import org.apache.tapestry5.ioc.internal.util.*; 027import org.apache.tapestry5.ioc.modules.TapestryIOCModule; 028import org.apache.tapestry5.ioc.services.*; 029import org.apache.tapestry5.ioc.util.AvailableValues; 030import org.apache.tapestry5.ioc.util.UnknownValueException; 031import org.apache.tapestry5.services.UpdateListenerHub; 032import org.slf4j.Logger; 033 034import java.io.IOException; 035import java.lang.annotation.Annotation; 036import java.lang.reflect.Constructor; 037import java.lang.reflect.InvocationHandler; 038import java.lang.reflect.Method; 039import java.lang.reflect.Proxy; 040import java.util.*; 041 042@SuppressWarnings("all") 043public class RegistryImpl implements Registry, InternalRegistry, ServiceProxyProvider 044{ 045 private static final String SYMBOL_SOURCE_SERVICE_ID = "SymbolSource"; 046 047 private static final String REGISTRY_SHUTDOWN_HUB_SERVICE_ID = "RegistryShutdownHub"; 048 049 static final String PERTHREAD_MANAGER_SERVICE_ID = "PerthreadManager"; 050 051 private static final String SERVICE_ACTIVITY_SCOREBOARD_SERVICE_ID = "ServiceActivityScoreboard"; 052 053 /** 054 * The set of marker annotations for a builtin service. 055 */ 056 private final static Set<Class> BUILTIN = CollectionFactory.newSet(); 057 058 // Split create/assign to appease generics gods 059 static 060 { 061 BUILTIN.add(Builtin.class); 062 } 063 064 065 static final String PLASTIC_PROXY_FACTORY_SERVICE_ID = "PlasticProxyFactory"; 066 067 static final String LOGGER_SOURCE_SERVICE_ID = "LoggerSource"; 068 069 private final OneShotLock lock = new OneShotLock(); 070 071 private final OneShotLock eagerLoadLock = new OneShotLock(); 072 073 private final Map<String, Object> builtinServices = CollectionFactory.newCaseInsensitiveMap(); 074 075 private final Map<String, Class> builtinTypes = CollectionFactory.newCaseInsensitiveMap(); 076 077 private final RegistryShutdownHubImpl registryShutdownHub; 078 079 private final LoggerSource loggerSource; 080 081 /** 082 * Map from service id to the Module that contains the service. 083 */ 084 private final Map<String, Module> serviceIdToModule = CollectionFactory.newCaseInsensitiveMap(); 085 086 private final Map<String, ServiceLifecycle2> lifecycles = CollectionFactory.newCaseInsensitiveMap(); 087 088 private final PerthreadManager perthreadManager; 089 090 private final PlasticProxyFactory proxyFactory; 091 092 private final ServiceActivityTracker tracker; 093 094 private SymbolSource symbolSource; 095 096 private final Map<Module, Set<ServiceDef2>> moduleToServiceDefs = CollectionFactory.newMap(); 097 098 /** 099 * From marker type to a list of marked service instances. 100 */ 101 private final Map<Class, List<ServiceDef2>> markerToServiceDef = CollectionFactory.newMap(); 102 103 private final Set<ServiceDef2> allServiceDefs = CollectionFactory.newSet(); 104 105 private final OperationTracker operationTracker; 106 107 private final TypeCoercerProxy typeCoercerProxy = new TypeCoercerProxyImpl(this); 108 109 private final Map<Class<? extends Annotation>, Annotation> cachedAnnotationProxies = CollectionFactory.newConcurrentMap(); 110 111 private final Set<Runnable> startups = CollectionFactory.newSet(); 112 113 private DelegatingServiceConfigurationListener serviceConfigurationListener; 114 115 /** 116 * Constructs the registry from a set of module definitions and other resources. 117 * 118 * @param moduleDefs 119 * defines the modules (and builders, decorators, etc., within) 120 * @param proxyFactory 121 * used to create new proxy objects 122 * @param loggerSource 123 * used to obtain Logger instances 124 * @param operationTracker 125 */ 126 public RegistryImpl(Collection<ModuleDef2> moduleDefs, PlasticProxyFactory proxyFactory, 127 LoggerSource loggerSource, OperationTracker operationTracker) 128 { 129 assert moduleDefs != null; 130 assert proxyFactory != null; 131 assert loggerSource != null; 132 assert operationTracker != null; 133 134 this.loggerSource = loggerSource; 135 this.operationTracker = operationTracker; 136 137 this.proxyFactory = proxyFactory; 138 139 serviceConfigurationListener = new DelegatingServiceConfigurationListener( 140 loggerForBuiltinService(ServiceConfigurationListener.class.getSimpleName())); 141 142 Logger logger = loggerForBuiltinService(PERTHREAD_MANAGER_SERVICE_ID); 143 144 PerthreadManagerImpl ptmImpl = new PerthreadManagerImpl(logger); 145 146 perthreadManager = ptmImpl; 147 148 final ServiceActivityTrackerImpl scoreboardAndTracker = new ServiceActivityTrackerImpl(perthreadManager); 149 150 tracker = scoreboardAndTracker; 151 152 logger = loggerForBuiltinService(REGISTRY_SHUTDOWN_HUB_SERVICE_ID); 153 154 registryShutdownHub = new RegistryShutdownHubImpl(logger); 155 ptmImpl.registerForShutdown(registryShutdownHub); 156 157 lifecycles.put("singleton", new SingletonServiceLifecycle()); 158 159 registryShutdownHub.addRegistryShutdownListener(new Runnable() 160 { 161 @Override 162 public void run() 163 { 164 scoreboardAndTracker.shutdown(); 165 } 166 }); 167 168 for (ModuleDef2 def : moduleDefs) 169 { 170 logger = this.loggerSource.getLogger(def.getLoggerName()); 171 172 Module module = new ModuleImpl(this, tracker, def, proxyFactory, logger); 173 174 Set<ServiceDef2> moduleServiceDefs = CollectionFactory.newSet(); 175 176 for (String serviceId : def.getServiceIds()) 177 { 178 ServiceDef2 serviceDef = module.getServiceDef(serviceId); 179 180 moduleServiceDefs.add(serviceDef); 181 allServiceDefs.add(serviceDef); 182 183 Module existing = serviceIdToModule.get(serviceId); 184 185 if (existing != null) 186 throw new RuntimeException(IOCMessages.serviceIdConflict(serviceId, 187 existing.getServiceDef(serviceId), serviceDef)); 188 189 serviceIdToModule.put(serviceId, module); 190 191 // The service is defined but will not have gone further than that. 192 tracker.define(serviceDef, Status.DEFINED); 193 194 for (Class marker : serviceDef.getMarkers()) 195 InternalUtils.addToMapList(markerToServiceDef, marker, serviceDef); 196 } 197 198 moduleToServiceDefs.put(module, moduleServiceDefs); 199 200 addStartupsInModule(def, module, logger); 201 } 202 203 addBuiltin(SERVICE_ACTIVITY_SCOREBOARD_SERVICE_ID, ServiceActivityScoreboard.class, scoreboardAndTracker); 204 addBuiltin(LOGGER_SOURCE_SERVICE_ID, LoggerSource.class, this.loggerSource); 205 addBuiltin(PERTHREAD_MANAGER_SERVICE_ID, PerthreadManager.class, perthreadManager); 206 addBuiltin(REGISTRY_SHUTDOWN_HUB_SERVICE_ID, RegistryShutdownHub.class, registryShutdownHub); 207 addBuiltin(PLASTIC_PROXY_FACTORY_SERVICE_ID, PlasticProxyFactory.class, proxyFactory); 208 209 validateContributeDefs(moduleDefs); 210 211 serviceConfigurationListener.setDelegates(getService(ServiceConfigurationListenerHub.class).getListeners()); 212 213 scoreboardAndTracker.startup(); 214 215 SerializationSupport.setProvider(this); 216 217 } 218 219 private void addStartupsInModule(ModuleDef2 def, final Module module, final Logger logger) 220 { 221 for (final StartupDef startup : def.getStartups()) 222 { 223 224 startups.add(new Runnable() 225 { 226 @Override 227 public void run() 228 { 229 startup.invoke(module, RegistryImpl.this, RegistryImpl.this, logger); 230 } 231 }); 232 } 233 } 234 235 /** 236 * Validate that each module's ContributeDefs correspond to an actual service. 237 */ 238 private void validateContributeDefs(Collection<ModuleDef2> moduleDefs) 239 { 240 for (ModuleDef2 module : moduleDefs) 241 { 242 Set<ContributionDef> contributionDefs = module.getContributionDefs(); 243 244 for (ContributionDef cd : contributionDefs) 245 { 246 String serviceId = cd.getServiceId(); 247 248 ContributionDef3 cd3 = InternalUtils.toContributionDef3(cd); 249 250 // Ignore any optional contribution methods; there's no way to validate that 251 // they contribute to a known service ... that's the point of @Optional 252 253 if (cd3.isOptional()) 254 { 255 continue; 256 } 257 258 // Otherwise, check that the service being contributed to exists ... 259 260 if (cd3.getServiceId() != null) 261 { 262 if (!serviceIdToModule.containsKey(serviceId)) 263 { 264 throw new IllegalArgumentException( 265 IOCMessages.contributionForNonexistentService(cd)); 266 } 267 } else if (!isContributionForExistentService(module, cd3)) 268 { 269 throw new IllegalArgumentException( 270 IOCMessages.contributionForUnqualifiedService(cd3)); 271 } 272 } 273 } 274 275 } 276 277 /** 278 * Invoked when the contribution method didn't follow the naming convention and so doesn't identify 279 * a service by id; instead there was an @Contribute to identify the service interface. 280 */ 281 @SuppressWarnings("all") 282 private boolean isContributionForExistentService(ModuleDef moduleDef, final ContributionDef2 cd) 283 { 284 final Set<Class> contributionMarkers = new HashSet(cd.getMarkers()); 285 286 boolean localOnly = contributionMarkers.contains(Local.class); 287 288 Flow<ServiceDef2> serviceDefs = localOnly ? getLocalServiceDefs(moduleDef) : F.flow(allServiceDefs); 289 290 contributionMarkers.retainAll(getMarkerAnnotations()); 291 contributionMarkers.remove(Local.class); 292 293 // Match services with the correct interface AND having as markers *all* the marker annotations 294 295 Flow<ServiceDef2> filtered = serviceDefs.filter(F.and(new Predicate<ServiceDef2>() 296 { 297 @Override 298 public boolean accept(ServiceDef2 object) 299 { 300 return object.getServiceInterface().equals(cd.getServiceInterface()); 301 } 302 }, new Predicate<ServiceDef2>() 303 { 304 @Override 305 public boolean accept(ServiceDef2 serviceDef) 306 { 307 return serviceDef.getMarkers().containsAll(contributionMarkers); 308 } 309 } 310 )); 311 312 // That's a lot of logic; the good news is it will short-circuit as soon as it finds a single match, 313 // thanks to the laziness inside Flow. 314 315 return !filtered.isEmpty(); 316 } 317 318 private Flow<ServiceDef2> getLocalServiceDefs(final ModuleDef moduleDef) 319 { 320 return F.flow(moduleDef.getServiceIds()).map(new Mapper<String, ServiceDef2>() 321 { 322 @Override 323 public ServiceDef2 map(String value) 324 { 325 return InternalUtils.toServiceDef2(moduleDef.getServiceDef(value)); 326 } 327 }); 328 } 329 330 /** 331 * It's not unreasonable for an eagerly-loaded service to decide to start a thread, at which 332 * point we raise issues 333 * about improper publishing of the Registry instance from the RegistryImpl constructor. Moving 334 * eager loading of 335 * services out to its own method should ensure thread safety. 336 */ 337 @Override 338 public void performRegistryStartup() 339 { 340 if (JDKUtils.JDK_1_5) 341 { 342 throw new RuntimeException("Your JDK version is too old." 343 + " Tapestry requires Java 1.6 or newer since version 5.4."); 344 } 345 eagerLoadLock.lock(); 346 347 List<EagerLoadServiceProxy> proxies = CollectionFactory.newList(); 348 349 for (Module m : moduleToServiceDefs.keySet()) 350 m.collectEagerLoadServices(proxies); 351 352 // TAPESTRY-2267: Gather up all the proxies before instantiating any of them. 353 354 for (EagerLoadServiceProxy proxy : proxies) 355 { 356 proxy.eagerLoadService(); 357 } 358 359 for (Runnable startup : startups) { 360 startup.run(); 361 } 362 363 startups.clear(); 364 365 getService("RegistryStartup", Runnable.class).run(); 366 367 cleanupThread(); 368 } 369 370 @Override 371 public Logger getServiceLogger(String serviceId) 372 { 373 Module module = serviceIdToModule.get(serviceId); 374 375 assert module != null; 376 377 return loggerSource.getLogger(module.getLoggerName() + "." + serviceId); 378 } 379 380 private Logger loggerForBuiltinService(String serviceId) 381 { 382 return loggerSource.getLogger(TapestryIOCModule.class + "." + serviceId); 383 } 384 385 private <T> void addBuiltin(final String serviceId, final Class<T> serviceInterface, T service) 386 { 387 builtinTypes.put(serviceId, serviceInterface); 388 builtinServices.put(serviceId, service); 389 390 // Make sure each of the builtin services is also available via the Builtin annotation 391 // marker. 392 393 ServiceDef2 serviceDef = new ServiceDef2() 394 { 395 @Override 396 public ObjectCreator createServiceCreator(ServiceBuilderResources resources) 397 { 398 return null; 399 } 400 401 @Override 402 public Set<Class> getMarkers() 403 { 404 return BUILTIN; 405 } 406 407 @Override 408 public String getServiceId() 409 { 410 return serviceId; 411 } 412 413 @Override 414 public Class getServiceInterface() 415 { 416 return serviceInterface; 417 } 418 419 @Override 420 public String getServiceScope() 421 { 422 return ScopeConstants.DEFAULT; 423 } 424 425 @Override 426 public boolean isEagerLoad() 427 { 428 return false; 429 } 430 431 @Override 432 public boolean isPreventDecoration() 433 { 434 return true; 435 } 436 437 @Override 438 public int hashCode() 439 { 440 final int prime = 31; 441 int result = 1; 442 result = prime * result + ((serviceId == null) ? 0 : serviceId.hashCode()); 443 return result; 444 } 445 446 @Override 447 public boolean equals(Object obj) 448 { 449 if (this == obj) { return true; } 450 if (obj == null) { return false; } 451 if (!(obj instanceof ServiceDefImpl)) { return false; } 452 ServiceDef other = (ServiceDef) obj; 453 if (serviceId == null) 454 { 455 if (other.getServiceId() != null) { return false; } 456 } 457 else if (!serviceId.equals(other.getServiceId())) { return false; } 458 return true; 459 } 460 461 }; 462 463 for (Class marker : serviceDef.getMarkers()) 464 { 465 InternalUtils.addToMapList(markerToServiceDef, marker, serviceDef); 466 allServiceDefs.add(serviceDef); 467 } 468 469 tracker.define(serviceDef, Status.BUILTIN); 470 } 471 472 @Override 473 public synchronized void shutdown() 474 { 475 lock.lock(); 476 477 registryShutdownHub.fireRegistryDidShutdown(); 478 479 SerializationSupport.clearProvider(this); 480 } 481 482 @Override 483 public <T> T getService(String serviceId, Class<T> serviceInterface) 484 { 485 lock.check(); 486 487 T result = checkForBuiltinService(serviceId, serviceInterface); 488 if (result != null) 489 return result; 490 491 // Checking serviceId and serviceInterface is overkill; they have been checked and rechecked 492 // all the way to here. 493 494 Module containingModule = locateModuleForService(serviceId); 495 496 return containingModule.getService(serviceId, serviceInterface); 497 } 498 499 private <T> T checkForBuiltinService(String serviceId, Class<T> serviceInterface) 500 { 501 Object service = builtinServices.get(serviceId); 502 503 if (service == null) 504 return null; 505 506 try 507 { 508 return serviceInterface.cast(service); 509 } catch (ClassCastException ex) 510 { 511 throw new RuntimeException(IOCMessages.serviceWrongInterface(serviceId, builtinTypes.get(serviceId), 512 serviceInterface)); 513 } 514 } 515 516 @Override 517 public void cleanupThread() 518 { 519 lock.check(); 520 521 perthreadManager.cleanup(); 522 } 523 524 private Module locateModuleForService(String serviceId) 525 { 526 Module module = serviceIdToModule.get(serviceId); 527 528 if (module == null) 529 throw new UnknownValueException(String.format("Service id '%s' is not defined by any module.", serviceId), 530 new AvailableValues("Defined service ids", serviceIdToModule)); 531 532 return module; 533 } 534 535 @Override 536 public <T> Collection<T> getUnorderedConfiguration(ServiceDef3 serviceDef, Class<T> objectType) 537 { 538 lock.check(); 539 540 final Collection<T> result = CollectionFactory.newList(); 541 542 for (Module m : moduleToServiceDefs.keySet()) 543 addToUnorderedConfiguration(result, objectType, serviceDef, m); 544 545 if (!isServiceConfigurationListenerServiceDef(serviceDef)) 546 { 547 serviceConfigurationListener.onUnorderedConfiguration(serviceDef, result); 548 } 549 550 return result; 551 } 552 553 @Override 554 @SuppressWarnings("unchecked") 555 public <T> List<T> getOrderedConfiguration(ServiceDef3 serviceDef, Class<T> objectType) 556 { 557 lock.check(); 558 559 String serviceId = serviceDef.getServiceId(); 560 Logger logger = getServiceLogger(serviceId); 561 562 Orderer<T> orderer = new Orderer<T>(logger); 563 Map<String, OrderedConfigurationOverride<T>> overrides = CollectionFactory.newCaseInsensitiveMap(); 564 565 // TAP5-2129. NOTICE: if someday an ordering between modules is added, this should be reverted 566 // or a notice added to the documentation. 567 List<Module> modules = new ArrayList<Module>(moduleToServiceDefs.keySet()); 568 Collections.sort(modules, new ModuleComparator()); 569 570 for (Module m : modules) 571 addToOrderedConfiguration(orderer, overrides, objectType, serviceDef, m); 572 573 // An ugly hack ... perhaps we should introduce a new builtin service so that this can be 574 // accomplished in the normal way? 575 576 if (serviceId.equals("MasterObjectProvider")) 577 { 578 ObjectProvider contribution = new ObjectProvider() 579 { 580 @Override 581 public <T> T provide(Class<T> objectType, AnnotationProvider annotationProvider, ObjectLocator locator) 582 { 583 return findServiceByMarkerAndType(objectType, annotationProvider, null); 584 } 585 }; 586 587 orderer.add("ServiceByMarker", (T) contribution); 588 } 589 590 for (OrderedConfigurationOverride<T> override : overrides.values()) 591 override.apply(); 592 593 final List<T> result = orderer.getOrdered(); 594 595 if (!isServiceConfigurationListenerServiceDef(serviceDef)) 596 { 597 serviceConfigurationListener.onOrderedConfiguration(serviceDef, result); 598 } 599 600 return result; 601 } 602 603 private boolean isServiceConfigurationListenerServiceDef(ServiceDef serviceDef) 604 { 605 return serviceDef.getServiceId().equalsIgnoreCase(ServiceConfigurationListener.class.getSimpleName()); 606 } 607 608 @Override 609 public <K, V> Map<K, V> getMappedConfiguration(ServiceDef3 serviceDef, Class<K> keyType, Class<V> objectType) 610 { 611 lock.check(); 612 613 // When the key type is String, then a case insensitive map is used. 614 615 Map<K, V> result = newConfigurationMap(keyType); 616 Map<K, ContributionDef> keyToContribution = newConfigurationMap(keyType); 617 Map<K, MappedConfigurationOverride<K, V>> overrides = newConfigurationMap(keyType); 618 619 for (Module m : moduleToServiceDefs.keySet()) 620 addToMappedConfiguration(result, overrides, keyToContribution, keyType, objectType, serviceDef, m); 621 622 for (MappedConfigurationOverride<K, V> override : overrides.values()) 623 { 624 override.apply(); 625 } 626 627 if (!isServiceConfigurationListenerServiceDef(serviceDef)) 628 { 629 serviceConfigurationListener.onMappedConfiguration(serviceDef, result); 630 } 631 632 return result; 633 } 634 635 @SuppressWarnings("unchecked") 636 private <K, V> Map<K, V> newConfigurationMap(Class<K> keyType) 637 { 638 if (keyType.equals(String.class)) 639 { 640 Map<String, K> result = CollectionFactory.newCaseInsensitiveMap(); 641 642 return (Map<K, V>) result; 643 } 644 645 return CollectionFactory.newMap(); 646 } 647 648 private <K, V> void addToMappedConfiguration(Map<K, V> map, Map<K, MappedConfigurationOverride<K, V>> overrides, 649 Map<K, ContributionDef> keyToContribution, Class<K> keyClass, Class<V> valueType, ServiceDef3 serviceDef, 650 final Module module) 651 { 652 String serviceId = serviceDef.getServiceId(); 653 Set<ContributionDef2> contributions = module.getContributorDefsForService(serviceDef); 654 655 if (contributions.isEmpty()) 656 return; 657 658 Logger logger = getServiceLogger(serviceId); 659 660 final ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger); 661 662 for (final ContributionDef def : contributions) 663 { 664 final MappedConfiguration<K, V> validating = new ValidatingMappedConfigurationWrapper<K, V>(valueType, 665 resources, typeCoercerProxy, map, overrides, serviceId, def, keyClass, keyToContribution); 666 667 String description = "Invoking " + def; 668 669 logger.debug(description); 670 671 operationTracker.run(description, new Runnable() 672 { 673 @Override 674 public void run() 675 { 676 def.contribute(module, resources, validating); 677 } 678 }); 679 } 680 } 681 682 private <T> void addToUnorderedConfiguration(Collection<T> collection, Class<T> valueType, ServiceDef3 serviceDef, 683 final Module module) 684 { 685 String serviceId = serviceDef.getServiceId(); 686 Set<ContributionDef2> contributions = module.getContributorDefsForService(serviceDef); 687 688 if (contributions.isEmpty()) 689 return; 690 691 Logger logger = getServiceLogger(serviceId); 692 693 final ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger); 694 695 for (final ContributionDef def : contributions) 696 { 697 final Configuration<T> validating = new ValidatingConfigurationWrapper<T>(valueType, resources, 698 typeCoercerProxy, collection, serviceId); 699 700 String description = "Invoking " + def; 701 702 logger.debug(description); 703 704 operationTracker.run(description, new Runnable() 705 { 706 @Override 707 public void run() 708 { 709 def.contribute(module, resources, validating); 710 } 711 }); 712 } 713 } 714 715 private <T> void addToOrderedConfiguration(Orderer<T> orderer, 716 Map<String, OrderedConfigurationOverride<T>> overrides, Class<T> valueType, ServiceDef3 serviceDef, 717 final Module module) 718 { 719 String serviceId = serviceDef.getServiceId(); 720 Set<ContributionDef2> contributions = module.getContributorDefsForService(serviceDef); 721 722 if (contributions.isEmpty()) 723 return; 724 725 Logger logger = getServiceLogger(serviceId); 726 727 final ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger); 728 729 for (final ContributionDef def : contributions) 730 { 731 final OrderedConfiguration<T> validating = new ValidatingOrderedConfigurationWrapper<T>(valueType, 732 resources, typeCoercerProxy, orderer, overrides, def); 733 734 String description = "Invoking " + def; 735 736 logger.debug(description); 737 738 operationTracker.run(description, new Runnable() 739 { 740 @Override 741 public void run() 742 { 743 def.contribute(module, resources, validating); 744 } 745 }); 746 } 747 } 748 749 @Override 750 public <T> T getService(Class<T> serviceInterface) 751 { 752 lock.check(); 753 754 return getServiceByTypeAndMarkers(serviceInterface); 755 } 756 757 @Override 758 public <T> T getService(Class<T> serviceInterface, Class<? extends Annotation>... markerTypes) 759 { 760 lock.check(); 761 762 return getServiceByTypeAndMarkers(serviceInterface, markerTypes); 763 } 764 765 private <T> T getServiceByTypeAlone(Class<T> serviceInterface) 766 { 767 List<String> serviceIds = findServiceIdsForInterface(serviceInterface); 768 769 if (serviceIds == null) 770 serviceIds = Collections.emptyList(); 771 772 switch (serviceIds.size()) 773 { 774 case 0: 775 776 throw new RuntimeException(IOCMessages.noServiceMatchesType(serviceInterface)); 777 778 case 1: 779 780 String serviceId = serviceIds.get(0); 781 782 return getService(serviceId, serviceInterface); 783 784 default: 785 786 Collections.sort(serviceIds); 787 788 throw new RuntimeException(IOCMessages.manyServiceMatches(serviceInterface, serviceIds)); 789 } 790 } 791 792 private <T> T getServiceByTypeAndMarkers(Class<T> serviceInterface, Class<? extends Annotation>... markerTypes) 793 { 794 if (markerTypes.length == 0) 795 { 796 return getServiceByTypeAlone(serviceInterface); 797 } 798 799 AnnotationProvider provider = createAnnotationProvider(markerTypes); 800 801 Set<ServiceDef2> matches = CollectionFactory.newSet(); 802 List<Class> markers = CollectionFactory.newList(); 803 804 findServiceDefsMatchingMarkerAndType(serviceInterface, provider, null, markers, matches); 805 806 return extractServiceFromMatches(serviceInterface, markers, matches); 807 } 808 809 private AnnotationProvider createAnnotationProvider(Class<? extends Annotation>... markerTypes) 810 { 811 final Map<Class<? extends Annotation>, Annotation> map = CollectionFactory.newMap(); 812 813 for (Class<? extends Annotation> markerType : markerTypes) 814 { 815 map.put(markerType, createAnnotationProxy(markerType)); 816 } 817 818 return new AnnotationProvider() 819 { 820 @Override 821 public <T extends Annotation> T getAnnotation(Class<T> annotationClass) 822 { 823 return annotationClass.cast(map.get(annotationClass)); 824 } 825 }; 826 } 827 828 private <A extends Annotation> Annotation createAnnotationProxy(final Class<A> annotationType) 829 { 830 Annotation result = cachedAnnotationProxies.get(annotationType); 831 832 if (result == null) 833 { 834 // We create a JDK proxy because its pretty quick and easy. 835 836 InvocationHandler handler = new InvocationHandler() 837 { 838 @Override 839 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 840 { 841 if (method.getName().equals("annotationType")) 842 { 843 return annotationType; 844 } 845 846 return method.invoke(proxy, args); 847 } 848 }; 849 850 result = (Annotation) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), 851 new Class[]{annotationType}, 852 handler); 853 854 cachedAnnotationProxies.put(annotationType, result); 855 } 856 857 return result; 858 } 859 860 private List<String> findServiceIdsForInterface(Class serviceInterface) 861 { 862 List<String> result = CollectionFactory.newList(); 863 864 for (Module module : moduleToServiceDefs.keySet()) 865 result.addAll(module.findServiceIdsForInterface(serviceInterface)); 866 867 for (Map.Entry<String, Object> entry : builtinServices.entrySet()) 868 { 869 if (serviceInterface.isInstance(entry.getValue())) 870 result.add(entry.getKey()); 871 } 872 873 Collections.sort(result); 874 875 return result; 876 } 877 878 @Override 879 public ServiceLifecycle2 getServiceLifecycle(String scope) 880 { 881 lock.check(); 882 883 ServiceLifecycle result = lifecycles.get(scope); 884 885 if (result == null) 886 { 887 ServiceLifecycleSource source = getService("ServiceLifecycleSource", ServiceLifecycleSource.class); 888 889 result = source.get(scope); 890 } 891 892 if (result == null) 893 throw new RuntimeException(IOCMessages.unknownScope(scope)); 894 895 return InternalUtils.toServiceLifecycle2(result); 896 } 897 898 @Override 899 public List<ServiceDecorator> findDecoratorsForService(ServiceDef3 serviceDef) 900 { 901 lock.check(); 902 903 assert serviceDef != null; 904 905 Logger logger = getServiceLogger(serviceDef.getServiceId()); 906 907 Orderer<ServiceDecorator> orderer = new Orderer<ServiceDecorator>(logger, true); 908 909 for (Module module : moduleToServiceDefs.keySet()) 910 { 911 Set<DecoratorDef> decoratorDefs = module.findMatchingDecoratorDefs(serviceDef); 912 913 if (decoratorDefs.isEmpty()) 914 continue; 915 916 ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger); 917 918 for (DecoratorDef decoratorDef : decoratorDefs) 919 { 920 ServiceDecorator decorator = decoratorDef.createDecorator(module, resources); 921 try 922 { 923 orderer.add(decoratorDef.getDecoratorId(), decorator, decoratorDef.getConstraints()); 924 } 925 catch (IllegalArgumentException e) { 926 throw new RuntimeException(String.format( 927 "Service %s has two different decorators methods named decorate%s in different module classes. " 928 + "You can solve this by renaming one of them and annotating it with @Match(\"%2$s\").", 929 serviceDef.getServiceId(), decoratorDef.getDecoratorId())); 930 } 931 } 932 } 933 934 return orderer.getOrdered(); 935 } 936 937 @Override 938 public List<ServiceAdvisor> findAdvisorsForService(ServiceDef3 serviceDef) 939 { 940 lock.check(); 941 942 assert serviceDef != null; 943 944 Logger logger = getServiceLogger(serviceDef.getServiceId()); 945 946 Orderer<ServiceAdvisor> orderer = new Orderer<ServiceAdvisor>(logger); 947 948 for (Module module : moduleToServiceDefs.keySet()) 949 { 950 Set<AdvisorDef> advisorDefs = module.findMatchingServiceAdvisors(serviceDef); 951 952 if (advisorDefs.isEmpty()) 953 continue; 954 955 ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger); 956 957 for (AdvisorDef advisorDef : advisorDefs) 958 { 959 ServiceAdvisor advisor = advisorDef.createAdvisor(module, resources); 960 961 orderer.add(advisorDef.getAdvisorId(), advisor, advisorDef.getConstraints()); 962 } 963 } 964 965 return orderer.getOrdered(); 966 } 967 968 @Override 969 public <T> T getObject(Class<T> objectType, AnnotationProvider annotationProvider, ObjectLocator locator, 970 Module localModule) 971 { 972 lock.check(); 973 974 AnnotationProvider effectiveProvider = annotationProvider != null ? annotationProvider 975 : new NullAnnotationProvider(); 976 977 // We do a check here for known marker/type combinations, so that you can use a marker 978 // annotation 979 // to inject into a contribution method that contributes to MasterObjectProvider. 980 // We also force a contribution into MasterObjectProvider to accomplish the same thing. 981 982 T result = findServiceByMarkerAndType(objectType, annotationProvider, localModule); 983 984 if (result != null) 985 return result; 986 987 MasterObjectProvider masterProvider = getService(IOCConstants.MASTER_OBJECT_PROVIDER_SERVICE_ID, 988 MasterObjectProvider.class); 989 990 return masterProvider.provide(objectType, effectiveProvider, locator, true); 991 } 992 993 private Collection<ServiceDef2> filterByType(Class<?> objectType, Collection<ServiceDef2> serviceDefs) 994 { 995 Collection<ServiceDef2> result = CollectionFactory.newSet(); 996 997 for (ServiceDef2 sd : serviceDefs) 998 { 999 if (objectType.isAssignableFrom(sd.getServiceInterface())) 1000 { 1001 result.add(sd); 1002 } 1003 } 1004 1005 return result; 1006 } 1007 1008 @SuppressWarnings("unchecked") 1009 private <T> T findServiceByMarkerAndType(Class<T> objectType, AnnotationProvider provider, Module localModule) 1010 { 1011 if (provider == null) 1012 return null; 1013 1014 Set<ServiceDef2> matches = CollectionFactory.newSet(); 1015 List<Class> markers = CollectionFactory.newList(); 1016 1017 findServiceDefsMatchingMarkerAndType(objectType, provider, localModule, markers, matches); 1018 1019 1020 // If didn't see @Local or any recognized marker annotation, then don't try to filter that 1021 // way. Continue on, eventually to the MasterObjectProvider service. 1022 1023 if (markers.isEmpty()) 1024 { 1025 return null; 1026 } 1027 1028 return extractServiceFromMatches(objectType, markers, matches); 1029 } 1030 1031 /** 1032 * Given markers and matches processed by {@link #findServiceDefsMatchingMarkerAndType(Class, org.apache.tapestry5.ioc.AnnotationProvider, Module, java.util.List, java.util.Set)}, this 1033 * finds the singular match, or reports an error for 0 or 2+ matches. 1034 */ 1035 private <T> T extractServiceFromMatches(Class<T> objectType, List<Class> markers, Set<ServiceDef2> matches) 1036 { 1037 switch (matches.size()) 1038 { 1039 1040 case 1: 1041 1042 ServiceDef def = matches.iterator().next(); 1043 1044 return getService(def.getServiceId(), objectType); 1045 1046 case 0: 1047 1048 // It's no accident that the user put the marker annotation at the injection 1049 // point, since it matches a known marker annotation, it better be there for 1050 // a reason. So if we don't get a match, we have to assume the user expected 1051 // one, and that is an error. 1052 1053 // This doesn't help when the user places an annotation they *think* is a marker 1054 // but isn't really a marker (because no service is marked by the annotation). 1055 1056 throw new RuntimeException(IOCMessages.noServicesMatchMarker(objectType, markers)); 1057 1058 default: 1059 throw new RuntimeException(IOCMessages.manyServicesMatchMarker(objectType, markers, matches)); 1060 } 1061 } 1062 1063 private <T> void findServiceDefsMatchingMarkerAndType(Class<T> objectType, AnnotationProvider provider, Module localModule, List<Class> markers, 1064 Set<ServiceDef2> matches) 1065 { 1066 assert provider != null; 1067 1068 boolean localOnly = localModule != null && provider.getAnnotation(Local.class) != null; 1069 1070 matches.addAll(filterByType(objectType, localOnly ? moduleToServiceDefs.get(localModule) : allServiceDefs)); 1071 1072 if (localOnly) 1073 { 1074 markers.add(Local.class); 1075 } 1076 1077 for (Class marker : markerToServiceDef.keySet()) 1078 { 1079 if (provider.getAnnotation(marker) == null) 1080 { 1081 continue; 1082 } 1083 1084 markers.add(marker); 1085 1086 matches.retainAll(markerToServiceDef.get(marker)); 1087 1088 if (matches.isEmpty()) 1089 { 1090 return; 1091 } 1092 } 1093 } 1094 1095 @Override 1096 public <T> T getObject(Class<T> objectType, AnnotationProvider annotationProvider) 1097 { 1098 return getObject(objectType, annotationProvider, this, null); 1099 } 1100 1101 @Override 1102 public void addRegistryShutdownListener(RegistryShutdownListener listener) 1103 { 1104 lock.check(); 1105 1106 registryShutdownHub.addRegistryShutdownListener(listener); 1107 } 1108 1109 @Override 1110 public void addRegistryShutdownListener(Runnable listener) 1111 { 1112 lock.check(); 1113 1114 registryShutdownHub.addRegistryShutdownListener(listener); 1115 } 1116 1117 @Override 1118 public void addRegistryWillShutdownListener(Runnable listener) 1119 { 1120 lock.check(); 1121 1122 registryShutdownHub.addRegistryWillShutdownListener(listener); 1123 } 1124 1125 @Override 1126 public String expandSymbols(String input) 1127 { 1128 lock.check(); 1129 1130 // Again, a bit of work to avoid instantiating the SymbolSource until absolutely necessary. 1131 1132 if (!InternalUtils.containsSymbols(input)) 1133 return input; 1134 1135 return getSymbolSource().expandSymbols(input); 1136 } 1137 1138 /** 1139 * Defers obtaining the symbol source until actually needed. 1140 */ 1141 private SymbolSource getSymbolSource() 1142 { 1143 if (symbolSource == null) 1144 symbolSource = getService(SYMBOL_SOURCE_SERVICE_ID, SymbolSource.class); 1145 1146 return symbolSource; 1147 } 1148 1149 @Override 1150 public <T> T autobuild(String description, final Class<T> clazz) 1151 { 1152 return invoke(description, new Invokable<T>() 1153 { 1154 @Override 1155 public T invoke() 1156 { 1157 return autobuild(clazz); 1158 } 1159 }); 1160 } 1161 1162 @Override 1163 public <T> T autobuild(final Class<T> clazz) 1164 { 1165 assert clazz != null; 1166 final Constructor constructor = InternalUtils.findAutobuildConstructor(clazz); 1167 1168 if (constructor == null) 1169 { 1170 throw new RuntimeException(IOCMessages.noAutobuildConstructor(clazz)); 1171 } 1172 1173 Map<Class, Object> resourcesMap = CollectionFactory.newMap(); 1174 resourcesMap.put(OperationTracker.class, RegistryImpl.this); 1175 1176 InjectionResources resources = new MapInjectionResources(resourcesMap); 1177 1178 ObjectCreator<T> plan = InternalUtils.createConstructorConstructionPlan(this, this, resources, null, "Invoking " + proxyFactory.getConstructorLocation(constructor).toString(), constructor); 1179 1180 return plan.createObject(); 1181 } 1182 1183 @Override 1184 public <T> T proxy(Class<T> interfaceClass, Class<? extends T> implementationClass) 1185 { 1186 return proxy(interfaceClass, implementationClass, this); 1187 } 1188 1189 @Override 1190 public <T> T proxy(Class<T> interfaceClass, Class<? extends T> implementationClass, ObjectLocator locator) 1191 { 1192 assert interfaceClass != null; 1193 assert implementationClass != null; 1194 1195 if (InternalUtils.SERVICE_CLASS_RELOADING_ENABLED && InternalUtils.isLocalFile(implementationClass)) 1196 return createReloadingProxy(interfaceClass, implementationClass, locator); 1197 1198 return createNonReloadingProxy(interfaceClass, implementationClass, locator); 1199 } 1200 1201 private <T> T createNonReloadingProxy(Class<T> interfaceClass, final Class<? extends T> implementationClass, 1202 final ObjectLocator locator) 1203 { 1204 final ObjectCreator<T> autobuildCreator = new ObjectCreator<T>() 1205 { 1206 @Override 1207 public T createObject() 1208 { 1209 return locator.autobuild(implementationClass); 1210 } 1211 }; 1212 1213 ObjectCreator<T> justInTime = new ObjectCreator<T>() 1214 { 1215 private T delegate; 1216 1217 @Override 1218 public synchronized T createObject() 1219 { 1220 if (delegate == null) 1221 delegate = autobuildCreator.createObject(); 1222 1223 return delegate; 1224 } 1225 }; 1226 1227 return proxyFactory.createProxy(interfaceClass, justInTime, 1228 String.format("<Autobuild proxy %s(%s)>", implementationClass.getName(), interfaceClass.getName())); 1229 } 1230 1231 private <T> T createReloadingProxy(Class<T> interfaceClass, final Class<? extends T> implementationClass, 1232 ObjectLocator locator) 1233 { 1234 ReloadableObjectCreator creator = new ReloadableObjectCreator(proxyFactory, implementationClass.getClassLoader(), 1235 implementationClass.getName(), loggerSource.getLogger(implementationClass), this, locator); 1236 1237 getService(UpdateListenerHub.class).addUpdateListener(creator); 1238 1239 return proxyFactory.createProxy(interfaceClass, implementationClass, (ObjectCreator<T>) creator, 1240 String.format("<Autoreload proxy %s(%s)>", implementationClass.getName(), interfaceClass.getName())); 1241 } 1242 1243 @Override 1244 public Object provideServiceProxy(String serviceId) 1245 { 1246 return getService(serviceId, Object.class); 1247 } 1248 1249 @Override 1250 public void run(String description, Runnable operation) 1251 { 1252 operationTracker.run(description, operation); 1253 } 1254 1255 @Override 1256 public <T> T invoke(String description, Invokable<T> operation) 1257 { 1258 return operationTracker.invoke(description, operation); 1259 } 1260 1261 @Override 1262 public <T> T perform(String description, IOOperation<T> operation) throws IOException 1263 { 1264 return operationTracker.perform(description, operation); 1265 } 1266 1267 @Override 1268 public Set<Class> getMarkerAnnotations() 1269 { 1270 return markerToServiceDef.keySet(); 1271 } 1272 1273 final private static class ModuleComparator implements Comparator<Module> { 1274 @Override 1275 public int compare(Module m1, Module m2) 1276 { 1277 return m1.getLoggerName().compareTo(m2.getLoggerName()); 1278 } 1279 } 1280 1281 final static private class DelegatingServiceConfigurationListener implements ServiceConfigurationListener { 1282 1283 final private Logger logger; 1284 1285 private List<ServiceConfigurationListener> delegates; 1286 private Map<ServiceDef, Map> mapped = CollectionFactory.newMap(); 1287 private Map<ServiceDef, Collection> unordered = CollectionFactory.newMap(); 1288 private Map<ServiceDef, List> ordered = CollectionFactory.newMap(); 1289 1290 public DelegatingServiceConfigurationListener(Logger logger) 1291 { 1292 this.logger = logger; 1293 } 1294 1295 public void setDelegates(List<ServiceConfigurationListener> delegates) 1296 { 1297 1298 this.delegates = delegates; 1299 1300 for (ServiceDef serviceDef : mapped.keySet()) 1301 { 1302 for (ServiceConfigurationListener delegate : delegates) 1303 { 1304 delegate.onMappedConfiguration(serviceDef, Collections.unmodifiableMap(mapped.get(serviceDef))); 1305 } 1306 } 1307 1308 for (ServiceDef serviceDef : unordered.keySet()) 1309 { 1310 for (ServiceConfigurationListener delegate : delegates) 1311 { 1312 delegate.onUnorderedConfiguration(serviceDef, Collections.unmodifiableCollection(unordered.get(serviceDef))); 1313 } 1314 } 1315 1316 for (ServiceDef serviceDef : ordered.keySet()) 1317 { 1318 for (ServiceConfigurationListener delegate : delegates) 1319 { 1320 delegate.onOrderedConfiguration(serviceDef, Collections.unmodifiableList(ordered.get(serviceDef))); 1321 } 1322 } 1323 1324 mapped.clear(); 1325 mapped = null; 1326 unordered.clear(); 1327 unordered = null; 1328 ordered.clear(); 1329 ordered = null; 1330 1331 } 1332 1333 @Override 1334 public void onOrderedConfiguration(ServiceDef serviceDef, List configuration) 1335 { 1336 log("ordered", serviceDef, configuration); 1337 if (delegates == null) 1338 { 1339 ordered.put(serviceDef, configuration); 1340 } 1341 else 1342 { 1343 for (ServiceConfigurationListener delegate : delegates) 1344 { 1345 delegate.onOrderedConfiguration(serviceDef, Collections.unmodifiableList(configuration)); 1346 } 1347 } 1348 } 1349 1350 @Override 1351 public void onUnorderedConfiguration(ServiceDef serviceDef, Collection configuration) 1352 { 1353 log("unordered", serviceDef, configuration); 1354 if (delegates == null) 1355 { 1356 unordered.put(serviceDef, configuration); 1357 } 1358 else 1359 { 1360 for (ServiceConfigurationListener delegate : delegates) 1361 { 1362 delegate.onUnorderedConfiguration(serviceDef, Collections.unmodifiableCollection(configuration)); 1363 } 1364 } 1365 } 1366 1367 @Override 1368 public void onMappedConfiguration(ServiceDef serviceDef, Map configuration) 1369 { 1370 log("mapped", serviceDef, configuration); 1371 if (delegates == null) 1372 { 1373 mapped.put(serviceDef, configuration); 1374 } 1375 else 1376 { 1377 for (ServiceConfigurationListener delegate : delegates) 1378 { 1379 delegate.onMappedConfiguration(serviceDef, Collections.unmodifiableMap(configuration)); 1380 } 1381 } 1382 1383 } 1384 1385 private void log(String type, ServiceDef serviceDef, Object configuration) 1386 { 1387 if (logger.isDebugEnabled()) 1388 { 1389 logger.debug(String.format("Service %s %s configuration: %s", 1390 serviceDef.getServiceId(), type, configuration.toString())); 1391 } 1392 } 1393 1394 } 1395 1396}