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