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