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