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 /** 114 * Constructs the registry from a set of module definitions and other resources. 115 * 116 * @param moduleDefs 117 * defines the modules (and builders, decorators, etc., within) 118 * @param proxyFactory 119 * used to create new proxy objects 120 * @param loggerSource 121 * used to obtain Logger instances 122 * @param operationTracker 123 */ 124 public RegistryImpl(Collection<ModuleDef2> moduleDefs, PlasticProxyFactory proxyFactory, 125 LoggerSource loggerSource, OperationTracker operationTracker) 126 { 127 assert moduleDefs != null; 128 assert proxyFactory != null; 129 assert loggerSource != null; 130 assert operationTracker != null; 131 132 this.loggerSource = loggerSource; 133 this.operationTracker = operationTracker; 134 135 this.proxyFactory = proxyFactory; 136 137 Logger logger = loggerForBuiltinService(PERTHREAD_MANAGER_SERVICE_ID); 138 139 PerthreadManagerImpl ptmImpl = new PerthreadManagerImpl(logger); 140 141 perthreadManager = ptmImpl; 142 143 final ServiceActivityTrackerImpl scoreboardAndTracker = new ServiceActivityTrackerImpl(perthreadManager); 144 145 tracker = scoreboardAndTracker; 146 147 logger = loggerForBuiltinService(REGISTRY_SHUTDOWN_HUB_SERVICE_ID); 148 149 registryShutdownHub = new RegistryShutdownHubImpl(logger); 150 ptmImpl.registerForShutdown(registryShutdownHub); 151 152 lifecycles.put("singleton", new SingletonServiceLifecycle()); 153 154 registryShutdownHub.addRegistryShutdownListener(new Runnable() 155 { 156 public void run() 157 { 158 scoreboardAndTracker.shutdown(); 159 } 160 }); 161 162 for (ModuleDef2 def : moduleDefs) 163 { 164 logger = this.loggerSource.getLogger(def.getLoggerName()); 165 166 Module module = new ModuleImpl(this, tracker, def, proxyFactory, logger); 167 168 Set<ServiceDef2> moduleServiceDefs = CollectionFactory.newSet(); 169 170 for (String serviceId : def.getServiceIds()) 171 { 172 ServiceDef2 serviceDef = module.getServiceDef(serviceId); 173 174 moduleServiceDefs.add(serviceDef); 175 allServiceDefs.add(serviceDef); 176 177 Module existing = serviceIdToModule.get(serviceId); 178 179 if (existing != null) 180 throw new RuntimeException(IOCMessages.serviceIdConflict(serviceId, 181 existing.getServiceDef(serviceId), serviceDef)); 182 183 serviceIdToModule.put(serviceId, module); 184 185 // The service is defined but will not have gone further than that. 186 tracker.define(serviceDef, Status.DEFINED); 187 188 for (Class marker : serviceDef.getMarkers()) 189 InternalUtils.addToMapList(markerToServiceDef, marker, serviceDef); 190 } 191 192 moduleToServiceDefs.put(module, moduleServiceDefs); 193 194 addStartupsInModule(def, module, logger); 195 } 196 197 addBuiltin(SERVICE_ACTIVITY_SCOREBOARD_SERVICE_ID, ServiceActivityScoreboard.class, scoreboardAndTracker); 198 addBuiltin(LOGGER_SOURCE_SERVICE_ID, LoggerSource.class, this.loggerSource); 199 addBuiltin(PERTHREAD_MANAGER_SERVICE_ID, PerthreadManager.class, perthreadManager); 200 addBuiltin(REGISTRY_SHUTDOWN_HUB_SERVICE_ID, RegistryShutdownHub.class, registryShutdownHub); 201 addBuiltin(PLASTIC_PROXY_FACTORY_SERVICE_ID, PlasticProxyFactory.class, proxyFactory); 202 203 validateContributeDefs(moduleDefs); 204 205 scoreboardAndTracker.startup(); 206 207 SerializationSupport.setProvider(this); 208 } 209 210 private void addStartupsInModule(ModuleDef2 def, final Module module, final Logger logger) 211 { 212 for (final StartupDef startup : def.getStartups()) 213 { 214 215 startups.add(new Runnable() 216 { 217 public void run() 218 { 219 startup.invoke(module, RegistryImpl.this, RegistryImpl.this, logger); 220 } 221 }); 222 } 223 } 224 225 /** 226 * Validate that each module's ContributeDefs correspond to an actual service. 227 */ 228 private void validateContributeDefs(Collection<ModuleDef2> moduleDefs) 229 { 230 for (ModuleDef2 module : moduleDefs) 231 { 232 Set<ContributionDef> contributionDefs = module.getContributionDefs(); 233 234 for (ContributionDef cd : contributionDefs) 235 { 236 String serviceId = cd.getServiceId(); 237 238 ContributionDef3 cd3 = InternalUtils.toContributionDef3(cd); 239 240 // Ignore any optional contribution methods; there's no way to validate that 241 // they contribute to a known service ... that's the point of @Optional 242 243 if (cd3.isOptional()) 244 { 245 continue; 246 } 247 248 // Otherwise, check that the service being contributed to exists ... 249 250 if (cd3.getServiceId() != null) 251 { 252 if (!serviceIdToModule.containsKey(serviceId)) 253 { 254 throw new IllegalArgumentException( 255 IOCMessages.contributionForNonexistentService(cd)); 256 } 257 } else if (!isContributionForExistentService(module, cd3)) 258 { 259 throw new IllegalArgumentException( 260 IOCMessages.contributionForUnqualifiedService(cd3)); 261 } 262 } 263 } 264 265 } 266 267 /** 268 * Invoked when the contribution method didn't follow the naming convention and so doesn't identify 269 * a service by id; instead there was an @Contribute to identify the service interface. 270 */ 271 @SuppressWarnings("all") 272 private boolean isContributionForExistentService(ModuleDef moduleDef, final ContributionDef2 cd) 273 { 274 final Set<Class> contributionMarkers = new HashSet(cd.getMarkers()); 275 276 boolean localOnly = contributionMarkers.contains(Local.class); 277 278 Flow<ServiceDef2> serviceDefs = localOnly ? getLocalServiceDefs(moduleDef) : F.flow(allServiceDefs); 279 280 contributionMarkers.retainAll(getMarkerAnnotations()); 281 contributionMarkers.remove(Local.class); 282 283 // Match services with the correct interface AND having as markers *all* the marker annotations 284 285 Flow<ServiceDef2> filtered = serviceDefs.filter(F.and(new Predicate<ServiceDef2>() 286 { 287 public boolean accept(ServiceDef2 object) 288 { 289 return object.getServiceInterface().equals(cd.getServiceInterface()); 290 } 291 }, new Predicate<ServiceDef2>() 292 { 293 public boolean accept(ServiceDef2 serviceDef) 294 { 295 return serviceDef.getMarkers().containsAll(contributionMarkers); 296 } 297 } 298 )); 299 300 // That's a lot of logic; the good news is it will short-circuit as soon as it finds a single match, 301 // thanks to the laziness inside Flow. 302 303 return !filtered.isEmpty(); 304 } 305 306 private Flow<ServiceDef2> getLocalServiceDefs(final ModuleDef moduleDef) 307 { 308 return F.flow(moduleDef.getServiceIds()).map(new Mapper<String, ServiceDef2>() 309 { 310 public ServiceDef2 map(String value) 311 { 312 return InternalUtils.toServiceDef2(moduleDef.getServiceDef(value)); 313 } 314 }); 315 } 316 317 /** 318 * It's not unreasonable for an eagerly-loaded service to decide to start a thread, at which 319 * point we raise issues 320 * about improper publishing of the Registry instance from the RegistryImpl constructor. Moving 321 * eager loading of 322 * services out to its own method should ensure thread safety. 323 */ 324 public void performRegistryStartup() 325 { 326 eagerLoadLock.lock(); 327 328 List<EagerLoadServiceProxy> proxies = CollectionFactory.newList(); 329 330 for (Module m : moduleToServiceDefs.keySet()) 331 m.collectEagerLoadServices(proxies); 332 333 // TAPESTRY-2267: Gather up all the proxies before instantiating any of them. 334 335 for (EagerLoadServiceProxy proxy : proxies) 336 { 337 proxy.eagerLoadService(); 338 } 339 340 for (Runnable startup : startups) { 341 startup.run(); 342 } 343 344 startups.clear(); 345 346 getService("RegistryStartup", Runnable.class).run(); 347 348 cleanupThread(); 349 } 350 351 public Logger getServiceLogger(String serviceId) 352 { 353 Module module = serviceIdToModule.get(serviceId); 354 355 assert module != null; 356 357 return loggerSource.getLogger(module.getLoggerName() + "." + serviceId); 358 } 359 360 private Logger loggerForBuiltinService(String serviceId) 361 { 362 return loggerSource.getLogger(TapestryIOCModule.class + "." + serviceId); 363 } 364 365 private <T> void addBuiltin(final String serviceId, final Class<T> serviceInterface, T service) 366 { 367 builtinTypes.put(serviceId, serviceInterface); 368 builtinServices.put(serviceId, service); 369 370 // Make sure each of the builtin services is also available via the Builtin annotation 371 // marker. 372 373 ServiceDef2 serviceDef = new ServiceDef2() 374 { 375 public ObjectCreator createServiceCreator(ServiceBuilderResources resources) 376 { 377 return null; 378 } 379 380 public Set<Class> getMarkers() 381 { 382 return BUILTIN; 383 } 384 385 public String getServiceId() 386 { 387 return serviceId; 388 } 389 390 public Class getServiceInterface() 391 { 392 return serviceInterface; 393 } 394 395 public String getServiceScope() 396 { 397 return ScopeConstants.DEFAULT; 398 } 399 400 public boolean isEagerLoad() 401 { 402 return false; 403 } 404 405 public boolean isPreventDecoration() 406 { 407 return true; 408 } 409 }; 410 411 for (Class marker : serviceDef.getMarkers()) 412 { 413 InternalUtils.addToMapList(markerToServiceDef, marker, serviceDef); 414 allServiceDefs.add(serviceDef); 415 } 416 417 tracker.define(serviceDef, Status.BUILTIN); 418 } 419 420 public synchronized void shutdown() 421 { 422 lock.lock(); 423 424 registryShutdownHub.fireRegistryDidShutdown(); 425 426 SerializationSupport.clearProvider(this); 427 } 428 429 public <T> T getService(String serviceId, Class<T> serviceInterface) 430 { 431 lock.check(); 432 433 T result = checkForBuiltinService(serviceId, serviceInterface); 434 if (result != null) 435 return result; 436 437 // Checking serviceId and serviceInterface is overkill; they have been checked and rechecked 438 // all the way to here. 439 440 Module containingModule = locateModuleForService(serviceId); 441 442 return containingModule.getService(serviceId, serviceInterface); 443 } 444 445 private <T> T checkForBuiltinService(String serviceId, Class<T> serviceInterface) 446 { 447 Object service = builtinServices.get(serviceId); 448 449 if (service == null) 450 return null; 451 452 try 453 { 454 return serviceInterface.cast(service); 455 } catch (ClassCastException ex) 456 { 457 throw new RuntimeException(IOCMessages.serviceWrongInterface(serviceId, builtinTypes.get(serviceId), 458 serviceInterface)); 459 } 460 } 461 462 public void cleanupThread() 463 { 464 lock.check(); 465 466 perthreadManager.cleanup(); 467 } 468 469 private Module locateModuleForService(String serviceId) 470 { 471 Module module = serviceIdToModule.get(serviceId); 472 473 if (module == null) 474 throw new UnknownValueException(String.format("Service id '%s' is not defined by any module.", serviceId), 475 new AvailableValues("Defined service ids", serviceIdToModule)); 476 477 return module; 478 } 479 480 public <T> Collection<T> getUnorderedConfiguration(ServiceDef3 serviceDef, Class<T> objectType) 481 { 482 lock.check(); 483 484 final Collection<T> result = CollectionFactory.newList(); 485 486 for (Module m : moduleToServiceDefs.keySet()) 487 addToUnorderedConfiguration(result, objectType, serviceDef, m); 488 489 return result; 490 } 491 492 @SuppressWarnings("unchecked") 493 public <T> List<T> getOrderedConfiguration(ServiceDef3 serviceDef, Class<T> objectType) 494 { 495 lock.check(); 496 497 String serviceId = serviceDef.getServiceId(); 498 Logger logger = getServiceLogger(serviceId); 499 500 Orderer<T> orderer = new Orderer<T>(logger); 501 Map<String, OrderedConfigurationOverride<T>> overrides = CollectionFactory.newCaseInsensitiveMap(); 502 503 for (Module m : moduleToServiceDefs.keySet()) 504 addToOrderedConfiguration(orderer, overrides, objectType, serviceDef, m); 505 506 // An ugly hack ... perhaps we should introduce a new builtin service so that this can be 507 // accomplished in the normal way? 508 509 if (serviceId.equals("MasterObjectProvider")) 510 { 511 ObjectProvider contribution = new ObjectProvider() 512 { 513 public <T> T provide(Class<T> objectType, AnnotationProvider annotationProvider, ObjectLocator locator) 514 { 515 return findServiceByMarkerAndType(objectType, annotationProvider, null); 516 } 517 }; 518 519 orderer.add("ServiceByMarker", (T) contribution); 520 } 521 522 for (OrderedConfigurationOverride<T> override : overrides.values()) 523 override.apply(); 524 525 return orderer.getOrdered(); 526 } 527 528 public <K, V> Map<K, V> getMappedConfiguration(ServiceDef3 serviceDef, Class<K> keyType, Class<V> objectType) 529 { 530 lock.check(); 531 532 // When the key type is String, then a case insensitive map is used. 533 534 Map<K, V> result = newConfigurationMap(keyType); 535 Map<K, ContributionDef> keyToContribution = newConfigurationMap(keyType); 536 Map<K, MappedConfigurationOverride<K, V>> overrides = newConfigurationMap(keyType); 537 538 for (Module m : moduleToServiceDefs.keySet()) 539 addToMappedConfiguration(result, overrides, keyToContribution, keyType, objectType, serviceDef, m); 540 541 for (MappedConfigurationOverride<K, V> override : overrides.values()) 542 { 543 override.apply(); 544 } 545 546 return result; 547 } 548 549 @SuppressWarnings("unchecked") 550 private <K, V> Map<K, V> newConfigurationMap(Class<K> keyType) 551 { 552 if (keyType.equals(String.class)) 553 { 554 Map<String, K> result = CollectionFactory.newCaseInsensitiveMap(); 555 556 return (Map<K, V>) result; 557 } 558 559 return CollectionFactory.newMap(); 560 } 561 562 private <K, V> void addToMappedConfiguration(Map<K, V> map, Map<K, MappedConfigurationOverride<K, V>> overrides, 563 Map<K, ContributionDef> keyToContribution, Class<K> keyClass, Class<V> valueType, ServiceDef3 serviceDef, 564 final Module module) 565 { 566 String serviceId = serviceDef.getServiceId(); 567 Set<ContributionDef2> contributions = module.getContributorDefsForService(serviceDef); 568 569 if (contributions.isEmpty()) 570 return; 571 572 Logger logger = getServiceLogger(serviceId); 573 574 final ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger); 575 576 for (final ContributionDef def : contributions) 577 { 578 final MappedConfiguration<K, V> validating = new ValidatingMappedConfigurationWrapper<K, V>(valueType, 579 resources, typeCoercerProxy, map, overrides, serviceId, def, keyClass, keyToContribution); 580 581 String description = "Invoking " + def; 582 583 logger.debug(description); 584 585 operationTracker.run(description, new Runnable() 586 { 587 public void run() 588 { 589 def.contribute(module, resources, validating); 590 } 591 }); 592 } 593 } 594 595 private <T> void addToUnorderedConfiguration(Collection<T> collection, Class<T> valueType, ServiceDef3 serviceDef, 596 final Module module) 597 { 598 String serviceId = serviceDef.getServiceId(); 599 Set<ContributionDef2> contributions = module.getContributorDefsForService(serviceDef); 600 601 if (contributions.isEmpty()) 602 return; 603 604 Logger logger = getServiceLogger(serviceId); 605 606 final ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger); 607 608 for (final ContributionDef def : contributions) 609 { 610 final Configuration<T> validating = new ValidatingConfigurationWrapper<T>(valueType, resources, 611 typeCoercerProxy, collection, serviceId); 612 613 String description = "Invoking " + def; 614 615 logger.debug(description); 616 617 operationTracker.run(description, new Runnable() 618 { 619 public void run() 620 { 621 def.contribute(module, resources, validating); 622 } 623 }); 624 } 625 } 626 627 private <T> void addToOrderedConfiguration(Orderer<T> orderer, 628 Map<String, OrderedConfigurationOverride<T>> overrides, Class<T> valueType, ServiceDef3 serviceDef, 629 final Module module) 630 { 631 String serviceId = serviceDef.getServiceId(); 632 Set<ContributionDef2> contributions = module.getContributorDefsForService(serviceDef); 633 634 if (contributions.isEmpty()) 635 return; 636 637 Logger logger = getServiceLogger(serviceId); 638 639 final ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger); 640 641 for (final ContributionDef def : contributions) 642 { 643 final OrderedConfiguration<T> validating = new ValidatingOrderedConfigurationWrapper<T>(valueType, 644 resources, typeCoercerProxy, orderer, overrides, def); 645 646 String description = "Invoking " + def; 647 648 logger.debug(description); 649 650 operationTracker.run(description, new Runnable() 651 { 652 public void run() 653 { 654 def.contribute(module, resources, validating); 655 } 656 }); 657 } 658 } 659 660 public <T> T getService(Class<T> serviceInterface) 661 { 662 lock.check(); 663 664 return getServiceByTypeAndMarkers(serviceInterface); 665 } 666 667 public <T> T getService(Class<T> serviceInterface, Class<? extends Annotation>... markerTypes) 668 { 669 lock.check(); 670 671 return getServiceByTypeAndMarkers(serviceInterface, markerTypes); 672 } 673 674 private <T> T getServiceByTypeAlone(Class<T> serviceInterface) 675 { 676 List<String> serviceIds = findServiceIdsForInterface(serviceInterface); 677 678 if (serviceIds == null) 679 serviceIds = Collections.emptyList(); 680 681 switch (serviceIds.size()) 682 { 683 case 0: 684 685 throw new RuntimeException(IOCMessages.noServiceMatchesType(serviceInterface)); 686 687 case 1: 688 689 String serviceId = serviceIds.get(0); 690 691 return getService(serviceId, serviceInterface); 692 693 default: 694 695 Collections.sort(serviceIds); 696 697 throw new RuntimeException(IOCMessages.manyServiceMatches(serviceInterface, serviceIds)); 698 } 699 } 700 701 private <T> T getServiceByTypeAndMarkers(Class<T> serviceInterface, Class<? extends Annotation>... markerTypes) 702 { 703 if (markerTypes.length == 0) 704 { 705 return getServiceByTypeAlone(serviceInterface); 706 } 707 708 AnnotationProvider provider = createAnnotationProvider(markerTypes); 709 710 Set<ServiceDef2> matches = CollectionFactory.newSet(); 711 List<Class> markers = CollectionFactory.newList(); 712 713 findServiceDefsMatchingMarkerAndType(serviceInterface, provider, null, markers, matches); 714 715 return extractServiceFromMatches(serviceInterface, markers, matches); 716 } 717 718 private AnnotationProvider createAnnotationProvider(Class<? extends Annotation>... markerTypes) 719 { 720 final Map<Class<? extends Annotation>, Annotation> map = CollectionFactory.newMap(); 721 722 for (Class<? extends Annotation> markerType : markerTypes) 723 { 724 map.put(markerType, createAnnotationProxy(markerType)); 725 } 726 727 return new AnnotationProvider() 728 { 729 public <T extends Annotation> T getAnnotation(Class<T> annotationClass) 730 { 731 return annotationClass.cast(map.get(annotationClass)); 732 } 733 }; 734 } 735 736 private <A extends Annotation> Annotation createAnnotationProxy(final Class<A> annotationType) 737 { 738 Annotation result = cachedAnnotationProxies.get(annotationType); 739 740 if (result == null) 741 { 742 // We create a JDK proxy because its pretty quick and easy. 743 744 InvocationHandler handler = new InvocationHandler() 745 { 746 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 747 { 748 if (method.getName().equals("annotationType")) 749 { 750 return annotationType; 751 } 752 753 return method.invoke(proxy, args); 754 } 755 }; 756 757 result = (Annotation) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), 758 new Class[]{annotationType}, 759 handler); 760 761 cachedAnnotationProxies.put(annotationType, result); 762 } 763 764 return result; 765 } 766 767 private List<String> findServiceIdsForInterface(Class serviceInterface) 768 { 769 List<String> result = CollectionFactory.newList(); 770 771 for (Module module : moduleToServiceDefs.keySet()) 772 result.addAll(module.findServiceIdsForInterface(serviceInterface)); 773 774 for (Map.Entry<String, Object> entry : builtinServices.entrySet()) 775 { 776 if (serviceInterface.isInstance(entry.getValue())) 777 result.add(entry.getKey()); 778 } 779 780 Collections.sort(result); 781 782 return result; 783 } 784 785 public ServiceLifecycle2 getServiceLifecycle(String scope) 786 { 787 lock.check(); 788 789 ServiceLifecycle result = lifecycles.get(scope); 790 791 if (result == null) 792 { 793 ServiceLifecycleSource source = getService("ServiceLifecycleSource", ServiceLifecycleSource.class); 794 795 result = source.get(scope); 796 } 797 798 if (result == null) 799 throw new RuntimeException(IOCMessages.unknownScope(scope)); 800 801 return InternalUtils.toServiceLifecycle2(result); 802 } 803 804 public List<ServiceDecorator> findDecoratorsForService(ServiceDef3 serviceDef) 805 { 806 lock.check(); 807 808 assert serviceDef != null; 809 810 Logger logger = getServiceLogger(serviceDef.getServiceId()); 811 812 Orderer<ServiceDecorator> orderer = new Orderer<ServiceDecorator>(logger); 813 814 for (Module module : moduleToServiceDefs.keySet()) 815 { 816 Set<DecoratorDef> decoratorDefs = module.findMatchingDecoratorDefs(serviceDef); 817 818 if (decoratorDefs.isEmpty()) 819 continue; 820 821 ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger); 822 823 for (DecoratorDef decoratorDef : decoratorDefs) 824 { 825 ServiceDecorator decorator = decoratorDef.createDecorator(module, resources); 826 827 orderer.add(decoratorDef.getDecoratorId(), decorator, decoratorDef.getConstraints()); 828 } 829 } 830 831 return orderer.getOrdered(); 832 } 833 834 public List<ServiceAdvisor> findAdvisorsForService(ServiceDef3 serviceDef) 835 { 836 lock.check(); 837 838 assert serviceDef != null; 839 840 Logger logger = getServiceLogger(serviceDef.getServiceId()); 841 842 Orderer<ServiceAdvisor> orderer = new Orderer<ServiceAdvisor>(logger); 843 844 for (Module module : moduleToServiceDefs.keySet()) 845 { 846 Set<AdvisorDef> advisorDefs = module.findMatchingServiceAdvisors(serviceDef); 847 848 if (advisorDefs.isEmpty()) 849 continue; 850 851 ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger); 852 853 for (AdvisorDef advisorDef : advisorDefs) 854 { 855 ServiceAdvisor advisor = advisorDef.createAdvisor(module, resources); 856 857 orderer.add(advisorDef.getAdvisorId(), advisor, advisorDef.getConstraints()); 858 } 859 } 860 861 return orderer.getOrdered(); 862 } 863 864 public <T> T getObject(Class<T> objectType, AnnotationProvider annotationProvider, ObjectLocator locator, 865 Module localModule) 866 { 867 lock.check(); 868 869 AnnotationProvider effectiveProvider = annotationProvider != null ? annotationProvider 870 : new NullAnnotationProvider(); 871 872 // We do a check here for known marker/type combinations, so that you can use a marker 873 // annotation 874 // to inject into a contribution method that contributes to MasterObjectProvider. 875 // We also force a contribution into MasterObjectProvider to accomplish the same thing. 876 877 T result = findServiceByMarkerAndType(objectType, annotationProvider, localModule); 878 879 if (result != null) 880 return result; 881 882 MasterObjectProvider masterProvider = getService(IOCConstants.MASTER_OBJECT_PROVIDER_SERVICE_ID, 883 MasterObjectProvider.class); 884 885 return masterProvider.provide(objectType, effectiveProvider, locator, true); 886 } 887 888 private Collection<ServiceDef2> filterByType(Class<?> objectType, Collection<ServiceDef2> serviceDefs) 889 { 890 Collection<ServiceDef2> result = CollectionFactory.newSet(); 891 892 for (ServiceDef2 sd : serviceDefs) 893 { 894 if (objectType.isAssignableFrom(sd.getServiceInterface())) 895 { 896 result.add(sd); 897 } 898 } 899 900 return result; 901 } 902 903 @SuppressWarnings("unchecked") 904 private <T> T findServiceByMarkerAndType(Class<T> objectType, AnnotationProvider provider, Module localModule) 905 { 906 if (provider == null) 907 return null; 908 909 Set<ServiceDef2> matches = CollectionFactory.newSet(); 910 List<Class> markers = CollectionFactory.newList(); 911 912 findServiceDefsMatchingMarkerAndType(objectType, provider, localModule, markers, matches); 913 914 915 // If didn't see @Local or any recognized marker annotation, then don't try to filter that 916 // way. Continue on, eventually to the MasterObjectProvider service. 917 918 if (markers.isEmpty()) 919 { 920 return null; 921 } 922 923 return extractServiceFromMatches(objectType, markers, matches); 924 } 925 926 /** 927 * Given markers and matches processed by {@link #findServiceDefsMatchingMarkerAndType(Class, org.apache.tapestry5.ioc.AnnotationProvider, Module, java.util.List, java.util.Set)}, this 928 * finds the singular match, or reports an error for 0 or 2+ matches. 929 */ 930 private <T> T extractServiceFromMatches(Class<T> objectType, List<Class> markers, Set<ServiceDef2> matches) 931 { 932 switch (matches.size()) 933 { 934 935 case 1: 936 937 ServiceDef def = matches.iterator().next(); 938 939 return getService(def.getServiceId(), objectType); 940 941 case 0: 942 943 // It's no accident that the user put the marker annotation at the injection 944 // point, since it matches a known marker annotation, it better be there for 945 // a reason. So if we don't get a match, we have to assume the user expected 946 // one, and that is an error. 947 948 // This doesn't help when the user places an annotation they *think* is a marker 949 // but isn't really a marker (because no service is marked by the annotation). 950 951 throw new RuntimeException(IOCMessages.noServicesMatchMarker(objectType, markers)); 952 953 default: 954 throw new RuntimeException(IOCMessages.manyServicesMatchMarker(objectType, markers, matches)); 955 } 956 } 957 958 private <T> void findServiceDefsMatchingMarkerAndType(Class<T> objectType, AnnotationProvider provider, Module localModule, List<Class> markers, 959 Set<ServiceDef2> matches) 960 { 961 assert provider != null; 962 963 boolean localOnly = localModule != null && provider.getAnnotation(Local.class) != null; 964 965 matches.addAll(filterByType(objectType, localOnly ? moduleToServiceDefs.get(localModule) : allServiceDefs)); 966 967 if (localOnly) 968 { 969 markers.add(Local.class); 970 } 971 972 for (Class marker : markerToServiceDef.keySet()) 973 { 974 if (provider.getAnnotation(marker) == null) 975 { 976 continue; 977 } 978 979 markers.add(marker); 980 981 matches.retainAll(markerToServiceDef.get(marker)); 982 983 if (matches.isEmpty()) 984 { 985 return; 986 } 987 } 988 } 989 990 public <T> T getObject(Class<T> objectType, AnnotationProvider annotationProvider) 991 { 992 return getObject(objectType, annotationProvider, this, null); 993 } 994 995 public void addRegistryShutdownListener(RegistryShutdownListener listener) 996 { 997 lock.check(); 998 999 registryShutdownHub.addRegistryShutdownListener(listener); 1000 } 1001 1002 public void addRegistryShutdownListener(Runnable listener) 1003 { 1004 lock.check(); 1005 1006 registryShutdownHub.addRegistryShutdownListener(listener); 1007 } 1008 1009 public void addRegistryWillShutdownListener(Runnable listener) 1010 { 1011 lock.check(); 1012 1013 registryShutdownHub.addRegistryWillShutdownListener(listener); 1014 } 1015 1016 public String expandSymbols(String input) 1017 { 1018 lock.check(); 1019 1020 // Again, a bit of work to avoid instantiating the SymbolSource until absolutely necessary. 1021 1022 if (!InternalUtils.containsSymbols(input)) 1023 return input; 1024 1025 return getSymbolSource().expandSymbols(input); 1026 } 1027 1028 /** 1029 * Defers obtaining the symbol source until actually needed. 1030 */ 1031 private SymbolSource getSymbolSource() 1032 { 1033 if (symbolSource == null) 1034 symbolSource = getService(SYMBOL_SOURCE_SERVICE_ID, SymbolSource.class); 1035 1036 return symbolSource; 1037 } 1038 1039 public <T> T autobuild(String description, final Class<T> clazz) 1040 { 1041 return invoke(description, new Invokable<T>() 1042 { 1043 public T invoke() 1044 { 1045 return autobuild(clazz); 1046 } 1047 }); 1048 } 1049 1050 public <T> T autobuild(final Class<T> clazz) 1051 { 1052 assert clazz != null; 1053 final Constructor constructor = InternalUtils.findAutobuildConstructor(clazz); 1054 1055 if (constructor == null) 1056 { 1057 throw new RuntimeException(IOCMessages.noAutobuildConstructor(clazz)); 1058 } 1059 1060 Map<Class, Object> resourcesMap = CollectionFactory.newMap(); 1061 resourcesMap.put(OperationTracker.class, RegistryImpl.this); 1062 1063 InjectionResources resources = new MapInjectionResources(resourcesMap); 1064 1065 ObjectCreator<T> plan = InternalUtils.createConstructorConstructionPlan(this, this, resources, null, "Invoking " + proxyFactory.getConstructorLocation(constructor).toString(), constructor); 1066 1067 return plan.createObject(); 1068 } 1069 1070 public <T> T proxy(Class<T> interfaceClass, Class<? extends T> implementationClass) 1071 { 1072 return proxy(interfaceClass, implementationClass, this); 1073 } 1074 1075 public <T> T proxy(Class<T> interfaceClass, Class<? extends T> implementationClass, ObjectLocator locator) 1076 { 1077 assert interfaceClass != null; 1078 assert implementationClass != null; 1079 1080 if (InternalUtils.SERVICE_CLASS_RELOADING_ENABLED && InternalUtils.isLocalFile(implementationClass)) 1081 return createReloadingProxy(interfaceClass, implementationClass, locator); 1082 1083 return createNonReloadingProxy(interfaceClass, implementationClass, locator); 1084 } 1085 1086 private <T> T createNonReloadingProxy(Class<T> interfaceClass, final Class<? extends T> implementationClass, 1087 final ObjectLocator locator) 1088 { 1089 final ObjectCreator<T> autobuildCreator = new ObjectCreator<T>() 1090 { 1091 public T createObject() 1092 { 1093 return locator.autobuild(implementationClass); 1094 } 1095 }; 1096 1097 ObjectCreator<T> justInTime = new ObjectCreator<T>() 1098 { 1099 private T delegate; 1100 1101 public synchronized T createObject() 1102 { 1103 if (delegate == null) 1104 delegate = autobuildCreator.createObject(); 1105 1106 return delegate; 1107 } 1108 }; 1109 1110 return proxyFactory.createProxy(interfaceClass, justInTime, 1111 String.format("<Autobuild proxy %s(%s)>", implementationClass.getName(), interfaceClass.getName())); 1112 } 1113 1114 private <T> T createReloadingProxy(Class<T> interfaceClass, final Class<? extends T> implementationClass, 1115 ObjectLocator locator) 1116 { 1117 ReloadableObjectCreator creator = new ReloadableObjectCreator(proxyFactory, implementationClass.getClassLoader(), 1118 implementationClass.getName(), loggerSource.getLogger(implementationClass), this, locator); 1119 1120 getService(UpdateListenerHub.class).addUpdateListener(creator); 1121 1122 return proxyFactory.createProxy(interfaceClass, implementationClass, (ObjectCreator<T>) creator, 1123 String.format("<Autoreload proxy %s(%s)>", implementationClass.getName(), interfaceClass.getName())); 1124 } 1125 1126 public Object provideServiceProxy(String serviceId) 1127 { 1128 return getService(serviceId, Object.class); 1129 } 1130 1131 public void run(String description, Runnable operation) 1132 { 1133 operationTracker.run(description, operation); 1134 } 1135 1136 public <T> T invoke(String description, Invokable<T> operation) 1137 { 1138 return operationTracker.invoke(description, operation); 1139 } 1140 1141 public <T> T perform(String description, IOOperation<T> operation) throws IOException 1142 { 1143 return operationTracker.perform(description, operation); 1144 } 1145 1146 public Set<Class> getMarkerAnnotations() 1147 { 1148 return markerToServiceDef.keySet(); 1149 } 1150}