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