Package org.apache.tapestry5.ioc.internal

Source Code of org.apache.tapestry5.ioc.internal.RegistryImpl

// Copyright 2006, 2007, 2008, 2009, 2010 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package org.apache.tapestry5.ioc.internal;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.tapestry5.func.F;
import org.apache.tapestry5.func.Flow;
import org.apache.tapestry5.func.Mapper;
import org.apache.tapestry5.func.Predicate;
import org.apache.tapestry5.ioc.AdvisorDef;
import org.apache.tapestry5.ioc.AnnotationProvider;
import org.apache.tapestry5.ioc.Configuration;
import org.apache.tapestry5.ioc.IOCConstants;
import org.apache.tapestry5.ioc.Invokable;
import org.apache.tapestry5.ioc.LoggerSource;
import org.apache.tapestry5.ioc.MappedConfiguration;
import org.apache.tapestry5.ioc.ObjectCreator;
import org.apache.tapestry5.ioc.ObjectLocator;
import org.apache.tapestry5.ioc.ObjectProvider;
import org.apache.tapestry5.ioc.OperationTracker;
import org.apache.tapestry5.ioc.OrderedConfiguration;
import org.apache.tapestry5.ioc.Registry;
import org.apache.tapestry5.ioc.ScopeConstants;
import org.apache.tapestry5.ioc.ServiceAdvisor;
import org.apache.tapestry5.ioc.ServiceBuilderResources;
import org.apache.tapestry5.ioc.ServiceDecorator;
import org.apache.tapestry5.ioc.ServiceLifecycle;
import org.apache.tapestry5.ioc.ServiceLifecycle2;
import org.apache.tapestry5.ioc.ServiceResources;
import org.apache.tapestry5.ioc.annotations.Local;
import org.apache.tapestry5.ioc.def.ContributionDef;
import org.apache.tapestry5.ioc.def.ContributionDef2;
import org.apache.tapestry5.ioc.def.DecoratorDef;
import org.apache.tapestry5.ioc.def.ModuleDef;
import org.apache.tapestry5.ioc.def.ServiceDef;
import org.apache.tapestry5.ioc.def.ServiceDef2;
import org.apache.tapestry5.ioc.internal.services.PerthreadManagerImpl;
import org.apache.tapestry5.ioc.internal.services.RegistryShutdownHubImpl;
import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
import org.apache.tapestry5.ioc.internal.util.InjectionResources;
import org.apache.tapestry5.ioc.internal.util.InternalUtils;
import org.apache.tapestry5.ioc.internal.util.MapInjectionResources;
import org.apache.tapestry5.ioc.internal.util.OneShotLock;
import org.apache.tapestry5.ioc.internal.util.Orderer;
import org.apache.tapestry5.ioc.services.Builtin;
import org.apache.tapestry5.ioc.services.ClassFab;
import org.apache.tapestry5.ioc.services.ClassFactory;
import org.apache.tapestry5.ioc.services.MasterObjectProvider;
import org.apache.tapestry5.ioc.services.PerthreadManager;
import org.apache.tapestry5.ioc.services.RegistryShutdownHub;
import org.apache.tapestry5.ioc.services.RegistryShutdownListener;
import org.apache.tapestry5.ioc.services.ServiceActivityScoreboard;
import org.apache.tapestry5.ioc.services.ServiceLifecycleSource;
import org.apache.tapestry5.ioc.services.Status;
import org.apache.tapestry5.ioc.services.SymbolSource;
import org.apache.tapestry5.ioc.services.TapestryIOCModule;
import org.apache.tapestry5.ioc.util.AvailableValues;
import org.apache.tapestry5.ioc.util.UnknownValueException;
import org.apache.tapestry5.services.UpdateListenerHub;
import org.slf4j.Logger;

@SuppressWarnings("all")
public class RegistryImpl implements Registry, InternalRegistry, ServiceProxyProvider
{
    private static final String SYMBOL_SOURCE_SERVICE_ID = "SymbolSource";

    private static final String REGISTRY_SHUTDOWN_HUB_SERVICE_ID = "RegistryShutdownHub";

    static final String PERTHREAD_MANAGER_SERVICE_ID = "PerthreadManager";

    private static final String SERVICE_ACTIVITY_SCOREBOARD_SERVICE_ID = "ServiceActivityScoreboard";

    /**
     * The set of marker annotations for a builtin service.
     */
    private final static Set<Class> BUILTIN = CollectionFactory.newSet();

    static
    {
        BUILTIN.add(Builtin.class);
    }

    /**
     * Used to obtain the {@link org.apache.tapestry5.ioc.services.ClassFactory} service, which is
     * crucial when creating
     * runtime classes for proxies and the like.
     */
    static final String CLASS_FACTORY_SERVICE_ID = "ClassFactory";

    static final String LOGGER_SOURCE_SERVICE_ID = "LoggerSource";

    private final OneShotLock lock = new OneShotLock();

    private final OneShotLock eagerLoadLock = new OneShotLock();

    private final Map<String, Object> builtinServices = CollectionFactory.newCaseInsensitiveMap();

    private final Map<String, Class> builtinTypes = CollectionFactory.newCaseInsensitiveMap();

    private final RegistryShutdownHubImpl registryShutdownHub;

    private final LoggerSource loggerSource;

    /**
     * Map from service id to the Module that contains the service.
     */
    private final Map<String, Module> serviceIdToModule = CollectionFactory.newCaseInsensitiveMap();

    private final Map<String, ServiceLifecycle2> lifecycles = CollectionFactory.newCaseInsensitiveMap();

    private final PerthreadManager perthreadManager;

    private final ClassFactory classFactory;

    private final ServiceActivityTracker tracker;

    private SymbolSource symbolSource;

    private final Map<Module, Set<ServiceDef2>> moduleToServiceDefs = CollectionFactory.newMap();

    /**
     * From marker type to a list of marked service instances.
     */
    private final Map<Class, List<ServiceDef2>> markerToServiceDef = CollectionFactory.newMap();

    private final Set<ServiceDef2> allServiceDefs = CollectionFactory.newSet();

    private final OperationTracker operationTracker;

    /**
     * Constructs the registry from a set of module definitions and other resources.
     *
     * @param moduleDefs
     *            defines the modules (and builders, decorators, etc., within)
     * @param classFactory
     *            TODO
     * @param loggerSource
     *            used to obtain Logger instances
     */
    public RegistryImpl(Collection<ModuleDef> moduleDefs, ClassFactory classFactory, LoggerSource loggerSource)
    {
        this.loggerSource = loggerSource;

        operationTracker = new PerThreadOperationTracker(loggerSource.getLogger(Registry.class));

        final ServiceActivityTrackerImpl scoreboardAndTracker = new ServiceActivityTrackerImpl();

        tracker = scoreboardAndTracker;

        this.classFactory = classFactory;

        Logger logger = loggerForBuiltinService(PERTHREAD_MANAGER_SERVICE_ID);

        perthreadManager = new PerthreadManagerImpl(logger);

        logger = loggerForBuiltinService(REGISTRY_SHUTDOWN_HUB_SERVICE_ID);

        registryShutdownHub = new RegistryShutdownHubImpl(logger);

        lifecycles.put("singleton", new SingletonServiceLifecycle());

        registryShutdownHub.addRegistryShutdownListener(new RegistryShutdownListener()
        {
            public void registryDidShutdown()
            {
                scoreboardAndTracker.shutdown();
            }
        });

        for (ModuleDef def : moduleDefs)
        {
            logger = this.loggerSource.getLogger(def.getLoggerName());

            Module module = new ModuleImpl(this, tracker, def, classFactory, logger);

            Set<ServiceDef2> moduleServiceDefs = CollectionFactory.newSet();

            for (String serviceId : def.getServiceIds())
            {
                ServiceDef2 serviceDef = module.getServiceDef(serviceId);

                moduleServiceDefs.add(serviceDef);
                allServiceDefs.add(serviceDef);

                Module existing = serviceIdToModule.get(serviceId);

                if (existing != null)
                    throw new RuntimeException(IOCMessages.serviceIdConflict(serviceId,
                            existing.getServiceDef(serviceId), serviceDef));

                serviceIdToModule.put(serviceId, module);

                // The service is defined but will not have gone further than that.
                tracker.define(serviceDef, Status.DEFINED);

                for (Class marker : serviceDef.getMarkers())
                    InternalUtils.addToMapList(markerToServiceDef, marker, serviceDef);
            }

            moduleToServiceDefs.put(module, moduleServiceDefs);
        }

        addBuiltin(SERVICE_ACTIVITY_SCOREBOARD_SERVICE_ID, ServiceActivityScoreboard.class, scoreboardAndTracker);
        addBuiltin(LOGGER_SOURCE_SERVICE_ID, LoggerSource.class, this.loggerSource);
        addBuiltin(CLASS_FACTORY_SERVICE_ID, ClassFactory.class, this.classFactory);
        addBuiltin(PERTHREAD_MANAGER_SERVICE_ID, PerthreadManager.class, perthreadManager);
        addBuiltin(REGISTRY_SHUTDOWN_HUB_SERVICE_ID, RegistryShutdownHub.class, registryShutdownHub);

        validateContributeDefs(moduleDefs);

        scoreboardAndTracker.startup();

        SerializationSupport.setProvider(this);
    }

    /**
     * Validate that each module's ContributeDefs correspond to an actual service.
     */
    private void validateContributeDefs(Collection<ModuleDef> moduleDefs)
    {
        for (ModuleDef module : moduleDefs)
        {
            Set<ContributionDef> contributionDefs = module.getContributionDefs();

            for (ContributionDef cd : contributionDefs)
            {
                String serviceId = cd.getServiceId();

                ContributionDef2 cd2 = InternalUtils.toContributionDef2(cd);

                if (cd2.getServiceId() != null)
                {
                    if (!serviceIdToModule.containsKey(serviceId)) { throw new IllegalArgumentException(
                            IOCMessages.contributionForNonexistentService(cd)); }
                }
                else if (!isContributionForExistentService(module, cd2)) { throw new IllegalArgumentException(
                        IOCMessages.contributionForUnqualifiedService(cd2)); }
            }
        }

    }

    /**
     * Invoked when the contribution method didn't follow the naming convention and so doesn't identify
     * a service by id; instead there was an @Contribute to identify the service interface.
     */
    @SuppressWarnings("all")
    private boolean isContributionForExistentService(ModuleDef moduleDef, final ContributionDef2 cd)
    {
        final Set<Class> contributionMarkers = new HashSet(cd.getMarkers());

        boolean localOnly = contributionMarkers.contains(Local.class);

        Flow<ServiceDef2> serviceDefs = localOnly ? getLocalServiceDefs(moduleDef) : F.flow(allServiceDefs);

        contributionMarkers.retainAll(getMarkerAnnotations());
        contributionMarkers.remove(Local.class);

        // Match services with the correct interface AND having as markers *all* the marker annotations
       
        Flow<ServiceDef2> filtered = serviceDefs.filter(new Predicate<ServiceDef2>()
        {
            public boolean accept(ServiceDef2 object)
            {
                return object.getServiceInterface().equals(cd.getServiceInterface());
            }
        }.and(new Predicate<ServiceDef2>()
        {
            public boolean accept(ServiceDef2 serviceDef)
            {
                return serviceDef.getMarkers().containsAll(contributionMarkers);
            }
        }));

        // That's a lot of logic; the good news is it will short-circuit as soon as it finds a single match,
        // thanks to the laziness inside Flow.

        return !filtered.isEmpty();
    }

    private Flow<ServiceDef2> getLocalServiceDefs(final ModuleDef moduleDef)
    {
        return F.flow(moduleDef.getServiceIds()).map(new Mapper<String, ServiceDef2>()
        {
            public ServiceDef2 map(String value)
            {
                return InternalUtils.toServiceDef2(moduleDef.getServiceDef(value));
            }
        });
    }

    /**
     * It's not unreasonable for an eagerly-loaded service to decide to start a thread, at which
     * point we raise issues
     * about improper publishing of the Registry instance from the RegistryImpl constructor. Moving
     * eager loading of
     * services out to its own method should ensure thread safety.
     */
    public void performRegistryStartup()
    {
        eagerLoadLock.lock();

        List<EagerLoadServiceProxy> proxies = CollectionFactory.newList();

        for (Module m : moduleToServiceDefs.keySet())
            m.collectEagerLoadServices(proxies);

        // TAPESTRY-2267: Gather up all the proxies before instantiating any of them.

        for (EagerLoadServiceProxy proxy : proxies)
            proxy.eagerLoadService();

        getService("RegistryStartup", Runnable.class).run();

        cleanupThread();
    }

    public Logger getServiceLogger(String serviceId)
    {
        Module module = serviceIdToModule.get(serviceId);

        assert module != null;

        return loggerSource.getLogger(module.getLoggerName() + "." + serviceId);
    }

    private Logger loggerForBuiltinService(String serviceId)
    {
        return loggerSource.getLogger(TapestryIOCModule.class + "." + serviceId);
    }

    private <T> void addBuiltin(final String serviceId, final Class<T> serviceInterface, T service)
    {
        builtinTypes.put(serviceId, serviceInterface);
        builtinServices.put(serviceId, service);

        // Make sure each of the builtin services is also available via the Builtin annotation
        // marker.

        ServiceDef2 serviceDef = new ServiceDef2()
        {
            public ObjectCreator createServiceCreator(ServiceBuilderResources resources)
            {
                return null;
            }

            public Set<Class> getMarkers()
            {
                return BUILTIN;
            }

            public String getServiceId()
            {
                return serviceId;
            }

            public Class getServiceInterface()
            {
                return serviceInterface;
            }

            public String getServiceScope()
            {
                return ScopeConstants.DEFAULT;
            }

            public boolean isEagerLoad()
            {
                return false;
            }

            public boolean isPreventDecoration()
            {
                return true;
            }
        };

        for (Class marker : serviceDef.getMarkers())
        {
            InternalUtils.addToMapList(markerToServiceDef, marker, serviceDef);
            allServiceDefs.add(serviceDef);
        }

        tracker.define(serviceDef, Status.BUILTIN);
    }

    public synchronized void shutdown()
    {
        lock.lock();

        registryShutdownHub.fireRegistryDidShutdown();

        SerializationSupport.clearProvider(this);
    }

    public <T> T getService(String serviceId, Class<T> serviceInterface)
    {
        lock.check();

        T result = checkForBuiltinService(serviceId, serviceInterface);
        if (result != null)
            return result;

        // Checking serviceId and serviceInterface is overkill; they have been checked and rechecked
        // all the way to here.

        Module containingModule = locateModuleForService(serviceId);

        return containingModule.getService(serviceId, serviceInterface);
    }

    private <T> T checkForBuiltinService(String serviceId, Class<T> serviceInterface)
    {
        Object service = builtinServices.get(serviceId);

        if (service == null)
            return null;

        try
        {
            return serviceInterface.cast(service);
        }
        catch (ClassCastException ex)
        {
            throw new RuntimeException(IOCMessages.serviceWrongInterface(serviceId, builtinTypes.get(serviceId),
                    serviceInterface));
        }
    }

    public void cleanupThread()
    {
        lock.check();

        perthreadManager.cleanup();
    }

    private Module locateModuleForService(String serviceId)
    {
        Module module = serviceIdToModule.get(serviceId);

        if (module == null)
            throw new UnknownValueException(String.format("Service id '%s' is not defined by any module.", serviceId),
                    new AvailableValues("Defined service ids", serviceIdToModule));

        return module;
    }

    public <T> Collection<T> getUnorderedConfiguration(ServiceDef serviceDef, Class<T> objectType)
    {
        lock.check();

        final Collection<T> result = CollectionFactory.newList();

        for (Module m : moduleToServiceDefs.keySet())
            addToUnorderedConfiguration(result, objectType, serviceDef, m);

        return result;
    }

    @SuppressWarnings("unchecked")
    public <T> List<T> getOrderedConfiguration(ServiceDef serviceDef, Class<T> objectType)
    {
        lock.check();

        String serviceId = serviceDef.getServiceId();
        Logger logger = getServiceLogger(serviceId);

        Orderer<T> orderer = new Orderer<T>(logger);
        Map<String, OrderedConfigurationOverride<T>> overrides = CollectionFactory.newCaseInsensitiveMap();

        for (Module m : moduleToServiceDefs.keySet())
            addToOrderedConfiguration(orderer, overrides, objectType, serviceDef, m);

        // An ugly hack ... perhaps we should introduce a new builtin service so that this can be
        // accomplished in the normal way?

        if (serviceId.equals("MasterObjectProvider"))
        {
            ObjectProvider contribution = new ObjectProvider()
            {
                public <T> T provide(Class<T> objectType, AnnotationProvider annotationProvider, ObjectLocator locator)
                {
                    return findServiceByMarkerAndType(objectType, annotationProvider, null);
                }
            };

            orderer.add("ServiceByMarker", (T) contribution);
        }

        for (OrderedConfigurationOverride<T> override : overrides.values())
            override.apply();

        return orderer.getOrdered();
    }

    public <K, V> Map<K, V> getMappedConfiguration(ServiceDef serviceDef, Class<K> keyType, Class<V> objectType)
    {
        lock.check();

        // When the key type is String, then a case insensitive map is used.

        Map<K, V> result = newConfigurationMap(keyType);
        Map<K, ContributionDef> keyToContribution = newConfigurationMap(keyType);
        Map<K, MappedConfigurationOverride<K, V>> overrides = newConfigurationMap(keyType);

        for (Module m : moduleToServiceDefs.keySet())
            addToMappedConfiguration(result, overrides, keyToContribution, keyType, objectType, serviceDef, m);

        for (MappedConfigurationOverride<K, V> override : overrides.values())
        {
            override.apply();
        }

        return result;
    }

    @SuppressWarnings("unchecked")
    private <K, V> Map<K, V> newConfigurationMap(Class<K> keyType)
    {
        if (keyType.equals(String.class))
        {
            Map<String, K> result = CollectionFactory.newCaseInsensitiveMap();

            return (Map<K, V>) result;
        }

        return CollectionFactory.newMap();
    }

    private <K, V> void addToMappedConfiguration(Map<K, V> map, Map<K, MappedConfigurationOverride<K, V>> overrides,
            Map<K, ContributionDef> keyToContribution, Class<K> keyClass, Class<V> valueType, ServiceDef serviceDef,
            final Module module)
    {
        String serviceId = serviceDef.getServiceId();
        Set<ContributionDef2> contributions = module.getContributorDefsForService(serviceDef);

        if (contributions.isEmpty())
            return;

        Logger logger = getServiceLogger(serviceId);

        boolean debug = logger.isDebugEnabled();

        final ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, classFactory, logger);

        for (final ContributionDef def : contributions)
        {
            final MappedConfiguration<K, V> validating = new ValidatingMappedConfigurationWrapper<K, V>(valueType,
                    resources, map, overrides, serviceId, def, keyClass, keyToContribution);

            String description = IOCMessages.invokingMethod(def);

            if (debug)
                logger.debug(description);

            operationTracker.run(description, new Runnable()
            {
                public void run()
                {
                    def.contribute(module, resources, validating);
                }
            });
        }
    }

    private <T> void addToUnorderedConfiguration(Collection<T> collection, Class<T> valueType, ServiceDef serviceDef,
            final Module module)
    {
        String serviceId = serviceDef.getServiceId();
        Set<ContributionDef2> contributions = module.getContributorDefsForService(serviceDef);

        if (contributions.isEmpty())
            return;

        Logger logger = getServiceLogger(serviceId);

        boolean debug = logger.isDebugEnabled();

        final ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, classFactory, logger);

        for (final ContributionDef def : contributions)
        {
            final Configuration<T> validating = new ValidatingConfigurationWrapper<T>(valueType, resources, collection,
                    serviceId);

            String description = IOCMessages.invokingMethod(def);

            if (debug)
                logger.debug(description);

            operationTracker.run(description, new Runnable()
            {
                public void run()
                {
                    def.contribute(module, resources, validating);
                }
            });
        }
    }

    private <T> void addToOrderedConfiguration(Orderer<T> orderer,
            Map<String, OrderedConfigurationOverride<T>> overrides, Class<T> valueType, ServiceDef serviceDef,
            final Module module)
    {
        String serviceId = serviceDef.getServiceId();
        Set<ContributionDef2> contributions = module.getContributorDefsForService(serviceDef);

        if (contributions.isEmpty())
            return;

        Logger logger = getServiceLogger(serviceId);
        boolean debug = logger.isDebugEnabled();

        final ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, classFactory, logger);

        for (final ContributionDef def : contributions)
        {
            final OrderedConfiguration<T> validating = new ValidatingOrderedConfigurationWrapper<T>(valueType,
                    resources, orderer, overrides, def, serviceId);

            String description = IOCMessages.invokingMethod(def);

            if (debug)
                logger.debug(description);

            operationTracker.run(description, new Runnable()
            {
                public void run()
                {

                    def.contribute(module, resources, validating);
                }
            });
        }
    }

    public <T> T getService(Class<T> serviceInterface)
    {
        lock.check();

        List<String> serviceIds = findServiceIdsForInterface(serviceInterface);

        if (serviceIds == null)
            serviceIds = Collections.emptyList();

        switch (serviceIds.size())
        {
            case 0:

                throw new RuntimeException(IOCMessages.noServiceMatchesType(serviceInterface));

            case 1:

                String serviceId = serviceIds.get(0);

                return getService(serviceId, serviceInterface);

            default:

                Collections.sort(serviceIds);

                throw new RuntimeException(IOCMessages.manyServiceMatches(serviceInterface, serviceIds));
        }
    }

    private List<String> findServiceIdsForInterface(Class serviceInterface)
    {
        List<String> result = CollectionFactory.newList();

        for (Module module : moduleToServiceDefs.keySet())
            result.addAll(module.findServiceIdsForInterface(serviceInterface));

        for (Map.Entry<String, Object> entry : builtinServices.entrySet())
        {
            if (serviceInterface.isInstance(entry.getValue()))
                result.add(entry.getKey());
        }

        Collections.sort(result);

        return result;
    }

    public ServiceLifecycle2 getServiceLifecycle(String scope)
    {
        lock.check();

        ServiceLifecycle result = lifecycles.get(scope);

        if (result == null)
        {
            ServiceLifecycleSource source = getService("ServiceLifecycleSource", ServiceLifecycleSource.class);

            result = source.get(scope);
        }

        if (result == null)
            throw new RuntimeException(IOCMessages.unknownScope(scope));

        return InternalUtils.toServiceLifecycle2(result);
    }

    public List<ServiceDecorator> findDecoratorsForService(ServiceDef serviceDef)
    {
        lock.check();

        assert serviceDef != null;

        Logger logger = getServiceLogger(serviceDef.getServiceId());

        Orderer<ServiceDecorator> orderer = new Orderer<ServiceDecorator>(logger);

        for (Module module : moduleToServiceDefs.keySet())
        {
            Set<DecoratorDef> decoratorDefs = module.findMatchingDecoratorDefs(serviceDef);

            if (decoratorDefs.isEmpty())
                continue;

            ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, classFactory, logger);

            for (DecoratorDef decoratorDef : decoratorDefs)
            {
                ServiceDecorator decorator = decoratorDef.createDecorator(module, resources);

                orderer.add(decoratorDef.getDecoratorId(), decorator, decoratorDef.getConstraints());
            }
        }

        return orderer.getOrdered();
    }

    public List<ServiceAdvisor> findAdvisorsForService(ServiceDef serviceDef)
    {
        lock.check();

        assert serviceDef != null;

        Logger logger = getServiceLogger(serviceDef.getServiceId());

        Orderer<ServiceAdvisor> orderer = new Orderer<ServiceAdvisor>(logger);

        for (Module module : moduleToServiceDefs.keySet())
        {
            Set<AdvisorDef> advisorDefs = module.findMatchingServiceAdvisors(serviceDef);

            if (advisorDefs.isEmpty())
                continue;

            ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, classFactory, logger);

            for (AdvisorDef advisorDef : advisorDefs)
            {
                ServiceAdvisor advisor = advisorDef.createAdvisor(module, resources);

                orderer.add(advisorDef.getAdvisorId(), advisor, advisorDef.getConstraints());
            }
        }

        return orderer.getOrdered();
    }

    public ClassFab newClass(Class serviceInterface)
    {
        lock.check();

        return classFactory.newClass(serviceInterface);
    }

    public <T> T getObject(Class<T> objectType, AnnotationProvider annotationProvider, ObjectLocator locator,
            Module localModule)
    {
        lock.check();

        AnnotationProvider effectiveProvider = annotationProvider != null ? annotationProvider
                : new NullAnnotationProvider();

        // We do a check here for known marker/type combinations, so that you can use a marker
        // annotation
        // to inject into a contribution method that contributes to MasterObjectProvider.
        // We also force a contribution into MasterObjectProvider to accomplish the same thing.

        T result = findServiceByMarkerAndType(objectType, annotationProvider, localModule);

        if (result != null)
            return result;

        MasterObjectProvider masterProvider = getService(IOCConstants.MASTER_OBJECT_PROVIDER_SERVICE_ID,
                MasterObjectProvider.class);

        return masterProvider.provide(objectType, effectiveProvider, locator, true);
    }

    private Collection<ServiceDef2> filterByType(Class<?> objectType, Collection<ServiceDef2> serviceDefs)
    {
        Collection<ServiceDef2> result = CollectionFactory.newSet();

        for (ServiceDef2 sd : serviceDefs)
        {
            if (objectType.isAssignableFrom(sd.getServiceInterface()))
            {
                result.add(sd);
            }
        }

        return result;
    }

    @SuppressWarnings("unchecked")
    private <T> T findServiceByMarkerAndType(Class<T> objectType, AnnotationProvider provider, Module localModule)
    {
        if (provider == null)
            return null;

        boolean localOnly = localModule != null && provider.getAnnotation(Local.class) != null;

        Set<ServiceDef2> matches = CollectionFactory.newSet();

        matches.addAll(filterByType(objectType, localOnly ? moduleToServiceDefs.get(localModule) : allServiceDefs));

        List<Class> markers = CollectionFactory.newList();

        if (localOnly)
            markers.add(Local.class);

        for (Class marker : markerToServiceDef.keySet())
        {
            if (provider.getAnnotation(marker) == null)
                continue;

            markers.add(marker);

            matches = intersection(matches, markerToServiceDef.get(marker));
        }

        // If didn't see @Local or any recognized marker annotation, then don't try to filter that
        // way.
        // Continue on, eventually to the MasterObjectProvider service.

        if (markers.isEmpty())
            return null;

        switch (matches.size())
        {

            case 1:

                ServiceDef def = matches.iterator().next();

                return getService(def.getServiceId(), objectType);

            case 0:

                // It's no accident that the user put the marker annotation at the injection
                // point, since it matches a known marker annotation, it better be there for
                // a reason. So if we don't get a match, we have to assume the user expected
                // one, and that is an error.

                // This doesn't help when the user places an annotation they *think* is a marker
                // but isn't really a marker (because no service is marked by the annotation).

                throw new RuntimeException(IOCMessages.noServicesMatchMarker(objectType, markers));

            default:
                throw new RuntimeException(IOCMessages.manyServicesMatchMarker(objectType, markers, matches));
        }
    }

    /**
     * Filters the set into a new set, containing only elements shared between the set and the
     * filter collection.
     *
     * @param set
     *            to be filtered
     * @param filter
     *            values to keep from the set
     * @return a new set containing only the shared values
     */
    private static <T> Set<T> intersection(Set<T> set, Collection<T> filter)
    {
        if (set.isEmpty())
            return Collections.emptySet();

        Set<T> result = CollectionFactory.newSet();

        for (T elem : filter)
        {
            if (set.contains(elem))
                result.add(elem);
        }

        return result;
    }

    public <T> T getObject(Class<T> objectType, AnnotationProvider annotationProvider)
    {
        return getObject(objectType, annotationProvider, this, null);
    }

    public void addRegistryShutdownListener(RegistryShutdownListener listener)
    {
        lock.check();

        registryShutdownHub.addRegistryShutdownListener(listener);
    }

    public String expandSymbols(String input)
    {
        lock.check();

        // Again, a bit of work to avoid instantiating the SymbolSource until absolutely necessary.

        if (!InternalUtils.containsSymbols(input))
            return input;

        return getSymbolSource().expandSymbols(input);
    }

    /**
     * Defers obtaining the symbol source until actually needed.
     */
    private synchronized SymbolSource getSymbolSource()
    {
        if (symbolSource == null)
            symbolSource = getService(SYMBOL_SOURCE_SERVICE_ID, SymbolSource.class);

        return symbolSource;
    }

    public <T> T autobuild(String description, final Class<T> clazz)
    {
        return invoke(description, new Invokable<T>()
        {
            public T invoke()
            {
                return autobuild(clazz);
            }
        });
    }

    public <T> T autobuild(final Class<T> clazz)
    {
        assert clazz != null;
        final Constructor constructor = InternalUtils.findAutobuildConstructor(clazz);

        if (constructor == null)
            throw new RuntimeException(IOCMessages.noAutobuildConstructor(clazz));

        Map<Class, Object> resourcesMap = CollectionFactory.newMap();
        resourcesMap.put(OperationTracker.class, RegistryImpl.this);

        final InjectionResources resources = new MapInjectionResources(resourcesMap);

        final Invokable<T> operation = new Invokable<T>()
        {
            public T invoke()
            {
                InternalUtils.validateConstructorForAutobuild(constructor);

                Object result = invokeConstructor(constructor, resources);

                InternalUtils.injectIntoFields(result, RegistryImpl.this, resources, RegistryImpl.this);

                return clazz.cast(result);
            }
        };

        T result = invoke("Autobuilding instance of class " + clazz.getName(), operation);

        InternalUtils.invokePostInjectionMethods(result, this, resources, this);

        return result;
    }

    public <T> T proxy(Class<T> interfaceClass, Class<? extends T> implementationClass)
    {
        return proxy(interfaceClass, implementationClass, this);
    }

    public <T> T proxy(Class<T> interfaceClass, Class<? extends T> implementationClass, ObjectLocator locator)
    {
        assert interfaceClass != null;
        assert implementationClass != null;
       
        if (InternalUtils.SERVICE_CLASS_RELOADING_ENABLED && InternalUtils.isLocalFile(implementationClass))
            return createReloadingProxy(interfaceClass, implementationClass, locator);

        return createNonReloadingProxy(interfaceClass, implementationClass, locator);
    }

    private <T> T createNonReloadingProxy(Class<T> interfaceClass, final Class<? extends T> implementationClass,
            final ObjectLocator locator)
    {
        final ObjectCreator autobuildCreator = new ObjectCreator()
        {
            public Object createObject()
            {
                return locator.autobuild(implementationClass);
            }
        };

        ObjectCreator justInTime = new ObjectCreator()
        {
            private Object delegate;

            public synchronized Object createObject()
            {
                if (delegate == null)
                    delegate = autobuildCreator.createObject();

                return delegate;
            }
        };

        return classFactory.createProxy(interfaceClass, implementationClass, justInTime,
                String.format("<Autobuild proxy %s(%s)>", implementationClass.getName(), interfaceClass.getName()));
    }

    private <T> T createReloadingProxy(Class<T> interfaceClass, final Class<? extends T> implementationClass,
            ObjectLocator locator)
    {
        ReloadableObjectCreator creator = new ReloadableObjectCreator(implementationClass.getClassLoader(),
                implementationClass.getName(), loggerSource.getLogger(implementationClass), this, locator);

        getService(UpdateListenerHub.class).addUpdateListener(creator);

        return classFactory.createProxy(interfaceClass, implementationClass, creator,
                String.format("<Autoreload proxy %s(%s)>", implementationClass.getName(), interfaceClass.getName()));
    }

    public Object provideServiceProxy(String serviceId)
    {
        return getService(serviceId, Object.class);
    }

    public void run(String description, Runnable operation)
    {
        operationTracker.run(description, operation);
    }

    public <T> T invoke(String description, Invokable<T> operation)
    {
        return operationTracker.invoke(description, operation);
    }

    private Object invokeConstructor(final Constructor constructor, final InjectionResources resources)
    {
        final String description = classFactory.getConstructorLocation(constructor).toString();

        return invoke("Invoking " + description, new Invokable<Object>()
        {
            public Object invoke()
            {
                Throwable failure;

                try
                {
                    Object[] parameters = InternalUtils.calculateParametersForConstructor(constructor,
                            RegistryImpl.this, resources, RegistryImpl.this);

                    return constructor.newInstance(parameters);
                }
                catch (InvocationTargetException ite)
                {
                    failure = ite.getTargetException();
                }
                catch (Exception ex)
                {
                    failure = ex;
                }

                throw new RuntimeException(IOCMessages.autobuildConstructorError(description, failure), failure);
            }
        });
    }

    public Set<Class> getMarkerAnnotations()
    {
        return markerToServiceDef.keySet();
    }

}
TOP

Related Classes of org.apache.tapestry5.ioc.internal.RegistryImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.