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