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    }