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