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