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