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