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