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
015package org.apache.tapestry5.ioc.internal;
016
017import org.apache.tapestry5.func.F;
018import org.apache.tapestry5.func.Flow;
019import org.apache.tapestry5.func.Mapper;
020import org.apache.tapestry5.func.Predicate;
021import org.apache.tapestry5.ioc.*;
022import org.apache.tapestry5.ioc.annotations.Local;
023import org.apache.tapestry5.ioc.def.*;
024import org.apache.tapestry5.ioc.internal.services.PerthreadManagerImpl;
025import org.apache.tapestry5.ioc.internal.services.RegistryShutdownHubImpl;
026import org.apache.tapestry5.ioc.internal.util.*;
027import org.apache.tapestry5.ioc.services.*;
028import org.apache.tapestry5.ioc.util.AvailableValues;
029import org.apache.tapestry5.ioc.util.UnknownValueException;
030import org.apache.tapestry5.services.UpdateListenerHub;
031import org.slf4j.Logger;
032
033import java.lang.annotation.Annotation;
034import java.lang.reflect.Constructor;
035import java.lang.reflect.InvocationHandler;
036import java.lang.reflect.Method;
037import java.lang.reflect.Proxy;
038import java.util.*;
039
040@SuppressWarnings("all")
041public 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}