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