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