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    }