    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    private DelegatingServiceConfigurationListener serviceConfigurationListener;
    114
    115    /**
    116     * Constructs the registry from a set of module definitions and other resources.
    117     *
    118     * @param moduleDefs
    119     *         defines the modules (and builders, decorators, etc., within)
    120     * @param proxyFactory
    121     *         used to create new proxy objects
    122     * @param loggerSource
    123     *         used to obtain Logger instances
    124     * @param operationTracker
    125     */
    126    public RegistryImpl(Collection<ModuleDef2> moduleDefs, PlasticProxyFactory proxyFactory,
    127                        LoggerSource loggerSource, OperationTracker operationTracker)
    128    {
    129        assert moduleDefs != null;
    130        assert proxyFactory != null;
    131        assert loggerSource != null;
    132        assert operationTracker != null;
    133
    134        this.loggerSource = loggerSource;
    135        this.operationTracker = operationTracker;
    136
    137        this.proxyFactory = proxyFactory;
    138
    139        serviceConfigurationListener = new DelegatingServiceConfigurationListener(
    140                loggerForBuiltinService(ServiceConfigurationListener.class.getSimpleName()));
    141
    142        Logger logger = loggerForBuiltinService(PERTHREAD_MANAGER_SERVICE_ID);
    143
    144        PerthreadManagerImpl ptmImpl = new PerthreadManagerImpl(logger);
    145
    146        perthreadManager = ptmImpl;
    147
    148        final ServiceActivityTrackerImpl scoreboardAndTracker = new ServiceActivityTrackerImpl(perthreadManager);
    149
    150        tracker = scoreboardAndTracker;
    151
    152        logger = loggerForBuiltinService(REGISTRY_SHUTDOWN_HUB_SERVICE_ID);
    153
    154        registryShutdownHub = new RegistryShutdownHubImpl(logger);
    155        ptmImpl.registerForShutdown(registryShutdownHub);
    156
    157        lifecycles.put("singleton", new SingletonServiceLifecycle());
    158
    159        registryShutdownHub.addRegistryShutdownListener(new Runnable()
    160        {
    161            @Override
    162            public void run()
    163            {
    164                scoreboardAndTracker.shutdown();
    165            }
    166        });
    167
    168        for (ModuleDef2 def : moduleDefs)
    169        {
    170            logger = this.loggerSource.getLogger(def.getLoggerName());
    171
    172            Module module = new ModuleImpl(this, tracker, def, proxyFactory, logger);
    173
    174            Set<ServiceDef2> moduleServiceDefs = CollectionFactory.newSet();
    175
    176            for (String serviceId : def.getServiceIds())
    177            {
    178                ServiceDef2 serviceDef = module.getServiceDef(serviceId);
    179
    180                moduleServiceDefs.add(serviceDef);
    181                allServiceDefs.add(serviceDef);
    182
    183                Module existing = serviceIdToModule.get(serviceId);
    184
    185                if (existing != null)
    186                    throw new RuntimeException(IOCMessages.serviceIdConflict(serviceId,
    187                            existing.getServiceDef(serviceId), serviceDef));
    188
    189                serviceIdToModule.put(serviceId, module);
    190
    191                // The service is defined but will not have gone further than that.
    192                tracker.define(serviceDef, Status.DEFINED);
    193
    194                for (Class marker : serviceDef.getMarkers())
    195                    InternalUtils.addToMapList(markerToServiceDef, marker, serviceDef);
    196            }
    197
    198            moduleToServiceDefs.put(module, moduleServiceDefs);
    199
    200            addStartupsInModule(def, module, logger);
    201        }
    202
    203        addBuiltin(SERVICE_ACTIVITY_SCOREBOARD_SERVICE_ID, ServiceActivityScoreboard.class, scoreboardAndTracker);
    204        addBuiltin(LOGGER_SOURCE_SERVICE_ID, LoggerSource.class, this.loggerSource);
    205        addBuiltin(PERTHREAD_MANAGER_SERVICE_ID, PerthreadManager.class, perthreadManager);
    206        addBuiltin(REGISTRY_SHUTDOWN_HUB_SERVICE_ID, RegistryShutdownHub.class, registryShutdownHub);
    207        addBuiltin(PLASTIC_PROXY_FACTORY_SERVICE_ID, PlasticProxyFactory.class, proxyFactory);
    208
    209        validateContributeDefs(moduleDefs);
    210
    211        serviceConfigurationListener.setDelegates(getService(ServiceConfigurationListenerHub.class).getListeners());
    212
    213        scoreboardAndTracker.startup();
    214
    215        SerializationSupport.setProvider(this);
    216
    217    }
    218
    219    private void addStartupsInModule(ModuleDef2 def, final Module module, final Logger logger)
    220    {
    221        for (final StartupDef startup : def.getStartups())
    222        {
    223
    224            startups.add(new Runnable()
    225            {
    226                @Override
    227                public void run()
    228                {
    229                    startup.invoke(module, RegistryImpl.this, RegistryImpl.this, logger);
    230                }
    231            });
    232        }
    233    }
    234
    235    /**
    236     * Validate that each module's ContributeDefs correspond to an actual service.
    237     */
    238    private void validateContributeDefs(Collection<ModuleDef2> moduleDefs)
    239    {
    240        for (ModuleDef2 module : moduleDefs)
    241        {
    242            Set<ContributionDef> contributionDefs = module.getContributionDefs();
    243
    244            for (ContributionDef cd : contributionDefs)
    245            {
    246                String serviceId = cd.getServiceId();
    247
    248                ContributionDef3 cd3 = InternalUtils.toContributionDef3(cd);
    249
    250                // Ignore any optional contribution methods; there's no way to validate that
    251                // they contribute to a known service ... that's the point of @Optional
    252
    253                if (cd3.isOptional())
    254                {
    255                    continue;
    256                }
    257
    258                // Otherwise, check that the service being contributed to exists ...
    259
    260                if (cd3.getServiceId() != null)
    261                {
    262                    if (!serviceIdToModule.containsKey(serviceId))
    263                    {
    264                        throw new IllegalArgumentException(
    265                                IOCMessages.contributionForNonexistentService(cd));
    266                    }
    267                } else if (!isContributionForExistentService(module, cd3))
    268                {
    269                    throw new IllegalArgumentException(
    270                            IOCMessages.contributionForUnqualifiedService(cd3));
    271                }
    272            }
    273        }
    274
    275    }
    276
    277    /**
    278     * Invoked when the contribution method didn't follow the naming convention and so doesn't identify
    279     * a service by id; instead there was an @Contribute to identify the service interface.
    280     */
    281    @SuppressWarnings("all")
    282    private boolean isContributionForExistentService(ModuleDef moduleDef, final ContributionDef2 cd)
    283    {
    284        final Set<Class> contributionMarkers = new HashSet(cd.getMarkers());
    285
    286        boolean localOnly = contributionMarkers.contains(Local.class);
    287
    288        Flow<ServiceDef2> serviceDefs = localOnly ? getLocalServiceDefs(moduleDef) : F.flow(allServiceDefs);
    289
    290        contributionMarkers.retainAll(getMarkerAnnotations());
    291        contributionMarkers.remove(Local.class);
    292
    293        // Match services with the correct interface AND having as markers *all* the marker annotations
    294
    295        Flow<ServiceDef2> filtered = serviceDefs.filter(F.and(new Predicate<ServiceDef2>()
    296                                                              {
    297                                                                  @Override
    298                                                                  public boolean accept(ServiceDef2 object)
    299                                                                  {
    300                                                                      return object.getServiceInterface().equals(cd.getServiceInterface());
    301                                                                  }
    302                                                              }, new Predicate<ServiceDef2>()
    303                                                              {
    304                                                                  @Override
    305                                                                  public boolean accept(ServiceDef2 serviceDef)
    306                                                                  {
    307                                                                      return serviceDef.getMarkers().containsAll(contributionMarkers);
    308                                                                  }
    309                                                              }
    310        ));
    311
    312        // That's a lot of logic; the good news is it will short-circuit as soon as it finds a single match,
    313        // thanks to the laziness inside Flow.
    314
    315        return !filtered.isEmpty();
    316    }
    317
    318    private Flow<ServiceDef2> getLocalServiceDefs(final ModuleDef moduleDef)
    319    {
    320        return F.flow(moduleDef.getServiceIds()).map(new Mapper<String, ServiceDef2>()
    321        {
    322            @Override
    323            public ServiceDef2 map(String value)
    324            {
    325                return InternalUtils.toServiceDef2(moduleDef.getServiceDef(value));
    326            }
    327        });
    328    }
    329
    330    /**
    331     * It's not unreasonable for an eagerly-loaded service to decide to start a thread, at which
    332     * point we raise issues
    333     * about improper publishing of the Registry instance from the RegistryImpl constructor. Moving
    334     * eager loading of
    335     * services out to its own method should ensure thread safety.
    336     */
    337    @Override
    338    public void performRegistryStartup()
    339    {
    340        if (JDKUtils.JDK_1_5)
    341        {
    342            throw new RuntimeException("Your JDK version is too old."
    343                    + " Tapestry requires Java 1.6 or newer since version 5.4.");
    344        }
    345        eagerLoadLock.lock();
    346
    347        List<EagerLoadServiceProxy> proxies = CollectionFactory.newList();
    348
    349        for (Module m : moduleToServiceDefs.keySet())
    350            m.collectEagerLoadServices(proxies);
    351
    352        // TAPESTRY-2267: Gather up all the proxies before instantiating any of them.
    353
    354        for (EagerLoadServiceProxy proxy : proxies)
    355        {
    356            proxy.eagerLoadService();
    357        }
    358
    359        for (Runnable startup : startups) {
    360            startup.run();
    361        }
    362
    363        startups.clear();
    364
    365        getService("RegistryStartup", Runnable.class).run();
    366
    367        cleanupThread();
    368    }
    369
    370    @Override
    371    public Logger getServiceLogger(String serviceId)
    372    {
    373        Module module = serviceIdToModule.get(serviceId);
    374
    375        assert module != null;
    376
    377        return loggerSource.getLogger(module.getLoggerName() + "." + serviceId);
    378    }
    379
    380    private Logger loggerForBuiltinService(String serviceId)
    381    {
    382        return loggerSource.getLogger(TapestryIOCModule.class + "." + serviceId);
    383    }
    384
    385    private <T> void addBuiltin(final String serviceId, final Class<T> serviceInterface, T service)
    386    {
    387        builtinTypes.put(serviceId, serviceInterface);
    388        builtinServices.put(serviceId, service);
    389
    390        // Make sure each of the builtin services is also available via the Builtin annotation
    391        // marker.
    392
    393        ServiceDef2 serviceDef = new ServiceDef2()
    394        {
    395            @Override
    396            public ObjectCreator createServiceCreator(ServiceBuilderResources resources)
    397            {
    398                return null;
    399            }
    400
    401            @Override
    402            public Set<Class> getMarkers()
    403            {
    404                return BUILTIN;
    405            }
    406
    407            @Override
    408            public String getServiceId()
    409            {
    410                return serviceId;
    411            }
    412
    413            @Override
    414            public Class getServiceInterface()
    415            {
    416                return serviceInterface;
    417            }
    418
    419            @Override
    420            public String getServiceScope()
    421            {
    422                return ScopeConstants.DEFAULT;
    423            }
    424
    425            @Override
    426            public boolean isEagerLoad()
    427            {
    428                return false;
    429            }
    430
    431            @Override
    432            public boolean isPreventDecoration()
    433            {
    434                return true;
    435            }
    436
    437            @Override
    438            public int hashCode()
    439            {
    440                final int prime = 31;
    441                int result = 1;
    442                result = prime * result + ((serviceId == null) ? 0 : serviceId.hashCode());
    443                return result;
    444            }
    445
    446            @Override
    447            public boolean equals(Object obj)
    448            {
    449                if (this == obj) { return true; }
    450                if (obj == null) { return false; }
    451                if (!(obj instanceof ServiceDefImpl)) { return false; }
    452                ServiceDef other = (ServiceDef) obj;
    453                if (serviceId == null)
    454                {
    455                    if (other.getServiceId() != null) { return false; }
    456                }
    457                else if (!serviceId.equals(other.getServiceId())) { return false; }
    458                return true;
    459            }
    460
    461        };
    462
    463        for (Class marker : serviceDef.getMarkers())
    464        {
    465            InternalUtils.addToMapList(markerToServiceDef, marker, serviceDef);
    466            allServiceDefs.add(serviceDef);
    467        }
    468
    469        tracker.define(serviceDef, Status.BUILTIN);
    470    }
    471
    472    @Override
    473    public synchronized void shutdown()
    474    {
    475        lock.lock();
    476
    477        registryShutdownHub.fireRegistryDidShutdown();
    478
    479        SerializationSupport.clearProvider(this);
    480    }
    481
    482    @Override
    483    public <T> T getService(String serviceId, Class<T> serviceInterface)
    484    {
    485        lock.check();
    486
    487        T result = checkForBuiltinService(serviceId, serviceInterface);
    488        if (result != null)
    489            return result;
    490
    491        // Checking serviceId and serviceInterface is overkill; they have been checked and rechecked
    492        // all the way to here.
    493
    494        Module containingModule = locateModuleForService(serviceId);
    495
    496        return containingModule.getService(serviceId, serviceInterface);
    497    }
    498
    499    private <T> T checkForBuiltinService(String serviceId, Class<T> serviceInterface)
    500    {
    501        Object service = builtinServices.get(serviceId);
    502
    503        if (service == null)
    504            return null;
    505
    506        try
    507        {
    508            return serviceInterface.cast(service);
    509        } catch (ClassCastException ex)
    510        {
    511            throw new RuntimeException(IOCMessages.serviceWrongInterface(serviceId, builtinTypes.get(serviceId),
    512                    serviceInterface));
    513        }
    514    }
    515
    516    @Override
    517    public void cleanupThread()
    518    {
    519        lock.check();
    520
    521        perthreadManager.cleanup();
    522    }
    523
    524    private Module locateModuleForService(String serviceId)
    525    {
    526        Module module = serviceIdToModule.get(serviceId);
    527
    528        if (module == null)
    529            throw new UnknownValueException(String.format("Service id '%s' is not defined by any module.", serviceId),
    530                    new AvailableValues("Defined service ids", serviceIdToModule));
    531
    532        return module;
    533    }
    534
    535    @Override
    536    public <T> Collection<T> getUnorderedConfiguration(ServiceDef3 serviceDef, Class<T> objectType)
    537    {
    538        lock.check();
    539
    540        final Collection<T> result = CollectionFactory.newList();
    541
    542        for (Module m : moduleToServiceDefs.keySet())
    543            addToUnorderedConfiguration(result, objectType, serviceDef, m);
    544
    545        if (!isServiceConfigurationListenerServiceDef(serviceDef))
    546        {
    547            serviceConfigurationListener.onUnorderedConfiguration(serviceDef, result);
    548        }
    549
    550        return result;
    551    }
    552
    553    @Override
    554    @SuppressWarnings("unchecked")
    555    public <T> List<T> getOrderedConfiguration(ServiceDef3 serviceDef, Class<T> objectType)
    556    {
    557        lock.check();
    558
    559        String serviceId = serviceDef.getServiceId();
    560        Logger logger = getServiceLogger(serviceId);
    561
    562        Orderer<T> orderer = new Orderer<T>(logger);
    563        Map<String, OrderedConfigurationOverride<T>> overrides = CollectionFactory.newCaseInsensitiveMap();
    564
    565        // TAP5-2129. NOTICE: if someday an ordering between modules is added, this should be reverted
    566        // or a notice added to the documentation.
    567        List<Module> modules = new ArrayList<Module>(moduleToServiceDefs.keySet());
    568        Collections.sort(modules, new ModuleComparator());
    569
    570        for (Module m : modules)
    571            addToOrderedConfiguration(orderer, overrides, objectType, serviceDef, m);
    572
    573        // An ugly hack ... perhaps we should introduce a new builtin service so that this can be
    574        // accomplished in the normal way?
    575
    576        if (serviceId.equals("MasterObjectProvider"))
    577        {
    578            ObjectProvider contribution = new ObjectProvider()
    579            {
    580                @Override
    581                public <T> T provide(Class<T> objectType, AnnotationProvider annotationProvider, ObjectLocator locator)
    582                {
    583                    return findServiceByMarkerAndType(objectType, annotationProvider, null);
    584                }
    585            };
    586
    587            orderer.add("ServiceByMarker", (T) contribution);
    588        }
    589
    590        for (OrderedConfigurationOverride<T> override : overrides.values())
    591            override.apply();
    592
    593        final List<T> result = orderer.getOrdered();
    594
    595        if (!isServiceConfigurationListenerServiceDef(serviceDef))
    596        {
    597            serviceConfigurationListener.onOrderedConfiguration(serviceDef, result);
    598        }
    599
    600        return result;
    601    }
    602
    603    private boolean isServiceConfigurationListenerServiceDef(ServiceDef serviceDef)
    604    {
    605        return serviceDef.getServiceId().equalsIgnoreCase(ServiceConfigurationListener.class.getSimpleName());
    606    }
    607
    608    @Override
    609    public <K, V> Map<K, V> getMappedConfiguration(ServiceDef3 serviceDef, Class<K> keyType, Class<V> objectType)
    610    {
    611        lock.check();
    612
    613        // When the key type is String, then a case insensitive map is used.
    614
    615        Map<K, V> result = newConfigurationMap(keyType);
    616        Map<K, ContributionDef> keyToContribution = newConfigurationMap(keyType);
    617        Map<K, MappedConfigurationOverride<K, V>> overrides = newConfigurationMap(keyType);
    618
    619        for (Module m : moduleToServiceDefs.keySet())
    620            addToMappedConfiguration(result, overrides, keyToContribution, keyType, objectType, serviceDef, m);
    621
    622        for (MappedConfigurationOverride<K, V> override : overrides.values())
    623        {
    624            override.apply();
    625        }
    626
    627        if (!isServiceConfigurationListenerServiceDef(serviceDef))
    628        {
    629            serviceConfigurationListener.onMappedConfiguration(serviceDef, result);
    630        }
    631
    632        return result;
    633    }
    634
    635    @SuppressWarnings("unchecked")
    636    private <K, V> Map<K, V> newConfigurationMap(Class<K> keyType)
    637    {
    638        if (keyType.equals(String.class))
    639        {
    640            Map<String, K> result = CollectionFactory.newCaseInsensitiveMap();
    641
    642            return (Map<K, V>) result;
    643        }
    644
    645        return CollectionFactory.newMap();
    646    }
    647
    648    private <K, V> void addToMappedConfiguration(Map<K, V> map, Map<K, MappedConfigurationOverride<K, V>> overrides,
    649                                                 Map<K, ContributionDef> keyToContribution, Class<K> keyClass, Class<V> valueType, ServiceDef3 serviceDef,
    650                                                 final Module module)
    651    {
    652        String serviceId = serviceDef.getServiceId();
    653        Set<ContributionDef2> contributions = module.getContributorDefsForService(serviceDef);
    654
    655        if (contributions.isEmpty())
    656            return;
    657
    658        Logger logger = getServiceLogger(serviceId);
    659
    660        final ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger);
    661
    662        for (final ContributionDef def : contributions)
    663        {
    664            final MappedConfiguration<K, V> validating = new ValidatingMappedConfigurationWrapper<K, V>(valueType,
    665                    resources, typeCoercerProxy, map, overrides, serviceId, def, keyClass, keyToContribution);
    666
    667            String description = "Invoking " + def;
    668
    669            logger.debug(description);
    670
    671            operationTracker.run(description, new Runnable()
    672            {
    673                @Override
    674                public void run()
    675                {
    676                    def.contribute(module, resources, validating);
    677                }
    678            });
    679        }
    680    }
    681
    682    private <T> void addToUnorderedConfiguration(Collection<T> collection, Class<T> valueType, ServiceDef3 serviceDef,
    683                                                 final Module module)
    684    {
    685        String serviceId = serviceDef.getServiceId();
    686        Set<ContributionDef2> contributions = module.getContributorDefsForService(serviceDef);
    687
    688        if (contributions.isEmpty())
    689            return;
    690
    691        Logger logger = getServiceLogger(serviceId);
    692
    693        final ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger);
    694
    695        for (final ContributionDef def : contributions)
    696        {
    697            final Configuration<T> validating = new ValidatingConfigurationWrapper<T>(valueType, resources,
    698                    typeCoercerProxy, collection, serviceId);
    699
    700            String description = "Invoking " + def;
    701
    702            logger.debug(description);
    703
    704            operationTracker.run(description, new Runnable()
    705            {
    706                @Override
    707                public void run()
    708                {
    709                    def.contribute(module, resources, validating);
    710                }
    711            });
    712        }
    713    }
    714
    715    private <T> void addToOrderedConfiguration(Orderer<T> orderer,
    716                                               Map<String, OrderedConfigurationOverride<T>> overrides, Class<T> valueType, ServiceDef3 serviceDef,
    717                                               final Module module)
    718    {
    719        String serviceId = serviceDef.getServiceId();
    720        Set<ContributionDef2> contributions = module.getContributorDefsForService(serviceDef);
    721
    722        if (contributions.isEmpty())
    723            return;
    724
    725        Logger logger = getServiceLogger(serviceId);
    726
    727        final ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger);
    728
    729        for (final ContributionDef def : contributions)
    730        {
    731            final OrderedConfiguration<T> validating = new ValidatingOrderedConfigurationWrapper<T>(valueType,
    732                    resources, typeCoercerProxy, orderer, overrides, def);
    733
    734            String description = "Invoking " + def;
    735
    736            logger.debug(description);
    737
    738            operationTracker.run(description, new Runnable()
    739            {
    740                @Override
    741                public void run()
    742                {
    743                    def.contribute(module, resources, validating);
    744                }
    745            });
    746        }
    747    }
    748
    749    @Override
    750    public <T> T getService(Class<T> serviceInterface)
    751    {
    752        lock.check();
    753
    754        return getServiceByTypeAndMarkers(serviceInterface);
    755    }
    756
    757    @Override
    758    public <T> T getService(Class<T> serviceInterface, Class<? extends Annotation>... markerTypes)
    759    {
    760        lock.check();
    761
    762        return getServiceByTypeAndMarkers(serviceInterface, markerTypes);
    763    }
    764
    765    private <T> T getServiceByTypeAlone(Class<T> serviceInterface)
    766    {
    767        List<String> serviceIds = findServiceIdsForInterface(serviceInterface);
    768
    769        if (serviceIds == null)
    770            serviceIds = Collections.emptyList();
    771
    772        switch (serviceIds.size())
    773        {
    774            case 0:
    775
    776                throw new RuntimeException(IOCMessages.noServiceMatchesType(serviceInterface));
    777
    778            case 1:
    779
    780                String serviceId = serviceIds.get(0);
    781
    782                return getService(serviceId, serviceInterface);
    783
    784            default:
    785
    786                Collections.sort(serviceIds);
    787
    788                throw new RuntimeException(IOCMessages.manyServiceMatches(serviceInterface, serviceIds));
    789        }
    790    }
    791
    792    private <T> T getServiceByTypeAndMarkers(Class<T> serviceInterface, Class<? extends Annotation>... markerTypes)
    793    {
    794        if (markerTypes.length == 0)
    795        {
    796            return getServiceByTypeAlone(serviceInterface);
    797        }
    798
    799        AnnotationProvider provider = createAnnotationProvider(markerTypes);
    800
    801        Set<ServiceDef2> matches = CollectionFactory.newSet();
    802        List<Class> markers = CollectionFactory.newList();
    803
    804        findServiceDefsMatchingMarkerAndType(serviceInterface, provider, null, markers, matches);
    805
    806        return extractServiceFromMatches(serviceInterface, markers, matches);
    807    }
    808
    809    private AnnotationProvider createAnnotationProvider(Class<? extends Annotation>... markerTypes)
    810    {
    811        final Map<Class<? extends Annotation>, Annotation> map = CollectionFactory.newMap();
    812
    813        for (Class<? extends Annotation> markerType : markerTypes)
    814        {
    815            map.put(markerType, createAnnotationProxy(markerType));
    816        }
    817
    818        return new AnnotationProvider()
    819        {
    820            @Override
    821            public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
    822            {
    823                return annotationClass.cast(map.get(annotationClass));
    824            }
    825        };
    826    }
    827
    828    private <A extends Annotation> Annotation createAnnotationProxy(final Class<A> annotationType)
    829    {
    830        Annotation result = cachedAnnotationProxies.get(annotationType);
    831
    832        if (result == null)
    833        {
    834            // We create a JDK proxy because its pretty quick and easy.
    835
    836            InvocationHandler handler = new InvocationHandler()
    837            {
    838                @Override
    839                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    840                {
    841                    if (method.getName().equals("annotationType"))
    842                    {
    843                        return annotationType;
    844                    }
    845
    846                    return method.invoke(proxy, args);
    847                }
    848            };
    849
    850            result = (Annotation) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
    851                    new Class[]{annotationType},
    852                    handler);
    853
    854            cachedAnnotationProxies.put(annotationType, result);
    855        }
    856
    857        return result;
    858    }
    859
    860    private List<String> findServiceIdsForInterface(Class serviceInterface)
    861    {
    862        List<String> result = CollectionFactory.newList();
    863
    864        for (Module module : moduleToServiceDefs.keySet())
    865            result.addAll(module.findServiceIdsForInterface(serviceInterface));
    866
    867        for (Map.Entry<String, Object> entry : builtinServices.entrySet())
    868        {
    869            if (serviceInterface.isInstance(entry.getValue()))
    870                result.add(entry.getKey());
    871        }
    872
    873        Collections.sort(result);
    874
    875        return result;
    876    }
    877
    878    @Override
    879    public ServiceLifecycle2 getServiceLifecycle(String scope)
    880    {
    881        lock.check();
    882
    883        ServiceLifecycle result = lifecycles.get(scope);
    884
    885        if (result == null)
    886        {
    887            ServiceLifecycleSource source = getService("ServiceLifecycleSource", ServiceLifecycleSource.class);
    888
    889            result = source.get(scope);
    890        }
    891
    892        if (result == null)
    893            throw new RuntimeException(IOCMessages.unknownScope(scope));
    894
    895        return InternalUtils.toServiceLifecycle2(result);
    896    }
    897
    898    @Override
    899    public List<ServiceDecorator> findDecoratorsForService(ServiceDef3 serviceDef)
    900    {
    901        lock.check();
    902
    903        assert serviceDef != null;
    904
    905        Logger logger = getServiceLogger(serviceDef.getServiceId());
    906
    907        Orderer<ServiceDecorator> orderer = new Orderer<ServiceDecorator>(logger, true);
    908
    909        for (Module module : moduleToServiceDefs.keySet())
    910        {
    911            Set<DecoratorDef> decoratorDefs = module.findMatchingDecoratorDefs(serviceDef);
    912
    913            if (decoratorDefs.isEmpty())
    914                continue;
    915
    916            ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger);
    917
    918            for (DecoratorDef decoratorDef : decoratorDefs)
    919            {
    920                ServiceDecorator decorator = decoratorDef.createDecorator(module, resources);
    921                try
    922                {
    923                    orderer.add(decoratorDef.getDecoratorId(), decorator, decoratorDef.getConstraints());
    924                }
    925                catch (IllegalArgumentException e) {
    926                    throw new RuntimeException(String.format(
    927                            "Service %s has two different decorators methods named decorate%s in different module classes. "
    928                            + "You can solve this by renaming one of them and annotating it with @Match(\"%2$s\").",
    929                            serviceDef.getServiceId(), decoratorDef.getDecoratorId()));
    930                }
    931            }
    932        }
    933
    934        return orderer.getOrdered();
    935    }
    936
    937    @Override
    938    public List<ServiceAdvisor> findAdvisorsForService(ServiceDef3 serviceDef)
    939    {
    940        lock.check();
    941
    942        assert serviceDef != null;
    943
    944        Logger logger = getServiceLogger(serviceDef.getServiceId());
    945
    946        Orderer<ServiceAdvisor> orderer = new Orderer<ServiceAdvisor>(logger);
    947
    948        for (Module module : moduleToServiceDefs.keySet())
    949        {
    950            Set<AdvisorDef> advisorDefs = module.findMatchingServiceAdvisors(serviceDef);
    951
    952            if (advisorDefs.isEmpty())
    953                continue;
    954
    955            ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger);
    956
    957            for (AdvisorDef advisorDef : advisorDefs)
    958            {
    959                ServiceAdvisor advisor = advisorDef.createAdvisor(module, resources);
    960
    961                orderer.add(advisorDef.getAdvisorId(), advisor, advisorDef.getConstraints());
    962            }
    963        }
    964
    965        return orderer.getOrdered();
    966    }
    967
    968    @Override
    969    public <T> T getObject(Class<T> objectType, AnnotationProvider annotationProvider, ObjectLocator locator,
    970                           Module localModule)
    971    {
    972        lock.check();
    973
    974        AnnotationProvider effectiveProvider = annotationProvider != null ? annotationProvider
    975                : new NullAnnotationProvider();
    976
    977        // We do a check here for known marker/type combinations, so that you can use a marker
    978        // annotation
    979        // to inject into a contribution method that contributes to MasterObjectProvider.
    980        // We also force a contribution into MasterObjectProvider to accomplish the same thing.
    981
    982        T result = findServiceByMarkerAndType(objectType, annotationProvider, localModule);
    983
    984        if (result != null)
    985            return result;
    986
    987        MasterObjectProvider masterProvider = getService(IOCConstants.MASTER_OBJECT_PROVIDER_SERVICE_ID,
    988                MasterObjectProvider.class);
    989
    990        return masterProvider.provide(objectType, effectiveProvider, locator, true);
    991    }
    992
    993    private Collection<ServiceDef2> filterByType(Class<?> objectType, Collection<ServiceDef2> serviceDefs)
    994    {
    995        Collection<ServiceDef2> result = CollectionFactory.newSet();
    996
    997        for (ServiceDef2 sd : serviceDefs)
    998        {
    999            if (objectType.isAssignableFrom(sd.getServiceInterface()))
    1000            {
    1001                result.add(sd);
    1002            }
    1003        }
    1004
    1005        return result;
    1006    }
    1007
    1008    @SuppressWarnings("unchecked")
    1009    private <T> T findServiceByMarkerAndType(Class<T> objectType, AnnotationProvider provider, Module localModule)
    1010    {
    1011        if (provider == null)
    1012            return null;
    1013
    1014        Set<ServiceDef2> matches = CollectionFactory.newSet();
    1015        List<Class> markers = CollectionFactory.newList();
    1016
    1017        findServiceDefsMatchingMarkerAndType(objectType, provider, localModule, markers, matches);
    1018
    1019
    1020        // If didn't see @Local or any recognized marker annotation, then don't try to filter that
    1021        // way. Continue on, eventually to the MasterObjectProvider service.
    1022
    1023        if (markers.isEmpty())
    1024        {
    1025            return null;
    1026        }
    1027
    1028        return extractServiceFromMatches(objectType, markers, matches);
    1029    }
    1030
    1031    /**
    1032     * Given markers and matches processed by {@link #findServiceDefsMatchingMarkerAndType(Class, org.apache.tapestry5.ioc.AnnotationProvider, Module, java.util.List, java.util.Set)}, this
    1033     * finds the singular match, or reports an error for 0 or 2+ matches.
    1034     */
    1035    private <T> T extractServiceFromMatches(Class<T> objectType, List<Class> markers, Set<ServiceDef2> matches)
    1036    {
    1037        switch (matches.size())
    1038        {
    1039
    1040            case 1:
    1041
    1042                ServiceDef def = matches.iterator().next();
    1043
    1044                return getService(def.getServiceId(), objectType);
    1045
    1046            case 0:
    1047
    1048                // It's no accident that the user put the marker annotation at the injection
    1049                // point, since it matches a known marker annotation, it better be there for
    1050                // a reason. So if we don't get a match, we have to assume the user expected
    1051                // one, and that is an error.
    1052
    1053                // This doesn't help when the user places an annotation they *think* is a marker
    1054                // but isn't really a marker (because no service is marked by the annotation).
    1055
    1056                throw new RuntimeException(IOCMessages.noServicesMatchMarker(objectType, markers));
    1057
    1058            default:
    1059                throw new RuntimeException(IOCMessages.manyServicesMatchMarker(objectType, markers, matches));
    1060        }
    1061    }
    1062
    1063    private <T> void findServiceDefsMatchingMarkerAndType(Class<T> objectType, AnnotationProvider provider, Module localModule, List<Class> markers,
    1064                                                          Set<ServiceDef2> matches)
    1065    {
    1066        assert provider != null;
    1067
    1068        boolean localOnly = localModule != null && provider.getAnnotation(Local.class) != null;
    1069
    1070        matches.addAll(filterByType(objectType, localOnly ? moduleToServiceDefs.get(localModule) : allServiceDefs));
    1071
    1072        if (localOnly)
    1073        {
    1074            markers.add(Local.class);
    1075        }
    1076
    1077        for (Class marker : markerToServiceDef.keySet())
    1078        {
    1079            if (provider.getAnnotation(marker) == null)
    1080            {
    1081                continue;
    1082            }
    1083
    1084            markers.add(marker);
    1085
    1086            matches.retainAll(markerToServiceDef.get(marker));
    1087
    1088            if (matches.isEmpty())
    1089            {
    1090                return;
    1091            }
    1092        }
    1093    }
    1094
    1095    @Override
    1096    public <T> T getObject(Class<T> objectType, AnnotationProvider annotationProvider)
    1097    {
    1098        return getObject(objectType, annotationProvider, this, null);
    1099    }
    1100
    1101    @Override
    1102    public void addRegistryShutdownListener(RegistryShutdownListener listener)
    1103    {
    1104        lock.check();
    1105
    1106        registryShutdownHub.addRegistryShutdownListener(listener);
    1107    }
    1108
    1109    @Override
    1110    public void addRegistryShutdownListener(Runnable listener)
    1111    {
    1112        lock.check();
    1113
    1114        registryShutdownHub.addRegistryShutdownListener(listener);
    1115    }
    1116
    1117    @Override
    1118    public void addRegistryWillShutdownListener(Runnable listener)
    1119    {
    1120        lock.check();
    1121
    1122        registryShutdownHub.addRegistryWillShutdownListener(listener);
    1123    }
    1124
    1125    @Override
    1126    public String expandSymbols(String input)
    1127    {
    1128        lock.check();
    1129
    1130        // Again, a bit of work to avoid instantiating the SymbolSource until absolutely necessary.
    1131
    1132        if (!InternalUtils.containsSymbols(input))
    1133            return input;
    1134
    1135        return getSymbolSource().expandSymbols(input);
    1136    }
    1137
    1138    /**
    1139     * Defers obtaining the symbol source until actually needed.
    1140     */
    1141    private SymbolSource getSymbolSource()
    1142    {
    1143        if (symbolSource == null)
    1144            symbolSource = getService(SYMBOL_SOURCE_SERVICE_ID, SymbolSource.class);
    1145
    1146        return symbolSource;
    1147    }
    1148
    1149    @Override
    1150    public <T> T autobuild(String description, final Class<T> clazz)
    1151    {
    1152        return invoke(description, new Invokable<T>()
    1153        {
    1154            @Override
    1155            public T invoke()
    1156            {
    1157                return autobuild(clazz);
    1158            }
    1159        });
    1160    }
    1161
    1162    @Override
    1163    public <T> T autobuild(final Class<T> clazz)
    1164    {
    1165        assert clazz != null;
    1166        final Constructor constructor = InternalUtils.findAutobuildConstructor(clazz);
    1167
    1168        if (constructor == null)
    1169        {
    1170            throw new RuntimeException(IOCMessages.noAutobuildConstructor(clazz));
    1171        }
    1172
    1173        Map<Class, Object> resourcesMap = CollectionFactory.newMap();
    1174        resourcesMap.put(OperationTracker.class, RegistryImpl.this);
    1175
    1176        InjectionResources resources = new MapInjectionResources(resourcesMap);
    1177
    1178        ObjectCreator<T> plan = InternalUtils.createConstructorConstructionPlan(this, this, resources, null, "Invoking " + proxyFactory.getConstructorLocation(constructor).toString(), constructor);
    1179
    1180        return plan.createObject();
    1181    }
    1182
    1183    @Override
    1184    public <T> T proxy(Class<T> interfaceClass, Class<? extends T> implementationClass)
    1185    {
    1186        return proxy(interfaceClass, implementationClass, this);
    1187    }
    1188
    1189    @Override
    1190    public <T> T proxy(Class<T> interfaceClass, Class<? extends T> implementationClass, ObjectLocator locator)
    1191    {
    1192        assert interfaceClass != null;
    1193        assert implementationClass != null;
    1194
    1195        if (InternalUtils.SERVICE_CLASS_RELOADING_ENABLED && InternalUtils.isLocalFile(implementationClass))
    1196            return createReloadingProxy(interfaceClass, implementationClass, locator);
    1197
    1198        return createNonReloadingProxy(interfaceClass, implementationClass, locator);
    1199    }
    1200
    1201    private <T> T createNonReloadingProxy(Class<T> interfaceClass, final Class<? extends T> implementationClass,
    1202                                          final ObjectLocator locator)
    1203    {
    1204        final ObjectCreator<T> autobuildCreator = new ObjectCreator<T>()
    1205        {
    1206            @Override
    1207            public T createObject()
    1208            {
    1209                return locator.autobuild(implementationClass);
    1210            }
    1211        };
    1212
    1213        ObjectCreator<T> justInTime = new ObjectCreator<T>()
    1214        {
    1215            private T delegate;
    1216
    1217            @Override
    1218            public synchronized T createObject()
    1219            {
    1220                if (delegate == null)
    1221                    delegate = autobuildCreator.createObject();
    1222
    1223                return delegate;
    1224            }
    1225        };
    1226
    1227        return proxyFactory.createProxy(interfaceClass, justInTime,
    1228                String.format("<Autobuild proxy %s(%s)>", implementationClass.getName(), interfaceClass.getName()));
    1229    }
    1230
    1231    private <T> T createReloadingProxy(Class<T> interfaceClass, final Class<? extends T> implementationClass,
    1232                                       ObjectLocator locator)
    1233    {
    1234        ReloadableObjectCreator creator = new ReloadableObjectCreator(proxyFactory, implementationClass.getClassLoader(),
    1235                implementationClass.getName(), loggerSource.getLogger(implementationClass), this, locator);
    1236
    1237        getService(UpdateListenerHub.class).addUpdateListener(creator);
    1238
    1239        return proxyFactory.createProxy(interfaceClass, implementationClass, (ObjectCreator<T>) creator,
    1240                String.format("<Autoreload proxy %s(%s)>", implementationClass.getName(), interfaceClass.getName()));
    1241    }
    1242
    1243    @Override
    1244    public Object provideServiceProxy(String serviceId)
    1245    {
    1246        return getService(serviceId, Object.class);
    1247    }
    1248
    1249    @Override
    1250    public void run(String description, Runnable operation)
    1251    {
    1252        operationTracker.run(description, operation);
    1253    }
    1254
    1255    @Override
    1256    public <T> T invoke(String description, Invokable<T> operation)
    1257    {
    1258        return operationTracker.invoke(description, operation);
    1259    }
    1260
    1261    @Override
    1262    public <T> T perform(String description, IOOperation<T> operation) throws IOException
    1263    {
    1264        return operationTracker.perform(description, operation);
    1265    }
    1266
    1267    @Override
    1268    public Set<Class> getMarkerAnnotations()
    1269    {
    1270        return markerToServiceDef.keySet();
    1271    }
    1272
    1273    final private static class ModuleComparator implements Comparator<Module> {
    1274        @Override
    1275        public int compare(Module m1, Module m2)
    1276        {
    1277            return m1.getLoggerName().compareTo(m2.getLoggerName());
    1278        }
    1279    }
    1280
    1281    final static private class DelegatingServiceConfigurationListener implements ServiceConfigurationListener {
    1282
    1283        final private Logger logger;
    1284
    1285        private List<ServiceConfigurationListener> delegates;
    1286        private Map<ServiceDef, Map> mapped = CollectionFactory.newMap();
    1287        private Map<ServiceDef, Collection> unordered = CollectionFactory.newMap();
    1288        private Map<ServiceDef, List> ordered = CollectionFactory.newMap();
    1289
    1290        public DelegatingServiceConfigurationListener(Logger logger)
    1291        {
    1292            this.logger = logger;
    1293        }
    1294
    1295        public void setDelegates(List<ServiceConfigurationListener> delegates)
    1296        {
    1297
    1298            this.delegates = delegates;
    1299
    1300            for (ServiceDef serviceDef : mapped.keySet())
    1301            {
    1302                for (ServiceConfigurationListener delegate : delegates)
    1303                {
    1304                    delegate.onMappedConfiguration(serviceDef, Collections.unmodifiableMap(mapped.get(serviceDef)));
    1305                }
    1306            }
    1307
    1308            for (ServiceDef serviceDef : unordered.keySet())
    1309            {
    1310                for (ServiceConfigurationListener delegate : delegates)
    1311                {
    1312                    delegate.onUnorderedConfiguration(serviceDef, Collections.unmodifiableCollection(unordered.get(serviceDef)));
    1313                }
    1314            }
    1315
    1316            for (ServiceDef serviceDef : ordered.keySet())
    1317            {
    1318                for (ServiceConfigurationListener delegate : delegates)
    1319                {
    1320                    delegate.onOrderedConfiguration(serviceDef, Collections.unmodifiableList(ordered.get(serviceDef)));
    1321                }
    1322            }
    1323
    1324            mapped.clear();
    1325            mapped = null;
    1326            unordered.clear();
    1327            unordered = null;
    1328            ordered.clear();
    1329            ordered = null;
    1330
    1331        }
    1332
    1333        @Override
    1334        public void onOrderedConfiguration(ServiceDef serviceDef, List configuration)
    1335        {
    1336            log("ordered", serviceDef, configuration);
    1337            if (delegates == null)
    1338            {
    1339                ordered.put(serviceDef, configuration);
    1340            }
    1341            else
    1342            {
    1343                for (ServiceConfigurationListener delegate : delegates)
    1344                {
    1345                    delegate.onOrderedConfiguration(serviceDef, Collections.unmodifiableList(configuration));
    1346                }
    1347            }
    1348        }
    1349
    1350        @Override
    1351        public void onUnorderedConfiguration(ServiceDef serviceDef, Collection configuration)
    1352        {
    1353            log("unordered", serviceDef, configuration);
    1354            if (delegates == null)
    1355            {
    1356                unordered.put(serviceDef, configuration);
    1357            }
    1358            else
    1359            {
    1360                for (ServiceConfigurationListener delegate : delegates)
    1361                {
    1362                    delegate.onUnorderedConfiguration(serviceDef, Collections.unmodifiableCollection(configuration));
    1363                }
    1364            }
    1365        }
    1366
    1367        @Override
    1368        public void onMappedConfiguration(ServiceDef serviceDef, Map configuration)
    1369        {
    1370            log("mapped", serviceDef, configuration);
    1371            if (delegates == null)
    1372            {
    1373                mapped.put(serviceDef, configuration);
    1374            }
    1375            else
    1376            {
    1377                for (ServiceConfigurationListener delegate : delegates)
    1378                {
    1379                    delegate.onMappedConfiguration(serviceDef, Collections.unmodifiableMap(configuration));
    1380                }
    1381            }
    1382
    1383        }
    1384
    1385        private void log(String type, ServiceDef serviceDef, Object configuration)
    1386        {
    1387            if (logger.isDebugEnabled())
    1388            {
    1389                logger.debug(String.format("Service %s %s configuration: %s",
    1390                        serviceDef.getServiceId(), type, configuration.toString()));
    1391            }
    1392        }
    1393
    1394    }
    1395
    1396}