    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        // TAP5-2649. NOTICE: if someday an ordering between modules is added, this should be reverted
    544        // or a notice added to the documentation.
    545        List<Module> modules = new ArrayList<Module>(moduleToServiceDefs.keySet());
    546        Collections.sort(modules, new ModuleComparator());
    547
    548        for (Module m : modules)
    549            addToUnorderedConfiguration(result, objectType, serviceDef, m);
    550
    551        if (!isServiceConfigurationListenerServiceDef(serviceDef))
    552        {
    553            serviceConfigurationListener.onUnorderedConfiguration(serviceDef, result);
    554        }
    555
    556        return result;
    557    }
    558
    559    @Override
    560    @SuppressWarnings("unchecked")
    561    public <T> List<T> getOrderedConfiguration(ServiceDef3 serviceDef, Class<T> objectType)
    562    {
    563        lock.check();
    564
    565        String serviceId = serviceDef.getServiceId();
    566        Logger logger = getServiceLogger(serviceId);
    567
    568        Orderer<T> orderer = new Orderer<T>(logger);
    569        Map<String, OrderedConfigurationOverride<T>> overrides = CollectionFactory.newCaseInsensitiveMap();
    570
    571        // TAP5-2129. NOTICE: if someday an ordering between modules is added, this should be reverted
    572        // or a notice added to the documentation.
    573        List<Module> modules = new ArrayList<Module>(moduleToServiceDefs.keySet());
    574        Collections.sort(modules, new ModuleComparator());
    575
    576        for (Module m : modules)
    577            addToOrderedConfiguration(orderer, overrides, objectType, serviceDef, m);
    578
    579        // An ugly hack ... perhaps we should introduce a new builtin service so that this can be
    580        // accomplished in the normal way?
    581
    582        if (serviceId.equals("MasterObjectProvider"))
    583        {
    584            ObjectProvider contribution = new ObjectProvider()
    585            {
    586                @Override
    587                public <T> T provide(Class<T> objectType, AnnotationProvider annotationProvider, ObjectLocator locator)
    588                {
    589                    return findServiceByMarkerAndType(objectType, annotationProvider, null);
    590                }
    591            };
    592
    593            orderer.add("ServiceByMarker", (T) contribution);
    594        }
    595
    596        for (OrderedConfigurationOverride<T> override : overrides.values())
    597            override.apply();
    598
    599        final List<T> result = orderer.getOrdered();
    600
    601        if (!isServiceConfigurationListenerServiceDef(serviceDef))
    602        {
    603            serviceConfigurationListener.onOrderedConfiguration(serviceDef, result);
    604        }
    605
    606        return result;
    607    }
    608
    609    private boolean isServiceConfigurationListenerServiceDef(ServiceDef serviceDef)
    610    {
    611        return serviceDef.getServiceId().equalsIgnoreCase(ServiceConfigurationListener.class.getSimpleName());
    612    }
    613
    614    @Override
    615    public <K, V> Map<K, V> getMappedConfiguration(ServiceDef3 serviceDef, Class<K> keyType, Class<V> objectType)
    616    {
    617        lock.check();
    618
    619        // When the key type is String, then a case insensitive map is used.
    620
    621        Map<K, V> result = newConfigurationMap(keyType);
    622        Map<K, ContributionDef> keyToContribution = newConfigurationMap(keyType);
    623        Map<K, MappedConfigurationOverride<K, V>> overrides = newConfigurationMap(keyType);
    624
    625        for (Module m : moduleToServiceDefs.keySet())
    626            addToMappedConfiguration(result, overrides, keyToContribution, keyType, objectType, serviceDef, m);
    627
    628        for (MappedConfigurationOverride<K, V> override : overrides.values())
    629        {
    630            override.apply();
    631        }
    632
    633        if (!isServiceConfigurationListenerServiceDef(serviceDef))
    634        {
    635            serviceConfigurationListener.onMappedConfiguration(serviceDef, result);
    636        }
    637
    638        return result;
    639    }
    640
    641    @SuppressWarnings("unchecked")
    642    private <K, V> Map<K, V> newConfigurationMap(Class<K> keyType)
    643    {
    644        if (keyType.equals(String.class))
    645        {
    646            Map<String, K> result = CollectionFactory.newCaseInsensitiveMap();
    647
    648            return (Map<K, V>) result;
    649        }
    650
    651        return CollectionFactory.newMap();
    652    }
    653
    654    private <K, V> void addToMappedConfiguration(Map<K, V> map, Map<K, MappedConfigurationOverride<K, V>> overrides,
    655                                                 Map<K, ContributionDef> keyToContribution, Class<K> keyClass, Class<V> valueType, ServiceDef3 serviceDef,
    656                                                 final Module module)
    657    {
    658        String serviceId = serviceDef.getServiceId();
    659        Set<ContributionDef2> contributions = module.getContributorDefsForService(serviceDef);
    660
    661        if (contributions.isEmpty())
    662            return;
    663
    664        Logger logger = getServiceLogger(serviceId);
    665
    666        final ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger);
    667
    668        for (final ContributionDef def : contributions)
    669        {
    670            final MappedConfiguration<K, V> validating = new ValidatingMappedConfigurationWrapper<K, V>(valueType,
    671                    resources, typeCoercerProxy, map, overrides, serviceId, def, keyClass, keyToContribution);
    672
    673            String description = "Invoking " + def;
    674
    675            logger.debug(description);
    676
    677            operationTracker.run(description, new Runnable()
    678            {
    679                @Override
    680                public void run()
    681                {
    682                    def.contribute(module, resources, validating);
    683                }
    684            });
    685        }
    686    }
    687
    688    private <T> void addToUnorderedConfiguration(Collection<T> collection, Class<T> valueType, ServiceDef3 serviceDef,
    689                                                 final Module module)
    690    {
    691        String serviceId = serviceDef.getServiceId();
    692        Set<ContributionDef2> contributions = module.getContributorDefsForService(serviceDef);
    693
    694        if (contributions.isEmpty())
    695            return;
    696
    697        Logger logger = getServiceLogger(serviceId);
    698
    699        final ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger);
    700
    701        for (final ContributionDef def : contributions)
    702        {
    703            final Configuration<T> validating = new ValidatingConfigurationWrapper<T>(valueType, resources,
    704                    typeCoercerProxy, collection, serviceId);
    705
    706            String description = "Invoking " + def;
    707
    708            logger.debug(description);
    709
    710            operationTracker.run(description, new Runnable()
    711            {
    712                @Override
    713                public void run()
    714                {
    715                    def.contribute(module, resources, validating);
    716                }
    717            });
    718        }
    719    }
    720
    721    private <T> void addToOrderedConfiguration(Orderer<T> orderer,
    722                                               Map<String, OrderedConfigurationOverride<T>> overrides, Class<T> valueType, ServiceDef3 serviceDef,
    723                                               final Module module)
    724    {
    725        String serviceId = serviceDef.getServiceId();
    726        Set<ContributionDef2> contributions = module.getContributorDefsForService(serviceDef);
    727
    728        if (contributions.isEmpty())
    729            return;
    730
    731        Logger logger = getServiceLogger(serviceId);
    732
    733        final ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger);
    734
    735        for (final ContributionDef def : contributions)
    736        {
    737            final OrderedConfiguration<T> validating = new ValidatingOrderedConfigurationWrapper<T>(valueType,
    738                    resources, typeCoercerProxy, orderer, overrides, def);
    739
    740            String description = "Invoking " + def;
    741
    742            logger.debug(description);
    743
    744            operationTracker.run(description, new Runnable()
    745            {
    746                @Override
    747                public void run()
    748                {
    749                    def.contribute(module, resources, validating);
    750                }
    751            });
    752        }
    753    }
    754
    755    @Override
    756    public <T> T getService(Class<T> serviceInterface)
    757    {
    758        lock.check();
    759
    760        return getServiceByTypeAndMarkers(serviceInterface);
    761    }
    762
    763    @Override
    764    public <T> T getService(Class<T> serviceInterface, Class<? extends Annotation>... markerTypes)
    765    {
    766        lock.check();
    767
    768        return getServiceByTypeAndMarkers(serviceInterface, markerTypes);
    769    }
    770
    771    private <T> T getServiceByTypeAlone(Class<T> serviceInterface)
    772    {
    773        List<String> serviceIds = findServiceIdsForInterface(serviceInterface);
    774
    775        if (serviceIds == null)
    776            serviceIds = Collections.emptyList();
    777
    778        switch (serviceIds.size())
    779        {
    780            case 0:
    781
    782                throw new RuntimeException(IOCMessages.noServiceMatchesType(serviceInterface));
    783
    784            case 1:
    785
    786                String serviceId = serviceIds.get(0);
    787
    788                return getService(serviceId, serviceInterface);
    789
    790            default:
    791
    792                Collections.sort(serviceIds);
    793
    794                throw new RuntimeException(IOCMessages.manyServiceMatches(serviceInterface, serviceIds));
    795        }
    796    }
    797
    798    private <T> T getServiceByTypeAndMarkers(Class<T> serviceInterface, Class<? extends Annotation>... markerTypes)
    799    {
    800        if (markerTypes.length == 0)
    801        {
    802            return getServiceByTypeAlone(serviceInterface);
    803        }
    804
    805        AnnotationProvider provider = createAnnotationProvider(markerTypes);
    806
    807        Set<ServiceDef2> matches = CollectionFactory.newSet();
    808        List<Class> markers = CollectionFactory.newList();
    809
    810        findServiceDefsMatchingMarkerAndType(serviceInterface, provider, null, markers, matches);
    811
    812        return extractServiceFromMatches(serviceInterface, markers, matches);
    813    }
    814
    815    private AnnotationProvider createAnnotationProvider(Class<? extends Annotation>... markerTypes)
    816    {
    817        final Map<Class<? extends Annotation>, Annotation> map = CollectionFactory.newMap();
    818
    819        for (Class<? extends Annotation> markerType : markerTypes)
    820        {
    821            map.put(markerType, createAnnotationProxy(markerType));
    822        }
    823
    824        return new AnnotationProvider()
    825        {
    826            @Override
    827            public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
    828            {
    829                return annotationClass.cast(map.get(annotationClass));
    830            }
    831        };
    832    }
    833
    834    private <A extends Annotation> Annotation createAnnotationProxy(final Class<A> annotationType)
    835    {
    836        Annotation result = cachedAnnotationProxies.get(annotationType);
    837
    838        if (result == null)
    839        {
    840            // We create a JDK proxy because its pretty quick and easy.
    841
    842            InvocationHandler handler = new InvocationHandler()
    843            {
    844                @Override
    845                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    846                {
    847                    if (method.getName().equals("annotationType"))
    848                    {
    849                        return annotationType;
    850                    }
    851
    852                    return method.invoke(proxy, args);
    853                }
    854            };
    855
    856            result = (Annotation) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
    857                    new Class[]{annotationType},
    858                    handler);
    859
    860            cachedAnnotationProxies.put(annotationType, result);
    861        }
    862
    863        return result;
    864    }
    865
    866    private List<String> findServiceIdsForInterface(Class serviceInterface)
    867    {
    868        List<String> result = CollectionFactory.newList();
    869
    870        for (Module module : moduleToServiceDefs.keySet())
    871            result.addAll(module.findServiceIdsForInterface(serviceInterface));
    872
    873        for (Map.Entry<String, Object> entry : builtinServices.entrySet())
    874        {
    875            if (serviceInterface.isInstance(entry.getValue()))
    876                result.add(entry.getKey());
    877        }
    878
    879        Collections.sort(result);
    880
    881        return result;
    882    }
    883
    884    @Override
    885    public ServiceLifecycle2 getServiceLifecycle(String scope)
    886    {
    887        lock.check();
    888
    889        ServiceLifecycle result = lifecycles.get(scope);
    890
    891        if (result == null)
    892        {
    893            ServiceLifecycleSource source = getService("ServiceLifecycleSource", ServiceLifecycleSource.class);
    894
    895            result = source.get(scope);
    896        }
    897
    898        if (result == null)
    899            throw new RuntimeException(IOCMessages.unknownScope(scope));
    900
    901        return InternalUtils.toServiceLifecycle2(result);
    902    }
    903
    904    @Override
    905    public List<ServiceDecorator> findDecoratorsForService(ServiceDef3 serviceDef)
    906    {
    907        lock.check();
    908
    909        assert serviceDef != null;
    910
    911        Logger logger = getServiceLogger(serviceDef.getServiceId());
    912
    913        Orderer<ServiceDecorator> orderer = new Orderer<ServiceDecorator>(logger, true);
    914
    915        for (Module module : moduleToServiceDefs.keySet())
    916        {
    917            Set<DecoratorDef> decoratorDefs = module.findMatchingDecoratorDefs(serviceDef);
    918
    919            if (decoratorDefs.isEmpty())
    920                continue;
    921
    922            ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger);
    923
    924            for (DecoratorDef decoratorDef : decoratorDefs)
    925            {
    926                ServiceDecorator decorator = decoratorDef.createDecorator(module, resources);
    927                try
    928                {
    929                    orderer.add(decoratorDef.getDecoratorId(), decorator, decoratorDef.getConstraints());
    930                }
    931                catch (IllegalArgumentException e) {
    932                    throw new RuntimeException(String.format(
    933                            "Service %s has two different decorators methods named decorate%s in different module classes. "
    934                            + "You can solve this by renaming one of them and annotating it with @Match(\"%2$s\").",
    935                            serviceDef.getServiceId(), decoratorDef.getDecoratorId()));
    936                }
    937            }
    938        }
    939
    940        return orderer.getOrdered();
    941    }
    942
    943    @Override
    944    public List<ServiceAdvisor> findAdvisorsForService(ServiceDef3 serviceDef)
    945    {
    946        lock.check();
    947
    948        assert serviceDef != null;
    949
    950        Logger logger = getServiceLogger(serviceDef.getServiceId());
    951
    952        Orderer<ServiceAdvisor> orderer = new Orderer<ServiceAdvisor>(logger, true);
    953
    954        for (Module module : moduleToServiceDefs.keySet())
    955        {
    956            Set<AdvisorDef> advisorDefs = module.findMatchingServiceAdvisors(serviceDef);
    957
    958            if (advisorDefs.isEmpty())
    959                continue;
    960
    961            ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger);
    962
    963            for (AdvisorDef advisorDef : advisorDefs)
    964            {
    965                ServiceAdvisor advisor = advisorDef.createAdvisor(module, resources);
    966
    967                orderer.add(advisorDef.getAdvisorId(), advisor, advisorDef.getConstraints());
    968            }
    969        }
    970
    971        return orderer.getOrdered();
    972    }
    973
    974    @Override
    975    public <T> T getObject(Class<T> objectType, AnnotationProvider annotationProvider, ObjectLocator locator,
    976                           Module localModule)
    977    {
    978        lock.check();
    979
    980        AnnotationProvider effectiveProvider = annotationProvider != null ? annotationProvider
    981                : new NullAnnotationProvider();
    982
    983        // We do a check here for known marker/type combinations, so that you can use a marker
    984        // annotation
    985        // to inject into a contribution method that contributes to MasterObjectProvider.
    986        // We also force a contribution into MasterObjectProvider to accomplish the same thing.
    987
    988        T result = findServiceByMarkerAndType(objectType, annotationProvider, localModule);
    989
    990        if (result != null)
    991            return result;
    992
    993        MasterObjectProvider masterProvider = getService(IOCConstants.MASTER_OBJECT_PROVIDER_SERVICE_ID,
    994                MasterObjectProvider.class);
    995
    996        return masterProvider.provide(objectType, effectiveProvider, locator, true);
    997    }
    998
    999    private Collection<ServiceDef2> filterByType(Class<?> objectType, Collection<ServiceDef2> serviceDefs)
    1000    {
    1001        Collection<ServiceDef2> result = CollectionFactory.newSet();
    1002
    1003        for (ServiceDef2 sd : serviceDefs)
    1004        {
    1005            if (objectType.isAssignableFrom(sd.getServiceInterface()))
    1006            {
    1007                result.add(sd);
    1008            }
    1009        }
    1010
    1011        return result;
    1012    }
    1013
    1014    @SuppressWarnings("unchecked")
    1015    private <T> T findServiceByMarkerAndType(Class<T> objectType, AnnotationProvider provider, Module localModule)
    1016    {
    1017        if (provider == null)
    1018            return null;
    1019
    1020        Set<ServiceDef2> matches = CollectionFactory.newSet();
    1021        List<Class> markers = CollectionFactory.newList();
    1022
    1023        findServiceDefsMatchingMarkerAndType(objectType, provider, localModule, markers, matches);
    1024
    1025
    1026        // If didn't see @Local or any recognized marker annotation, then don't try to filter that
    1027        // way. Continue on, eventually to the MasterObjectProvider service.
    1028
    1029        if (markers.isEmpty())
    1030        {
    1031            return null;
    1032        }
    1033
    1034        return extractServiceFromMatches(objectType, markers, matches);
    1035    }
    1036
    1037    /**
    1038     * Given markers and matches processed by {@link #findServiceDefsMatchingMarkerAndType(Class, org.apache.tapestry5.ioc.AnnotationProvider, Module, java.util.List, java.util.Set)}, this
    1039     * finds the singular match, or reports an error for 0 or 2+ matches.
    1040     */
    1041    private <T> T extractServiceFromMatches(Class<T> objectType, List<Class> markers, Set<ServiceDef2> matches)
    1042    {
    1043        switch (matches.size())
    1044        {
    1045
    1046            case 1:
    1047
    1048                ServiceDef def = matches.iterator().next();
    1049
    1050                return getService(def.getServiceId(), objectType);
    1051
    1052            case 0:
    1053
    1054                // It's no accident that the user put the marker annotation at the injection
    1055                // point, since it matches a known marker annotation, it better be there for
    1056                // a reason. So if we don't get a match, we have to assume the user expected
    1057                // one, and that is an error.
    1058
    1059                // This doesn't help when the user places an annotation they *think* is a marker
    1060                // but isn't really a marker (because no service is marked by the annotation).
    1061
    1062                throw new RuntimeException(IOCMessages.noServicesMatchMarker(objectType, markers));
    1063
    1064            default:
    1065                throw new RuntimeException(IOCMessages.manyServicesMatchMarker(objectType, markers, matches));
    1066        }
    1067    }
    1068
    1069    private <T> void findServiceDefsMatchingMarkerAndType(Class<T> objectType, AnnotationProvider provider, Module localModule, List<Class> markers,
    1070                                                          Set<ServiceDef2> matches)
    1071    {
    1072        assert provider != null;
    1073
    1074        boolean localOnly = localModule != null && provider.getAnnotation(Local.class) != null;
    1075
    1076        matches.addAll(filterByType(objectType, localOnly ? moduleToServiceDefs.get(localModule) : allServiceDefs));
    1077
    1078        if (localOnly)
    1079        {
    1080            markers.add(Local.class);
    1081        }
    1082
    1083        for (Entry<Class, List<ServiceDef2>> entry : markerToServiceDef.entrySet())
    1084        {
    1085            Class marker = entry.getKey();
    1086            if (provider.getAnnotation(marker) == null)
    1087            {
    1088                continue;
    1089            }
    1090
    1091            markers.add(marker);
    1092
    1093            matches.retainAll(entry.getValue());
    1094
    1095            if (matches.isEmpty())
    1096            {
    1097                return;
    1098            }
    1099        }
    1100    }
    1101
    1102    @Override
    1103    public <T> T getObject(Class<T> objectType, AnnotationProvider annotationProvider)
    1104    {
    1105        return getObject(objectType, annotationProvider, this, null);
    1106    }
    1107
    1108    @Override
    1109    public void addRegistryShutdownListener(RegistryShutdownListener listener)
    1110    {
    1111        lock.check();
    1112
    1113        registryShutdownHub.addRegistryShutdownListener(listener);
    1114    }
    1115
    1116    @Override
    1117    public void addRegistryShutdownListener(Runnable listener)
    1118    {
    1119        lock.check();
    1120
    1121        registryShutdownHub.addRegistryShutdownListener(listener);
    1122    }
    1123
    1124    @Override
    1125    public void addRegistryWillShutdownListener(Runnable listener)
    1126    {
    1127        lock.check();
    1128
    1129        registryShutdownHub.addRegistryWillShutdownListener(listener);
    1130    }
    1131
    1132    @Override
    1133    public String expandSymbols(String input)
    1134    {
    1135        lock.check();
    1136
    1137        // Again, a bit of work to avoid instantiating the SymbolSource until absolutely necessary.
    1138
    1139        if (!InternalUtils.containsSymbols(input))
    1140            return input;
    1141
    1142        return getSymbolSource().expandSymbols(input);
    1143    }
    1144
    1145    /**
    1146     * Defers obtaining the symbol source until actually needed.
    1147     */
    1148    private SymbolSource getSymbolSource()
    1149    {
    1150        if (symbolSource == null)
    1151            symbolSource = getService(SYMBOL_SOURCE_SERVICE_ID, SymbolSource.class);
    1152
    1153        return symbolSource;
    1154    }
    1155
    1156    @Override
    1157    public <T> T autobuild(String description, final Class<T> clazz)
    1158    {
    1159        return invoke(description, new Invokable<T>()
    1160        {
    1161            @Override
    1162            public T invoke()
    1163            {
    1164                return autobuild(clazz);
    1165            }
    1166        });
    1167    }
    1168
    1169    @Override
    1170    public <T> T autobuild(final Class<T> clazz)
    1171    {
    1172        assert clazz != null;
    1173        final Constructor constructor = InternalUtils.findAutobuildConstructor(clazz);
    1174
    1175        if (constructor == null)
    1176        {
    1177            throw new RuntimeException(IOCMessages.noAutobuildConstructor(clazz));
    1178        }
    1179
    1180        Map<Class, Object> resourcesMap = CollectionFactory.newMap();
    1181        resourcesMap.put(OperationTracker.class, RegistryImpl.this);
    1182
    1183        InjectionResources resources = new MapInjectionResources(resourcesMap);
    1184
    1185        ObjectCreator<T> plan = InternalUtils.createConstructorConstructionPlan(this, this, resources, null, "Invoking " + proxyFactory.getConstructorLocation(constructor).toString(), constructor);
    1186
    1187        return plan.createObject();
    1188    }
    1189
    1190    @Override
    1191    public <T> T proxy(Class<T> interfaceClass, Class<? extends T> implementationClass)
    1192    {
    1193        return proxy(interfaceClass, implementationClass, this);
    1194    }
    1195
    1196    @Override
    1197    public <T> T proxy(Class<T> interfaceClass, Class<? extends T> implementationClass, ObjectLocator locator)
    1198    {
    1199        assert interfaceClass != null;
    1200        assert implementationClass != null;
    1201
    1202        if (InternalUtils.SERVICE_CLASS_RELOADING_ENABLED && InternalUtils.isLocalFile(implementationClass))
    1203            return createReloadingProxy(interfaceClass, implementationClass, locator);
    1204
    1205        return createNonReloadingProxy(interfaceClass, implementationClass, locator);
    1206    }
    1207
    1208    private <T> T createNonReloadingProxy(Class<T> interfaceClass, final Class<? extends T> implementationClass,
    1209                                          final ObjectLocator locator)
    1210    {
    1211        final ObjectCreator<T> autobuildCreator = new ObjectCreator<T>()
    1212        {
    1213            @Override
    1214            public T createObject()
    1215            {
    1216                return locator.autobuild(implementationClass);
    1217            }
    1218        };
    1219
    1220        ObjectCreator<T> justInTime = new ObjectCreator<T>()
    1221        {
    1222            private T delegate;
    1223
    1224            @Override
    1225            public synchronized T createObject()
    1226            {
    1227                if (delegate == null)
    1228                    delegate = autobuildCreator.createObject();
    1229
    1230                return delegate;
    1231            }
    1232        };
    1233
    1234        return proxyFactory.createProxy(interfaceClass, justInTime,
    1235                String.format("<Autobuild proxy %s(%s)>", implementationClass.getName(), interfaceClass.getName()));
    1236    }
    1237
    1238    private <T> T createReloadingProxy(Class<T> interfaceClass, final Class<? extends T> implementationClass,
    1239                                       ObjectLocator locator)
    1240    {
    1241        ReloadableObjectCreator creator = new ReloadableObjectCreator(proxyFactory, implementationClass.getClassLoader(),
    1242                implementationClass.getName(), loggerSource.getLogger(implementationClass), this, locator);
    1243
    1244        getService(UpdateListenerHub.class).addUpdateListener(creator);
    1245
    1246        return proxyFactory.createProxy(interfaceClass, implementationClass, (ObjectCreator<T>) creator,
    1247                String.format("<Autoreload proxy %s(%s)>", implementationClass.getName(), interfaceClass.getName()));
    1248    }
    1249
    1250    @Override
    1251    public Object provideServiceProxy(String serviceId)
    1252    {
    1253        return getService(serviceId, Object.class);
    1254    }
    1255
    1256    @Override
    1257    public void run(String description, Runnable operation)
    1258    {
    1259        operationTracker.run(description, operation);
    1260    }
    1261
    1262    @Override
    1263    public <T> T invoke(String description, Invokable<T> operation)
    1264    {
    1265        return operationTracker.invoke(description, operation);
    1266    }
    1267
    1268    @Override
    1269    public <T> T perform(String description, IOOperation<T> operation) throws IOException
    1270    {
    1271        return operationTracker.perform(description, operation);
    1272    }
    1273
    1274    @Override
    1275    public Set<Class> getMarkerAnnotations()
    1276    {
    1277        return markerToServiceDef.keySet();
    1278    }
    1279
    1280    final private static class ModuleComparator implements Comparator<Module> {
    1281        @Override
    1282        public int compare(Module m1, Module m2)
    1283        {
    1284            return m1.getLoggerName().compareTo(m2.getLoggerName());
    1285        }
    1286    }
    1287
    1288    final static private class DelegatingServiceConfigurationListener implements ServiceConfigurationListener {
    1289
    1290        final private Logger logger;
    1291
    1292        private List<ServiceConfigurationListener> delegates;
    1293        private Map<ServiceDef, Map> mapped = CollectionFactory.newMap();
    1294        private Map<ServiceDef, Collection> unordered = CollectionFactory.newMap();
    1295        private Map<ServiceDef, List> ordered = CollectionFactory.newMap();
    1296
    1297        public DelegatingServiceConfigurationListener(Logger logger)
    1298        {
    1299            this.logger = logger;
    1300        }
    1301
    1302        public void setDelegates(List<ServiceConfigurationListener> delegates)
    1303        {
    1304
    1305            this.delegates = delegates;
    1306
    1307            for (Entry<ServiceDef, Map> entry : mapped.entrySet())
    1308            {
    1309                for (ServiceConfigurationListener delegate : delegates)
    1310                {
    1311                    delegate.onMappedConfiguration(entry.getKey(), Collections.unmodifiableMap(entry.getValue()));
    1312                }
    1313            }
    1314
    1315            for (Entry<ServiceDef, Collection> entry : unordered.entrySet())
    1316            {
    1317                for (ServiceConfigurationListener delegate : delegates)
    1318                {
    1319                    delegate.onUnorderedConfiguration(entry.getKey(), Collections.unmodifiableCollection(entry.getValue()));
    1320                }
    1321            }
    1322
    1323            for (Entry<ServiceDef, List> entry : ordered.entrySet())
    1324            {
    1325                for (ServiceConfigurationListener delegate : delegates)
    1326                {
    1327                    delegate.onOrderedConfiguration(entry.getKey(), Collections.unmodifiableList(entry.getValue()));
    1328                }
    1329            }
    1330
    1331            mapped.clear();
    1332            mapped = null;
    1333            unordered.clear();
    1334            unordered = null;
    1335            ordered.clear();
    1336            ordered = null;
    1337
    1338        }
    1339
    1340        @Override
    1341        public void onOrderedConfiguration(ServiceDef serviceDef, List configuration)
    1342        {
    1343            log("ordered", serviceDef, configuration);
    1344            if (delegates == null)
    1345            {
    1346                ordered.put(serviceDef, configuration);
    1347            }
    1348            else
    1349            {
    1350                for (ServiceConfigurationListener delegate : delegates)
    1351                {
    1352                    delegate.onOrderedConfiguration(serviceDef, Collections.unmodifiableList(configuration));
    1353                }
    1354            }
    1355        }
    1356
    1357        @Override
    1358        public void onUnorderedConfiguration(ServiceDef serviceDef, Collection configuration)
    1359        {
    1360            log("unordered", serviceDef, configuration);
    1361            if (delegates == null)
    1362            {
    1363                unordered.put(serviceDef, configuration);
    1364            }
    1365            else
    1366            {
    1367                for (ServiceConfigurationListener delegate : delegates)
    1368                {
    1369                    delegate.onUnorderedConfiguration(serviceDef, Collections.unmodifiableCollection(configuration));
    1370                }
    1371            }
    1372        }
    1373
    1374        @Override
    1375        public void onMappedConfiguration(ServiceDef serviceDef, Map configuration)
    1376        {
    1377            log("mapped", serviceDef, configuration);
    1378            if (delegates == null)
    1379            {
    1380                mapped.put(serviceDef, configuration);
    1381            }
    1382            else
    1383            {
    1384                for (ServiceConfigurationListener delegate : delegates)
    1385                {
    1386                    delegate.onMappedConfiguration(serviceDef, Collections.unmodifiableMap(configuration));
    1387                }
    1388            }
    1389
    1390        }
    1391
    1392        private void log(String type, ServiceDef serviceDef, Object configuration)
    1393        {
    1394            if (logger.isDebugEnabled())
    1395            {
    1396                logger.debug("Service {} {} configuration: {}",
    1397                        serviceDef.getServiceId(), type, configuration.toString());
    1398            }
    1399        }
    1400
    1401    }
    1402
    1403}