    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    }