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