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 for (Module m : moduleToServiceDefs.keySet()) 544 addToUnorderedConfiguration(result, objectType, serviceDef, m); 545 546 if (!isServiceConfigurationListenerServiceDef(serviceDef)) 547 { 548 serviceConfigurationListener.onUnorderedConfiguration(serviceDef, result); 549 } 550 551 return result; 552 } 553 554 @Override 555 @SuppressWarnings("unchecked") 556 public <T> List<T> getOrderedConfiguration(ServiceDef3 serviceDef, Class<T> objectType) 557 { 558 lock.check(); 559 560 String serviceId = serviceDef.getServiceId(); 561 Logger logger = getServiceLogger(serviceId); 562 563 Orderer<T> orderer = new Orderer<T>(logger); 564 Map<String, OrderedConfigurationOverride<T>> overrides = CollectionFactory.newCaseInsensitiveMap(); 565 566 // TAP5-2129. NOTICE: if someday an ordering between modules is added, this should be reverted 567 // or a notice added to the documentation. 568 List<Module> modules = new ArrayList<Module>(moduleToServiceDefs.keySet()); 569 Collections.sort(modules, new ModuleComparator()); 570 571 for (Module m : modules) 572 addToOrderedConfiguration(orderer, overrides, objectType, serviceDef, m); 573 574 // An ugly hack ... perhaps we should introduce a new builtin service so that this can be 575 // accomplished in the normal way? 576 577 if (serviceId.equals("MasterObjectProvider")) 578 { 579 ObjectProvider contribution = new ObjectProvider() 580 { 581 @Override 582 public <T> T provide(Class<T> objectType, AnnotationProvider annotationProvider, ObjectLocator locator) 583 { 584 return findServiceByMarkerAndType(objectType, annotationProvider, null); 585 } 586 }; 587 588 orderer.add("ServiceByMarker", (T) contribution); 589 } 590 591 for (OrderedConfigurationOverride<T> override : overrides.values()) 592 override.apply(); 593 594 final List<T> result = orderer.getOrdered(); 595 596 if (!isServiceConfigurationListenerServiceDef(serviceDef)) 597 { 598 serviceConfigurationListener.onOrderedConfiguration(serviceDef, result); 599 } 600 601 return result; 602 } 603 604 private boolean isServiceConfigurationListenerServiceDef(ServiceDef serviceDef) 605 { 606 return serviceDef.getServiceId().equalsIgnoreCase(ServiceConfigurationListener.class.getSimpleName()); 607 } 608 609 @Override 610 public <K, V> Map<K, V> getMappedConfiguration(ServiceDef3 serviceDef, Class<K> keyType, Class<V> objectType) 611 { 612 lock.check(); 613 614 // When the key type is String, then a case insensitive map is used. 615 616 Map<K, V> result = newConfigurationMap(keyType); 617 Map<K, ContributionDef> keyToContribution = newConfigurationMap(keyType); 618 Map<K, MappedConfigurationOverride<K, V>> overrides = newConfigurationMap(keyType); 619 620 for (Module m : moduleToServiceDefs.keySet()) 621 addToMappedConfiguration(result, overrides, keyToContribution, keyType, objectType, serviceDef, m); 622 623 for (MappedConfigurationOverride<K, V> override : overrides.values()) 624 { 625 override.apply(); 626 } 627 628 if (!isServiceConfigurationListenerServiceDef(serviceDef)) 629 { 630 serviceConfigurationListener.onMappedConfiguration(serviceDef, result); 631 } 632 633 return result; 634 } 635 636 @SuppressWarnings("unchecked") 637 private <K, V> Map<K, V> newConfigurationMap(Class<K> keyType) 638 { 639 if (keyType.equals(String.class)) 640 { 641 Map<String, K> result = CollectionFactory.newCaseInsensitiveMap(); 642 643 return (Map<K, V>) result; 644 } 645 646 return CollectionFactory.newMap(); 647 } 648 649 private <K, V> void addToMappedConfiguration(Map<K, V> map, Map<K, MappedConfigurationOverride<K, V>> overrides, 650 Map<K, ContributionDef> keyToContribution, Class<K> keyClass, Class<V> valueType, ServiceDef3 serviceDef, 651 final Module module) 652 { 653 String serviceId = serviceDef.getServiceId(); 654 Set<ContributionDef2> contributions = module.getContributorDefsForService(serviceDef); 655 656 if (contributions.isEmpty()) 657 return; 658 659 Logger logger = getServiceLogger(serviceId); 660 661 final ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger); 662 663 for (final ContributionDef def : contributions) 664 { 665 final MappedConfiguration<K, V> validating = new ValidatingMappedConfigurationWrapper<K, V>(valueType, 666 resources, typeCoercerProxy, map, overrides, serviceId, def, keyClass, keyToContribution); 667 668 String description = "Invoking " + def; 669 670 logger.debug(description); 671 672 operationTracker.run(description, new Runnable() 673 { 674 @Override 675 public void run() 676 { 677 def.contribute(module, resources, validating); 678 } 679 }); 680 } 681 } 682 683 private <T> void addToUnorderedConfiguration(Collection<T> collection, Class<T> valueType, ServiceDef3 serviceDef, 684 final Module module) 685 { 686 String serviceId = serviceDef.getServiceId(); 687 Set<ContributionDef2> contributions = module.getContributorDefsForService(serviceDef); 688 689 if (contributions.isEmpty()) 690 return; 691 692 Logger logger = getServiceLogger(serviceId); 693 694 final ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger); 695 696 for (final ContributionDef def : contributions) 697 { 698 final Configuration<T> validating = new ValidatingConfigurationWrapper<T>(valueType, resources, 699 typeCoercerProxy, collection, serviceId); 700 701 String description = "Invoking " + def; 702 703 logger.debug(description); 704 705 operationTracker.run(description, new Runnable() 706 { 707 @Override 708 public void run() 709 { 710 def.contribute(module, resources, validating); 711 } 712 }); 713 } 714 } 715 716 private <T> void addToOrderedConfiguration(Orderer<T> orderer, 717 Map<String, OrderedConfigurationOverride<T>> overrides, Class<T> valueType, ServiceDef3 serviceDef, 718 final Module module) 719 { 720 String serviceId = serviceDef.getServiceId(); 721 Set<ContributionDef2> contributions = module.getContributorDefsForService(serviceDef); 722 723 if (contributions.isEmpty()) 724 return; 725 726 Logger logger = getServiceLogger(serviceId); 727 728 final ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger); 729 730 for (final ContributionDef def : contributions) 731 { 732 final OrderedConfiguration<T> validating = new ValidatingOrderedConfigurationWrapper<T>(valueType, 733 resources, typeCoercerProxy, orderer, overrides, def); 734 735 String description = "Invoking " + def; 736 737 logger.debug(description); 738 739 operationTracker.run(description, new Runnable() 740 { 741 @Override 742 public void run() 743 { 744 def.contribute(module, resources, validating); 745 } 746 }); 747 } 748 } 749 750 @Override 751 public <T> T getService(Class<T> serviceInterface) 752 { 753 lock.check(); 754 755 return getServiceByTypeAndMarkers(serviceInterface); 756 } 757 758 @Override 759 public <T> T getService(Class<T> serviceInterface, Class<? extends Annotation>... markerTypes) 760 { 761 lock.check(); 762 763 return getServiceByTypeAndMarkers(serviceInterface, markerTypes); 764 } 765 766 private <T> T getServiceByTypeAlone(Class<T> serviceInterface) 767 { 768 List<String> serviceIds = findServiceIdsForInterface(serviceInterface); 769 770 if (serviceIds == null) 771 serviceIds = Collections.emptyList(); 772 773 switch (serviceIds.size()) 774 { 775 case 0: 776 777 throw new RuntimeException(IOCMessages.noServiceMatchesType(serviceInterface)); 778 779 case 1: 780 781 String serviceId = serviceIds.get(0); 782 783 return getService(serviceId, serviceInterface); 784 785 default: 786 787 Collections.sort(serviceIds); 788 789 throw new RuntimeException(IOCMessages.manyServiceMatches(serviceInterface, serviceIds)); 790 } 791 } 792 793 private <T> T getServiceByTypeAndMarkers(Class<T> serviceInterface, Class<? extends Annotation>... markerTypes) 794 { 795 if (markerTypes.length == 0) 796 { 797 return getServiceByTypeAlone(serviceInterface); 798 } 799 800 AnnotationProvider provider = createAnnotationProvider(markerTypes); 801 802 Set<ServiceDef2> matches = CollectionFactory.newSet(); 803 List<Class> markers = CollectionFactory.newList(); 804 805 findServiceDefsMatchingMarkerAndType(serviceInterface, provider, null, markers, matches); 806 807 return extractServiceFromMatches(serviceInterface, markers, matches); 808 } 809 810 private AnnotationProvider createAnnotationProvider(Class<? extends Annotation>... markerTypes) 811 { 812 final Map<Class<? extends Annotation>, Annotation> map = CollectionFactory.newMap(); 813 814 for (Class<? extends Annotation> markerType : markerTypes) 815 { 816 map.put(markerType, createAnnotationProxy(markerType)); 817 } 818 819 return new AnnotationProvider() 820 { 821 @Override 822 public <T extends Annotation> T getAnnotation(Class<T> annotationClass) 823 { 824 return annotationClass.cast(map.get(annotationClass)); 825 } 826 }; 827 } 828 829 private <A extends Annotation> Annotation createAnnotationProxy(final Class<A> annotationType) 830 { 831 Annotation result = cachedAnnotationProxies.get(annotationType); 832 833 if (result == null) 834 { 835 // We create a JDK proxy because its pretty quick and easy. 836 837 InvocationHandler handler = new InvocationHandler() 838 { 839 @Override 840 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 841 { 842 if (method.getName().equals("annotationType")) 843 { 844 return annotationType; 845 } 846 847 return method.invoke(proxy, args); 848 } 849 }; 850 851 result = (Annotation) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), 852 new Class[]{annotationType}, 853 handler); 854 855 cachedAnnotationProxies.put(annotationType, result); 856 } 857 858 return result; 859 } 860 861 private List<String> findServiceIdsForInterface(Class serviceInterface) 862 { 863 List<String> result = CollectionFactory.newList(); 864 865 for (Module module : moduleToServiceDefs.keySet()) 866 result.addAll(module.findServiceIdsForInterface(serviceInterface)); 867 868 for (Map.Entry<String, Object> entry : builtinServices.entrySet()) 869 { 870 if (serviceInterface.isInstance(entry.getValue())) 871 result.add(entry.getKey()); 872 } 873 874 Collections.sort(result); 875 876 return result; 877 } 878 879 @Override 880 public ServiceLifecycle2 getServiceLifecycle(String scope) 881 { 882 lock.check(); 883 884 ServiceLifecycle result = lifecycles.get(scope); 885 886 if (result == null) 887 { 888 ServiceLifecycleSource source = getService("ServiceLifecycleSource", ServiceLifecycleSource.class); 889 890 result = source.get(scope); 891 } 892 893 if (result == null) 894 throw new RuntimeException(IOCMessages.unknownScope(scope)); 895 896 return InternalUtils.toServiceLifecycle2(result); 897 } 898 899 @Override 900 public List<ServiceDecorator> findDecoratorsForService(ServiceDef3 serviceDef) 901 { 902 lock.check(); 903 904 assert serviceDef != null; 905 906 Logger logger = getServiceLogger(serviceDef.getServiceId()); 907 908 Orderer<ServiceDecorator> orderer = new Orderer<ServiceDecorator>(logger, true); 909 910 for (Module module : moduleToServiceDefs.keySet()) 911 { 912 Set<DecoratorDef> decoratorDefs = module.findMatchingDecoratorDefs(serviceDef); 913 914 if (decoratorDefs.isEmpty()) 915 continue; 916 917 ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger); 918 919 for (DecoratorDef decoratorDef : decoratorDefs) 920 { 921 ServiceDecorator decorator = decoratorDef.createDecorator(module, resources); 922 try 923 { 924 orderer.add(decoratorDef.getDecoratorId(), decorator, decoratorDef.getConstraints()); 925 } 926 catch (IllegalArgumentException e) { 927 throw new RuntimeException(String.format( 928 "Service %s has two different decorators methods named decorate%s in different module classes. " 929 + "You can solve this by renaming one of them and annotating it with @Match(\"%2$s\").", 930 serviceDef.getServiceId(), decoratorDef.getDecoratorId())); 931 } 932 } 933 } 934 935 return orderer.getOrdered(); 936 } 937 938 @Override 939 public List<ServiceAdvisor> findAdvisorsForService(ServiceDef3 serviceDef) 940 { 941 lock.check(); 942 943 assert serviceDef != null; 944 945 Logger logger = getServiceLogger(serviceDef.getServiceId()); 946 947 Orderer<ServiceAdvisor> orderer = new Orderer<ServiceAdvisor>(logger); 948 949 for (Module module : moduleToServiceDefs.keySet()) 950 { 951 Set<AdvisorDef> advisorDefs = module.findMatchingServiceAdvisors(serviceDef); 952 953 if (advisorDefs.isEmpty()) 954 continue; 955 956 ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger); 957 958 for (AdvisorDef advisorDef : advisorDefs) 959 { 960 ServiceAdvisor advisor = advisorDef.createAdvisor(module, resources); 961 962 orderer.add(advisorDef.getAdvisorId(), advisor, advisorDef.getConstraints()); 963 } 964 } 965 966 return orderer.getOrdered(); 967 } 968 969 @Override 970 public <T> T getObject(Class<T> objectType, AnnotationProvider annotationProvider, ObjectLocator locator, 971 Module localModule) 972 { 973 lock.check(); 974 975 AnnotationProvider effectiveProvider = annotationProvider != null ? annotationProvider 976 : new NullAnnotationProvider(); 977 978 // We do a check here for known marker/type combinations, so that you can use a marker 979 // annotation 980 // to inject into a contribution method that contributes to MasterObjectProvider. 981 // We also force a contribution into MasterObjectProvider to accomplish the same thing. 982 983 T result = findServiceByMarkerAndType(objectType, annotationProvider, localModule); 984 985 if (result != null) 986 return result; 987 988 MasterObjectProvider masterProvider = getService(IOCConstants.MASTER_OBJECT_PROVIDER_SERVICE_ID, 989 MasterObjectProvider.class); 990 991 return masterProvider.provide(objectType, effectiveProvider, locator, true); 992 } 993 994 private Collection<ServiceDef2> filterByType(Class<?> objectType, Collection<ServiceDef2> serviceDefs) 995 { 996 Collection<ServiceDef2> result = CollectionFactory.newSet(); 997 998 for (ServiceDef2 sd : serviceDefs) 999 { 1000 if (objectType.isAssignableFrom(sd.getServiceInterface())) 1001 { 1002 result.add(sd); 1003 } 1004 } 1005 1006 return result; 1007 } 1008 1009 @SuppressWarnings("unchecked") 1010 private <T> T findServiceByMarkerAndType(Class<T> objectType, AnnotationProvider provider, Module localModule) 1011 { 1012 if (provider == null) 1013 return null; 1014 1015 Set<ServiceDef2> matches = CollectionFactory.newSet(); 1016 List<Class> markers = CollectionFactory.newList(); 1017 1018 findServiceDefsMatchingMarkerAndType(objectType, provider, localModule, markers, matches); 1019 1020 1021 // If didn't see @Local or any recognized marker annotation, then don't try to filter that 1022 // way. Continue on, eventually to the MasterObjectProvider service. 1023 1024 if (markers.isEmpty()) 1025 { 1026 return null; 1027 } 1028 1029 return extractServiceFromMatches(objectType, markers, matches); 1030 } 1031 1032 /** 1033 * Given markers and matches processed by {@link #findServiceDefsMatchingMarkerAndType(Class, org.apache.tapestry5.ioc.AnnotationProvider, Module, java.util.List, java.util.Set)}, this 1034 * finds the singular match, or reports an error for 0 or 2+ matches. 1035 */ 1036 private <T> T extractServiceFromMatches(Class<T> objectType, List<Class> markers, Set<ServiceDef2> matches) 1037 { 1038 switch (matches.size()) 1039 { 1040 1041 case 1: 1042 1043 ServiceDef def = matches.iterator().next(); 1044 1045 return getService(def.getServiceId(), objectType); 1046 1047 case 0: 1048 1049 // It's no accident that the user put the marker annotation at the injection 1050 // point, since it matches a known marker annotation, it better be there for 1051 // a reason. So if we don't get a match, we have to assume the user expected 1052 // one, and that is an error. 1053 1054 // This doesn't help when the user places an annotation they *think* is a marker 1055 // but isn't really a marker (because no service is marked by the annotation). 1056 1057 throw new RuntimeException(IOCMessages.noServicesMatchMarker(objectType, markers)); 1058 1059 default: 1060 throw new RuntimeException(IOCMessages.manyServicesMatchMarker(objectType, markers, matches)); 1061 } 1062 } 1063 1064 private <T> void findServiceDefsMatchingMarkerAndType(Class<T> objectType, AnnotationProvider provider, Module localModule, List<Class> markers, 1065 Set<ServiceDef2> matches) 1066 { 1067 assert provider != null; 1068 1069 boolean localOnly = localModule != null && provider.getAnnotation(Local.class) != null; 1070 1071 matches.addAll(filterByType(objectType, localOnly ? moduleToServiceDefs.get(localModule) : allServiceDefs)); 1072 1073 if (localOnly) 1074 { 1075 markers.add(Local.class); 1076 } 1077 1078 for (Entry<Class, List<ServiceDef2>> entry : markerToServiceDef.entrySet()) 1079 { 1080 Class marker = entry.getKey(); 1081 if (provider.getAnnotation(marker) == null) 1082 { 1083 continue; 1084 } 1085 1086 markers.add(marker); 1087 1088 matches.retainAll(entry.getValue()); 1089 1090 if (matches.isEmpty()) 1091 { 1092 return; 1093 } 1094 } 1095 } 1096 1097 @Override 1098 public <T> T getObject(Class<T> objectType, AnnotationProvider annotationProvider) 1099 { 1100 return getObject(objectType, annotationProvider, this, null); 1101 } 1102 1103 @Override 1104 public void addRegistryShutdownListener(RegistryShutdownListener listener) 1105 { 1106 lock.check(); 1107 1108 registryShutdownHub.addRegistryShutdownListener(listener); 1109 } 1110 1111 @Override 1112 public void addRegistryShutdownListener(Runnable listener) 1113 { 1114 lock.check(); 1115 1116 registryShutdownHub.addRegistryShutdownListener(listener); 1117 } 1118 1119 @Override 1120 public void addRegistryWillShutdownListener(Runnable listener) 1121 { 1122 lock.check(); 1123 1124 registryShutdownHub.addRegistryWillShutdownListener(listener); 1125 } 1126 1127 @Override 1128 public String expandSymbols(String input) 1129 { 1130 lock.check(); 1131 1132 // Again, a bit of work to avoid instantiating the SymbolSource until absolutely necessary. 1133 1134 if (!InternalUtils.containsSymbols(input)) 1135 return input; 1136 1137 return getSymbolSource().expandSymbols(input); 1138 } 1139 1140 /** 1141 * Defers obtaining the symbol source until actually needed. 1142 */ 1143 private SymbolSource getSymbolSource() 1144 { 1145 if (symbolSource == null) 1146 symbolSource = getService(SYMBOL_SOURCE_SERVICE_ID, SymbolSource.class); 1147 1148 return symbolSource; 1149 } 1150 1151 @Override 1152 public <T> T autobuild(String description, final Class<T> clazz) 1153 { 1154 return invoke(description, new Invokable<T>() 1155 { 1156 @Override 1157 public T invoke() 1158 { 1159 return autobuild(clazz); 1160 } 1161 }); 1162 } 1163 1164 @Override 1165 public <T> T autobuild(final Class<T> clazz) 1166 { 1167 assert clazz != null; 1168 final Constructor constructor = InternalUtils.findAutobuildConstructor(clazz); 1169 1170 if (constructor == null) 1171 { 1172 throw new RuntimeException(IOCMessages.noAutobuildConstructor(clazz)); 1173 } 1174 1175 Map<Class, Object> resourcesMap = CollectionFactory.newMap(); 1176 resourcesMap.put(OperationTracker.class, RegistryImpl.this); 1177 1178 InjectionResources resources = new MapInjectionResources(resourcesMap); 1179 1180 ObjectCreator<T> plan = InternalUtils.createConstructorConstructionPlan(this, this, resources, null, "Invoking " + proxyFactory.getConstructorLocation(constructor).toString(), constructor); 1181 1182 return plan.createObject(); 1183 } 1184 1185 @Override 1186 public <T> T proxy(Class<T> interfaceClass, Class<? extends T> implementationClass) 1187 { 1188 return proxy(interfaceClass, implementationClass, this); 1189 } 1190 1191 @Override 1192 public <T> T proxy(Class<T> interfaceClass, Class<? extends T> implementationClass, ObjectLocator locator) 1193 { 1194 assert interfaceClass != null; 1195 assert implementationClass != null; 1196 1197 if (InternalUtils.SERVICE_CLASS_RELOADING_ENABLED && InternalUtils.isLocalFile(implementationClass)) 1198 return createReloadingProxy(interfaceClass, implementationClass, locator); 1199 1200 return createNonReloadingProxy(interfaceClass, implementationClass, locator); 1201 } 1202 1203 private <T> T createNonReloadingProxy(Class<T> interfaceClass, final Class<? extends T> implementationClass, 1204 final ObjectLocator locator) 1205 { 1206 final ObjectCreator<T> autobuildCreator = new ObjectCreator<T>() 1207 { 1208 @Override 1209 public T createObject() 1210 { 1211 return locator.autobuild(implementationClass); 1212 } 1213 }; 1214 1215 ObjectCreator<T> justInTime = new ObjectCreator<T>() 1216 { 1217 private T delegate; 1218 1219 @Override 1220 public synchronized T createObject() 1221 { 1222 if (delegate == null) 1223 delegate = autobuildCreator.createObject(); 1224 1225 return delegate; 1226 } 1227 }; 1228 1229 return proxyFactory.createProxy(interfaceClass, justInTime, 1230 String.format("<Autobuild proxy %s(%s)>", implementationClass.getName(), interfaceClass.getName())); 1231 } 1232 1233 private <T> T createReloadingProxy(Class<T> interfaceClass, final Class<? extends T> implementationClass, 1234 ObjectLocator locator) 1235 { 1236 ReloadableObjectCreator creator = new ReloadableObjectCreator(proxyFactory, implementationClass.getClassLoader(), 1237 implementationClass.getName(), loggerSource.getLogger(implementationClass), this, locator); 1238 1239 getService(UpdateListenerHub.class).addUpdateListener(creator); 1240 1241 return proxyFactory.createProxy(interfaceClass, implementationClass, (ObjectCreator<T>) creator, 1242 String.format("<Autoreload proxy %s(%s)>", implementationClass.getName(), interfaceClass.getName())); 1243 } 1244 1245 @Override 1246 public Object provideServiceProxy(String serviceId) 1247 { 1248 return getService(serviceId, Object.class); 1249 } 1250 1251 @Override 1252 public void run(String description, Runnable operation) 1253 { 1254 operationTracker.run(description, operation); 1255 } 1256 1257 @Override 1258 public <T> T invoke(String description, Invokable<T> operation) 1259 { 1260 return operationTracker.invoke(description, operation); 1261 } 1262 1263 @Override 1264 public <T> T perform(String description, IOOperation<T> operation) throws IOException 1265 { 1266 return operationTracker.perform(description, operation); 1267 } 1268 1269 @Override 1270 public Set<Class> getMarkerAnnotations() 1271 { 1272 return markerToServiceDef.keySet(); 1273 } 1274 1275 final private static class ModuleComparator implements Comparator<Module> { 1276 @Override 1277 public int compare(Module m1, Module m2) 1278 { 1279 return m1.getLoggerName().compareTo(m2.getLoggerName()); 1280 } 1281 } 1282 1283 final static private class DelegatingServiceConfigurationListener implements ServiceConfigurationListener { 1284 1285 final private Logger logger; 1286 1287 private List<ServiceConfigurationListener> delegates; 1288 private Map<ServiceDef, Map> mapped = CollectionFactory.newMap(); 1289 private Map<ServiceDef, Collection> unordered = CollectionFactory.newMap(); 1290 private Map<ServiceDef, List> ordered = CollectionFactory.newMap(); 1291 1292 public DelegatingServiceConfigurationListener(Logger logger) 1293 { 1294 this.logger = logger; 1295 } 1296 1297 public void setDelegates(List<ServiceConfigurationListener> delegates) 1298 { 1299 1300 this.delegates = delegates; 1301 1302 for (Entry<ServiceDef, Map> entry : mapped.entrySet()) 1303 { 1304 for (ServiceConfigurationListener delegate : delegates) 1305 { 1306 delegate.onMappedConfiguration(entry.getKey(), Collections.unmodifiableMap(entry.getValue())); 1307 } 1308 } 1309 1310 for (Entry<ServiceDef, Collection> entry : unordered.entrySet()) 1311 { 1312 for (ServiceConfigurationListener delegate : delegates) 1313 { 1314 delegate.onUnorderedConfiguration(entry.getKey(), Collections.unmodifiableCollection(entry.getValue())); 1315 } 1316 } 1317 1318 for (Entry<ServiceDef, List> entry : ordered.entrySet()) 1319 { 1320 for (ServiceConfigurationListener delegate : delegates) 1321 { 1322 delegate.onOrderedConfiguration(entry.getKey(), Collections.unmodifiableList(entry.getValue())); 1323 } 1324 } 1325 1326 mapped.clear(); 1327 mapped = null; 1328 unordered.clear(); 1329 unordered = null; 1330 ordered.clear(); 1331 ordered = null; 1332 1333 } 1334 1335 @Override 1336 public void onOrderedConfiguration(ServiceDef serviceDef, List configuration) 1337 { 1338 log("ordered", serviceDef, configuration); 1339 if (delegates == null) 1340 { 1341 ordered.put(serviceDef, configuration); 1342 } 1343 else 1344 { 1345 for (ServiceConfigurationListener delegate : delegates) 1346 { 1347 delegate.onOrderedConfiguration(serviceDef, Collections.unmodifiableList(configuration)); 1348 } 1349 } 1350 } 1351 1352 @Override 1353 public void onUnorderedConfiguration(ServiceDef serviceDef, Collection configuration) 1354 { 1355 log("unordered", serviceDef, configuration); 1356 if (delegates == null) 1357 { 1358 unordered.put(serviceDef, configuration); 1359 } 1360 else 1361 { 1362 for (ServiceConfigurationListener delegate : delegates) 1363 { 1364 delegate.onUnorderedConfiguration(serviceDef, Collections.unmodifiableCollection(configuration)); 1365 } 1366 } 1367 } 1368 1369 @Override 1370 public void onMappedConfiguration(ServiceDef serviceDef, Map configuration) 1371 { 1372 log("mapped", serviceDef, configuration); 1373 if (delegates == null) 1374 { 1375 mapped.put(serviceDef, configuration); 1376 } 1377 else 1378 { 1379 for (ServiceConfigurationListener delegate : delegates) 1380 { 1381 delegate.onMappedConfiguration(serviceDef, Collections.unmodifiableMap(configuration)); 1382 } 1383 } 1384 1385 } 1386 1387 private void log(String type, ServiceDef serviceDef, Object configuration) 1388 { 1389 if (logger.isDebugEnabled()) 1390 { 1391 logger.debug(String.format("Service %s %s configuration: %s", 1392 serviceDef.getServiceId(), type, configuration.toString())); 1393 } 1394 } 1395 1396 } 1397 1398}