/*
* Copyright 2010 the original author or authors.
*
* 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.gradle.internal.service;
import org.gradle.api.Action;
import org.gradle.api.Nullable;
import org.gradle.api.specs.Spec;
import org.gradle.internal.Factory;
import org.gradle.internal.concurrent.CompositeStoppable;
import org.gradle.internal.concurrent.Stoppable;
import org.gradle.internal.util.BiFunction;
import java.io.Closeable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Formatter;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* A hierarchical {@link ServiceRegistry} implementation.
*
* <p>Subclasses can register services by:</p>
*
* <li>Calling {@link #add(Class, Object)} to register a service instance.</li>
*
* <li>Calling {@link #addProvider(Object)} to register a service provider bean. A provider bean may have factory, decorator and configuration methods as described below.</li>
*
* <li>Adding a factory method. A factory method should have a name that starts with 'create', and have a non-void return type. For example, <code>protected SomeService createSomeService() { ....
* }</code>. Parameters are injected using services from this registry or its parents. Parameter of type {@link ServiceRegistry} will receive the service registry that owns the service. Parameter ot
* type {@code List<T>} will receive all services of type T, if any. Note that factory methods with a single parameter and an return type equal to that parameter type are interpreted as decorator
* methods.</li>
*
* <li>Adding a decorator method. A decorator method should have a name that starts with 'decorate', take a single parameter, and a have return type equal to the parameter type. Before invoking the
* method, the parameter is located in the parent service registry and then passed to the method.</li>
*
* <li>Adding a configure method. A configure method should be called 'configure', take a {@link ServiceRegistration} parameter, and a have a void return type. Additional parameters are injected using
* services from this registry or its parents.</li>
*
* </ul>
*
* <p>Service instances are created on demand. {@link #getFactory(Class)} looks for a service instance which implements {@code Factory<T>} where {@code T} is the expected type.</p>
*
* <p>Service instances and factories are closed when the registry that created them is closed using {@link #close()}. If a service instance or factory implements {@link java.io.Closeable} or {@link
* org.gradle.internal.concurrent.Stoppable} then the appropriate close() or stop() method is called. Instances are closed in reverse dependency order.</p>
*
* <p>Service registries are arranged in a hierarchy. If a service of a given type cannot be located, the registry uses its parent registry, if any, to locate the service.</p>
*/
public class DefaultServiceRegistry implements ServiceRegistry, Closeable {
private final static ServiceRegistry[] NO_PARENTS = new ServiceRegistry[0];
private final static ServiceProvider[] NO_DEPENDENTS = new ServiceProvider[0];
private final static Object[] NO_PARAMS = new Object[0];
private static final ConcurrentMap<Type, BiFunction<ServiceProvider, LookupContext, Provider>> SERVICE_TYPE_PROVIDER_CACHE = new ConcurrentHashMap<Type, BiFunction<ServiceProvider, LookupContext, Provider>>();
private final Map<Type, ServiceProvider> providerCache = new IdentityHashMap<Type, ServiceProvider>();
private final Object lock = new Object();
private final OwnServices ownServices;
private final Provider allServices;
private final Provider parentServices;
private final String displayName;
private boolean closed;
private boolean mutable = true; // access under lock
private Provider asParentServicesProvider;
public DefaultServiceRegistry() {
this(null, NO_PARENTS);
}
public DefaultServiceRegistry(String displayName) {
this(displayName, NO_PARENTS);
}
public DefaultServiceRegistry(ServiceRegistry... parents) {
this(null, parents);
}
public DefaultServiceRegistry(String displayName, ServiceRegistry... parents) {
this.displayName = displayName;
this.ownServices = new OwnServices();
if (parents.length == 0) {
this.parentServices = null;
this.allServices = CachingProvider.of(ownServices);
} else {
parentServices = setupParentServices(parents);
allServices = new CompositeProvider(CachingProvider.of(ownServices), parentServices);
}
findProviderMethods(this);
}
private static Provider setupParentServices(ServiceRegistry[] parents) {
Provider parentServices;
if (parents.length == 1) {
parentServices = toParentServices(parents[0]);
} else {
Provider[] parentProviders = new Provider[parents.length];
for (int i = 0; i < parents.length; i++) {
parentProviders[i] = toParentServices(parents[i]);
}
parentServices = new CompositeProvider(parentProviders);
}
return parentServices;
}
private Provider asProvider() {
if (asParentServicesProvider == null) {
asParentServicesProvider = CachingProvider.of(new ParentServices(this));
}
return asParentServicesProvider;
}
private static Provider toParentServices(ServiceRegistry serviceRegistry) {
if (serviceRegistry instanceof DefaultServiceRegistry) {
return ((DefaultServiceRegistry) serviceRegistry).asProvider();
}
return new ParentServices(serviceRegistry);
}
/**
* Creates a service registry that uses the given providers.
*/
public static ServiceRegistry create(Object... providers) {
DefaultServiceRegistry registry = new DefaultServiceRegistry();
for (Object provider : providers) {
registry.addProvider(provider);
}
return registry;
}
private String getDisplayName() {
return displayName == null ? getClass().getSimpleName() : displayName;
}
@Override
public String toString() {
return getDisplayName();
}
private void findProviderMethods(Object target) {
Class<?> type = target.getClass();
RelevantMethods methods = RelevantMethods.getMethods(type);
for (ServiceMethod method : methods.decorators) {
if (parentServices == null) {
throw new ServiceLookupException(String.format("Cannot use decorator method %s.%s() when no parent registry is provided.", type.getSimpleName(), method.getName()));
}
ownServices.add(new DecoratorMethodService(target, method));
}
for (ServiceMethod method : methods.factories) {
ownServices.add(new FactoryMethodService(target, method));
}
for (ServiceMethod method : methods.configurers) {
applyConfigureMethod(method, target);
}
}
private void applyConfigureMethod(ServiceMethod method, Object target) {
Object[] params = new Object[method.getParameterTypes().length];
DefaultLookupContext context = new DefaultLookupContext();
for (int i = 0; i < method.getParameterTypes().length; i++) {
Type paramType = method.getParameterTypes()[i];
if (paramType.equals(ServiceRegistration.class)) {
params[i] = newRegistration();
} else {
ServiceProvider paramProvider = context.find(paramType, allServices);
if (paramProvider == null) {
throw new ServiceLookupException(String.format("Cannot configure services using %s.%s() as required service of type %s is not available.",
method.getOwner().getSimpleName(),
method.getName(),
format(paramType)));
}
params[i] = paramProvider.get();
}
}
try {
method.invoke(target, params);
} catch (Exception e) {
throw new ServiceLookupException(String.format("Could not configure services using %s.%s().",
method.getOwner().getSimpleName(),
method.getName()), e);
}
}
/**
* Adds services to this container using the given action.
*/
public void register(Action<? super ServiceRegistration> action) {
assertMutable();
action.execute(newRegistration());
}
private void assertMutable() {
if (!mutable) {
throw new IllegalStateException("Cannot add provide to service registry " + this + " as it is no longer mutable");
}
}
private ServiceRegistration newRegistration() {
return new ServiceRegistration() {
public <T> void add(Class<T> serviceType, T serviceInstance) {
DefaultServiceRegistry.this.add(serviceType, serviceInstance);
}
public void add(Class<?> serviceType) {
ownServices.add(new ConstructorService(serviceType));
}
public void addProvider(Object provider) {
DefaultServiceRegistry.this.addProvider(provider);
}
};
}
/**
* Adds a service to this registry. The given object is closed when this registry is closed.
*/
public <T> DefaultServiceRegistry add(Class<T> serviceType, final T serviceInstance) {
assertMutable();
ownServices.add(new FixedInstanceService<T>(serviceType, serviceInstance));
return this;
}
/**
* Adds a service provider bean to this registry. This provider may define factory and decorator methods.
*/
public DefaultServiceRegistry addProvider(Object provider) {
assertMutable();
findProviderMethods(provider);
return this;
}
/**
* Closes all services for this registry. For each service, if the service has a public void close() or stop() method, that method is called to close the service.
*/
public void close() {
synchronized (lock) {
try {
CompositeStoppable.stoppable(allServices).stop();
} finally {
closed = true;
}
}
}
public boolean isClosed() {
return closed;
}
private static String format(Type type) {
if (type instanceof Class) {
Class<?> aClass = (Class) type;
return aClass.getSimpleName();
} else if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
StringBuilder builder = new StringBuilder();
builder.append(format(parameterizedType.getRawType()));
builder.append("<");
for (int i = 0; i < parameterizedType.getActualTypeArguments().length; i++) {
Type typeParam = parameterizedType.getActualTypeArguments()[i];
if (i > 0) {
builder.append(", ");
}
builder.append(format(typeParam));
}
builder.append(">");
return builder.toString();
}
return type.toString();
}
public boolean hasService(Class<?> serviceType) {
assertValidServiceType(serviceType);
return allServices.hasService(serviceType);
}
public <T> List<T> getAll(Class<T> serviceType) throws ServiceLookupException {
synchronized (lock) {
mutable = false;
if (closed) {
throw new IllegalStateException(String.format("Cannot locate service of type %s, as %s has been closed.", format(serviceType), getDisplayName()));
}
if (!hasService(serviceType)) {
return Collections.emptyList();
}
List<ServiceProvider> providers = new ArrayList<ServiceProvider>();
DefaultLookupContext context = new DefaultLookupContext();
allServices.getAll(context, serviceType, providers);
List<T> services = new ArrayList<T>(providers.size());
for (ServiceProvider provider : providers) {
services.add(serviceType.cast(provider.get()));
}
return services;
}
}
public <T> T get(Class<T> serviceType) throws UnknownServiceException, ServiceLookupException {
return serviceType.cast(doGet(serviceType));
}
public Object get(Type serviceType) throws UnknownServiceException, ServiceLookupException {
return doGet(serviceType);
}
private Object doGet(Type serviceType) throws IllegalArgumentException {
synchronized (lock) {
mutable = false;
if (closed) {
throw new IllegalStateException(String.format("Cannot locate service of type %s, as %s has been closed.", format(serviceType), getDisplayName()));
}
ServiceProvider provider = providerCache.get(serviceType);
if (provider == null) {
provider = getServiceProvider(serviceType);
providerCache.put(serviceType, provider);
}
return provider.get();
}
}
private ServiceProvider getServiceProvider(Type serviceType) {
Type lookupType = extractServiceType(serviceType);
ServiceProvider provider = hasService(unwrap(lookupType)) ? new DefaultLookupContext().find(serviceType, allServices) : null;
if (provider == null) {
throw new UnknownServiceException(serviceType, String.format("No service of type %s available in %s.", format(serviceType), getDisplayName()));
}
return provider;
}
private static Type extractServiceType(Type mayBeFactoryType) {
Type serviceType = mayBeFactoryType;
if (mayBeFactoryType instanceof ParameterizedType) {
Type rawType = ((ParameterizedType) mayBeFactoryType).getRawType();
if (rawType == List.class) {
serviceType = ((ParameterizedType) mayBeFactoryType).getActualTypeArguments()[0];
}
}
return serviceType;
}
public <T> Factory<T> getFactory(Class<T> type) {
synchronized (lock) {
if (closed) {
throw new IllegalStateException(String.format("Cannot locate factory for objects of type %s, as %s has been closed.", format(type), getDisplayName()));
}
if (hasService(Factory.class)) {
DefaultLookupContext context = new DefaultLookupContext();
ServiceProvider factory = allServices.getFactory(context, type);
if (factory != null) {
return (Factory<T>) factory.get();
}
}
throw new UnknownServiceException(type, String.format("No factory for objects of type %s available in %s.", format(type), getDisplayName()));
}
}
public <T> T newInstance(Class<T> type) {
return getFactory(type).create();
}
/**
* Provides a single service instance.
*/
interface ServiceProvider {
String getDisplayName();
Object get();
void requiredBy(Provider provider);
}
/**
* Provides a set or zero or more services.
*/
interface Provider extends Stoppable {
/**
* Locates a service instance of the given type. Returns null if this provider does not provide a service of this type.
*/
ServiceProvider getService(LookupContext context, TypeSpec serviceType);
/**
* Locates a factory for services of the given type. Returns null if this provider does not provide any services of this type.
*/
ServiceProvider getFactory(LookupContext context, Class<?> type);
/**
* Collects all services of the given type.
*/
void getAll(LookupContext context, Class<?> serviceType, List<ServiceProvider> result);
boolean hasService(Class<?> type);
}
private class OwnServices implements Provider {
private List<Provider> providers;
private Set<Class<?>> serviceTypes;
@Override
public ServiceProvider getFactory(LookupContext context, Class<?> type) {
if (!hasService(Factory.class)) {
return null;
}
List<ServiceProvider> candidates = new ArrayList<ServiceProvider>();
for (Provider provider : providers) {
ServiceProvider factory = provider.getFactory(context, type);
if (factory != null) {
candidates.add(factory);
}
}
if (candidates.size() == 0) {
return null;
}
if (candidates.size() == 1) {
return candidates.get(0);
}
Set<String> descriptions = new TreeSet<String>();
for (ServiceProvider candidate : candidates) {
descriptions.add(candidate.getDisplayName());
}
Formatter formatter = new Formatter();
formatter.format("Multiple factories for objects of type %s available in %s:", format(type), getDisplayName());
for (String description : descriptions) {
formatter.format("%n - %s", description);
}
throw new ServiceLookupException(formatter.toString());
}
@Override
public ServiceProvider getService(LookupContext context, TypeSpec serviceType) {
if (!hasService(unwrap(serviceType.getType()))) {
return null;
}
ServiceProvider singleCandidate = null;
List<ServiceProvider> candidates = null;
for (Provider provider : providers) {
ServiceProvider service = provider.getService(context, serviceType);
if (service != null) {
if (singleCandidate == null) {
singleCandidate = service;
} else {
if (candidates == null) {
candidates = new ArrayList<ServiceProvider>(2);
candidates.add(singleCandidate);
}
candidates.add(service);
}
}
}
if (candidates == null && singleCandidate == null) {
return null;
}
if (candidates == null) {
return singleCandidate;
}
Set<String> descriptions = new TreeSet<String>();
for (ServiceProvider candidate : candidates) {
descriptions.add(candidate.getDisplayName());
}
Formatter formatter = new Formatter();
formatter.format("Multiple services of type %s available in %s:", format(serviceType.getType()), getDisplayName());
for (String description : descriptions) {
formatter.format("%n - %s", description);
}
throw new ServiceLookupException(formatter.toString());
}
@Override
public void getAll(LookupContext context, Class<?> serviceType, List<ServiceProvider> result) {
if (!hasService(serviceType)) {
return;
}
for (Provider provider : providers) {
provider.getAll(context, serviceType, result);
}
}
public boolean hasService(Class<?> serviceType) {
return serviceTypes != null && serviceTypes.contains(serviceType);
}
@Override
public void stop() {
if (providers == null) {
return;
}
CompositeStoppable.stoppable(providers).stop();
}
public void add(Provider provider) {
assertMutable();
if (providers == null) {
providers = new ArrayList<Provider>();
serviceTypes = new HashSet<Class<?>>();
serviceTypes.add(Object.class);
}
this.providers.add(provider);
if (provider instanceof SingletonService) {
addServiceType(((SingletonService) provider).serviceClass);
} else {
throw new UnsupportedOperationException("Unsupported service provider type: " + provider);
}
}
private void addServiceType(Class<?> serviceType) {
if (serviceType!=null && serviceTypes.add(serviceType)) {
addServiceType(serviceType.getSuperclass());
Class<?>[] interfaces = serviceType.getInterfaces();
if (interfaces!=null) {
for (Class<?> intf : interfaces) {
addServiceType(intf);
}
}
}
}
}
private static Class<?> unwrap(Type type) {
if (type instanceof Class) {
return (Class) type;
} else {
if (type instanceof WildcardType) {
final WildcardType wildcardType = (WildcardType) type;
if (wildcardType.getUpperBounds()[0] instanceof Class && wildcardType.getLowerBounds().length == 0) {
return (Class<?>) wildcardType.getUpperBounds()[0];
}
}
ParameterizedType parameterizedType = (ParameterizedType) type;
return (Class) parameterizedType.getRawType();
}
}
private static abstract class ManagedObjectProvider<T> implements Provider {
private T instance;
private Set<Provider> dependents;
protected void setInstance(T instance) {
this.instance = instance;
}
public T getInstance() {
if (instance == null) {
instance = create();
assert instance != null : String.format("create() of %s returned null", toString());
}
return instance;
}
protected abstract T create();
public void requiredBy(Provider provider) {
if (dependents == null) {
dependents = new HashSet<Provider>();
}
dependents.add(provider);
}
public void stop() {
try {
if (instance != null) {
CompositeStoppable.stoppable(dependents == null ? Collections.emptyList() : dependents).add(instance).stop();
}
} finally {
if (dependents != null) {
dependents.clear();
}
instance = null;
}
}
}
private static abstract class SingletonService extends ManagedObjectProvider<Object> implements ServiceProvider {
final Type serviceType;
final Class serviceClass;
// cached for performance
Class factoryElementType;
boolean bound;
SingletonService(Type serviceType) {
this.serviceType = serviceType;
serviceClass = unwrap(serviceType);
}
@Override
public String toString() {
return getDisplayName();
}
@Override
public Object get() {
return getInstance();
}
private ServiceProvider prepare(LookupContext context) {
if (!bound) {
bind(context);
bound = true;
}
return this;
}
protected void bind(LookupContext context) {
}
@Override
public ServiceProvider getService(LookupContext context, TypeSpec serviceType) {
if (!serviceType.isSatisfiedBy(this.serviceType)) {
return null;
}
return prepare(context);
}
@Override
public void getAll(LookupContext context, Class<?> serviceType, List<ServiceProvider> result) {
if (serviceType.isAssignableFrom(this.serviceClass)) {
result.add(prepare(context));
}
}
@Override
public ServiceProvider getFactory(LookupContext context, Class<?> elementType) {
if (!isFactory(serviceType, elementType)) {
return null;
}
return prepare(context);
}
private boolean isFactory(Type type, Class<?> elementType) {
Class c = unwrap(type);
if (!Factory.class.isAssignableFrom(c)) {
return false;
}
if (factoryElementType != null) {
return elementType.isAssignableFrom(factoryElementType);
}
if (type instanceof ParameterizedType) {
// Check if type is Factory<? extends ElementType>
ParameterizedType parameterizedType = (ParameterizedType) type;
if (parameterizedType.getRawType().equals(Factory.class)) {
Type actualType = parameterizedType.getActualTypeArguments()[0];
if (actualType instanceof Class) {
factoryElementType = (Class) actualType;
return elementType.isAssignableFrom((Class<?>) actualType);
}
}
}
// Check if type extends Factory<? extends ElementType>
for (Type interfaceType : c.getGenericInterfaces()) {
if (isFactory(interfaceType, elementType)) {
return true;
}
}
return false;
}
@Override
public boolean hasService(Class<?> type) {
return type.isAssignableFrom(serviceClass);
}
}
private abstract class FactoryService extends SingletonService {
private ServiceProvider[] paramProviders;
protected FactoryService(Type serviceType) {
super(serviceType);
}
protected abstract Type[] getParameterTypes();
protected abstract Member getFactory();
@Override
protected void bind(LookupContext context) {
Type[] parameterTypes = getParameterTypes();
if (parameterTypes.length==0) {
paramProviders = NO_DEPENDENTS;
return;
}
paramProviders = new ServiceProvider[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
Type paramType = parameterTypes[i];
try {
if (paramType.equals(ServiceRegistry.class)) {
paramProviders[i] = getThisAsProvider();
} else {
ServiceProvider paramProvider = context.find(paramType, allServices);
if (paramProvider == null) {
throw new ServiceCreationException(String.format("Cannot create service of type %s using %s.%s() as required service of type %s is not available.",
format(serviceType),
getFactory().getDeclaringClass().getSimpleName(),
getFactory().getName(),
format(paramType)));
}
paramProviders[i] = paramProvider;
paramProvider.requiredBy(this);
}
} catch (ServiceValidationException e) {
throw new ServiceCreationException(String.format("Cannot create service of type %s using %s.%s() as there is a problem with parameter #%s of type %s.",
format(serviceType),
getFactory().getDeclaringClass().getSimpleName(),
getFactory().getName(),
i + 1,
format(paramType)), e);
}
}
}
@Override
protected Object create() {
Object[] params = assembleParameters();
Object result = invokeMethod(params);
// Can discard the state required to create instance
paramProviders = null;
return result;
}
private Object[] assembleParameters() {
if (paramProviders == NO_DEPENDENTS) {
return NO_PARAMS;
}
Object[] params = new Object[paramProviders.length];
for (int i = 0; i < paramProviders.length; i++) {
ServiceProvider paramProvider = paramProviders[i];
params[i] = paramProvider.get();
}
return params;
}
protected abstract Object invokeMethod(Object[] params);
}
private class FactoryMethodService extends FactoryService {
private final ServiceMethod method;
private Object target;
public FactoryMethodService(Object target, ServiceMethod method) {
super(method.getServiceType());
this.target = target;
this.method = method;
}
public String getDisplayName() {
return "Service " + format(method.getServiceType()) + " at " + method.getOwner().getSimpleName() + "." + method.getName() + "()";
}
protected Type[] getParameterTypes() {
return method.getParameterTypes();
}
@Override
protected Member getFactory() {
return method.getMethod();
}
protected Object invokeMethod(Object[] params) {
Object result;
try {
result = method.invoke(target, params);
} catch (Exception e) {
throw new ServiceCreationException(String.format("Could not create service of type %s using %s.%s().",
format(serviceType),
method.getOwner().getSimpleName(),
method.getName()),
e);
}
try {
if (result == null) {
throw new ServiceCreationException(String.format("Could not create service of type %s using %s.%s() as this method returned null.",
format(serviceType),
method.getOwner().getSimpleName(),
method.getName()));
}
return result;
} finally {
// Can discard the state required to create instance
target = null;
}
}
}
private ServiceProvider getThisAsProvider() {
return new ServiceProvider() {
public String getDisplayName() {
return "ServiceRegistry " + DefaultServiceRegistry.this.getDisplayName();
}
public Object get() {
return DefaultServiceRegistry.this;
}
public void requiredBy(Provider provider) {
}
};
}
private static class FixedInstanceService<T> extends SingletonService {
public FixedInstanceService(Class<T> serviceType, T serviceInstance) {
super(serviceType);
setInstance(serviceInstance);
}
public String getDisplayName() {
return "Service " + format(serviceType) + " with implementation " + getInstance();
}
@Override
protected Object create() {
throw new UnsupportedOperationException();
}
}
private class ConstructorService extends FactoryService {
private final Constructor<?> constructor;
private ConstructorService(Class<?> serviceType) {
super(serviceType);
Constructor<?>[] constructors = serviceType.getDeclaredConstructors();
if (constructors.length != 1) {
throw new ServiceValidationException(String.format("Expected a single constructor for %s.", format(serviceType)));
}
this.constructor = constructors[0];
}
@Override
protected Type[] getParameterTypes() {
return constructor.getGenericParameterTypes();
}
@Override
protected Member getFactory() {
return constructor;
}
@Override
protected Object invokeMethod(Object[] params) {
try {
return constructor.newInstance(params);
} catch (InvocationTargetException e) {
throw new ServiceCreationException(String.format("Could not create service of type %s.", format(serviceType)), e.getCause());
} catch (Exception e) {
throw new ServiceCreationException(String.format("Could not create service of type %s.", format(serviceType)), e);
}
}
public String getDisplayName() {
return "Service " + format(serviceType);
}
}
private class DecoratorMethodService extends SingletonService {
private final ServiceMethod method;
private Object target;
private ServiceProvider paramProvider;
public DecoratorMethodService(Object target, ServiceMethod method) {
super(method.getServiceType());
this.target = target;
this.method = method;
}
public String getDisplayName() {
return "Service " + format(method.getServiceType()) + " at " + method.getOwner().getSimpleName() + "." + method.getName() + "()";
}
@Override
protected void bind(LookupContext context) {
Type paramType = method.getParameterTypes()[0];
DefaultLookupContext parentLookupContext = new DefaultLookupContext();
paramProvider = parentLookupContext.find(paramType, parentServices);
if (paramProvider == null) {
throw new ServiceCreationException(String.format("Cannot create service of type %s using %s.%s() as required service of type %s is not available in parent registries.",
format(method.getServiceType()),
method.getOwner().getSimpleName(),
method.getName(),
format(paramType)));
}
}
@Override
protected Object create() {
Object param = paramProvider.get();
Object result;
try {
result = method.invoke(target, param);
} catch (Exception e) {
throw new ServiceCreationException(String.format("Could not create service of type %s using %s.%s().",
format(method.getServiceType()),
method.getOwner().getSimpleName(),
method.getName()),
e);
}
try {
if (result == null) {
throw new ServiceCreationException(String.format("Could not create service of type %s using %s.%s() as this method returned null.",
format(method.getServiceType()),
method.getOwner().getSimpleName(),
method.getName()));
}
return result;
} finally {
// Can discard state required to create instance
paramProvider = null;
target = null;
}
}
}
private static class CachingProvider implements Provider {
private static final Object ABSENT = new Object();
private final ConcurrentMap<Object, Object> seen = new ConcurrentHashMap<Object, Object>();
private final ConcurrentMap<Class<?>, List<ServiceProvider>> allServicesCache = new ConcurrentHashMap<Class<?>, List<ServiceProvider>>();
private final Map<Class<?>, Boolean> serviceTypes = new ConcurrentHashMap<Class<?>, Boolean>();
private final Provider delegate;
private CachingProvider(Provider delegate) {
this.delegate = delegate;
}
private static Provider of(Provider delegate) {
if (delegate instanceof CachingProvider) {
return delegate;
}
return new CachingProvider(delegate);
}
@Override
public ServiceProvider getService(LookupContext context, TypeSpec serviceType) {
Object cached = seen.get(serviceType);
if (cached != null) {
return cached == ABSENT ? null : (ServiceProvider) cached;
}
ServiceProvider service = delegate.getService(context, serviceType);
return cacheServiceProvider(serviceType, service);
}
private ServiceProvider cacheServiceProvider(Object key, ServiceProvider service) {
seen.putIfAbsent(key, service == null ? ABSENT : service);
return service;
}
@Override
public ServiceProvider getFactory(LookupContext context, Class<?> type) {
Object cached = seen.get(type);
if (cached != null) {
return cached == ABSENT ? null : (ServiceProvider) cached;
}
ServiceProvider service = delegate.getFactory(context, type);
return cacheServiceProvider(type, service);
}
@Override
public void getAll(LookupContext context, Class<?> serviceType, List<ServiceProvider> result) {
List<ServiceProvider> services = allServicesCache.get(serviceType);
if (services != null) {
result.addAll(services);
return;
}
ArrayList<ServiceProvider> tmp = new ArrayList<ServiceProvider>();
delegate.getAll(context, serviceType, tmp);
allServicesCache.putIfAbsent(serviceType, tmp);
if (!tmp.isEmpty()) {
result.addAll(tmp);
}
}
@Override
public boolean hasService(Class<?> type) {
Boolean val = serviceTypes.get(type);
if (val != null) {
return val;
}
val = delegate.hasService(type);
serviceTypes.put(type, val);
return val;
}
@Override
public void stop() {
delegate.stop();
seen.clear();
allServicesCache.clear();
}
}
private static class CompositeProvider implements Provider {
private final Provider[] providers;
private CompositeProvider(Provider... providers) {
this.providers = providers;
}
@Override
public ServiceProvider getService(LookupContext context, TypeSpec serviceType) {
if (hasService(unwrap(extractServiceType(serviceType.getType())))) {
for (Provider provider : providers) {
ServiceProvider service = provider.getService(context, serviceType);
if (service != null) {
return service;
}
}
}
return null;
}
@Override
public ServiceProvider getFactory(LookupContext context, Class<?> type) {
if (hasService(Factory.class)) {
for (Provider provider : providers) {
ServiceProvider factory = provider.getFactory(context, type);
if (factory != null) {
return factory;
}
}
}
return null;
}
@Override
public void getAll(LookupContext context, Class<?> serviceType, List<ServiceProvider> result) {
if (hasService(serviceType)) {
for (Provider provider : providers) {
provider.getAll(context, serviceType, result);
}
}
}
@Override
public boolean hasService(Class<?> type) {
for (Provider provider : providers) {
if (provider.hasService(type)) {
return true;
}
}
return false;
}
@Override
public void stop() {
try {
CompositeStoppable.stoppable(Arrays.asList(providers)).stop();
} finally {
for (int i = 0; i < providers.length; i++) {
providers[i] = null;
}
}
}
}
private static class ParentServices implements Provider {
private final ServiceRegistry parent;
private ParentServices(ServiceRegistry parent) {
this.parent = parent;
}
@Override
public ServiceProvider getFactory(LookupContext context, Class<?> type) {
try {
Factory<?> factory = parent.getFactory(type);
assert factory != null : String.format("parent returned null for factory type '%s'", type.getName());
return wrap(factory);
} catch (UnknownServiceException e) {
if (!e.getType().equals(type)) {
throw e;
}
}
return null;
}
@Override
public ServiceProvider getService(LookupContext context, TypeSpec serviceType) {
try {
Object service = parent.get(serviceType.getType());
assert service != null : String.format("parent returned null for service type %s", format(serviceType.getType()));
return wrap(service);
} catch (UnknownServiceException e) {
if (!e.getType().equals(serviceType.getType())) {
throw e;
}
}
return null;
}
private ServiceProvider wrap(final Object instance) {
return new ServiceProvider() {
public String getDisplayName() {
return "ServiceRegistry " + parent;
}
public Object get() {
return instance;
}
public void requiredBy(Provider provider) {
// Ignore
}
};
}
@Override
public void getAll(LookupContext context, Class<?> serviceType, List<ServiceProvider> result) {
List<?> services = parent.getAll(serviceType);
assert services != null : String.format("parent returned null for services of type %s", format(serviceType));
for (Object service : services) {
result.add(wrap(service));
}
}
@Override
public boolean hasService(Class<?> type) {
return parent.hasService(type);
}
@Override
public void stop() {
}
}
/**
* Maintains state used while locating services and their dependencies.
*/
interface LookupContext {
@Nullable
ServiceProvider find(Type type, Provider provider);
}
interface TypeSpec extends Spec<Type> {
Type getType();
}
private static class ClassSpec implements TypeSpec {
private final Class<?> type;
private ClassSpec(Class<?> type) {
this.type = type;
}
public Type getType() {
return type;
}
public boolean isSatisfiedBy(Type element) {
if (element instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) element;
if (parameterizedType.getRawType() instanceof Class) {
return type.isAssignableFrom((Class) parameterizedType.getRawType());
}
} else if (element instanceof Class) {
Class<?> other = (Class<?>) element;
return type.isAssignableFrom(other);
}
return false;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ClassSpec classSpec = (ClassSpec) o;
return type != null ? type.equals(classSpec.type) : classSpec.type == null;
}
@Override
public int hashCode() {
return type.hashCode();
}
}
private static class ParameterizedTypeSpec implements TypeSpec {
private final Type type;
private final TypeSpec rawType;
private final List<TypeSpec> paramSpecs;
private ParameterizedTypeSpec(Type type, TypeSpec rawType, List<TypeSpec> paramSpecs) {
this.type = type;
this.rawType = rawType;
this.paramSpecs = paramSpecs;
}
public Type getType() {
return type;
}
public boolean isSatisfiedBy(Type element) {
if (element instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) element;
if (!rawType.isSatisfiedBy(parameterizedType.getRawType())) {
return false;
}
for (int i = 0; i < parameterizedType.getActualTypeArguments().length; i++) {
Type type = parameterizedType.getActualTypeArguments()[i];
if (!paramSpecs.get(i).isSatisfiedBy(type)) {
return false;
}
}
return true;
}
return false;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ParameterizedTypeSpec that = (ParameterizedTypeSpec) o;
if (!type.equals(that.type)) {
return false;
}
if (!rawType.equals(that.rawType)) {
return false;
}
return paramSpecs.equals(that.paramSpecs);
}
@Override
public int hashCode() {
int result = type.hashCode();
result = 31 * result + rawType.hashCode();
result = 31 * result + paramSpecs.hashCode();
return result;
}
}
private static class DefaultLookupContext implements LookupContext {
private final Set<Type> visiting = new HashSet<Type>();
public ServiceProvider find(Type serviceType, Provider provider) {
if (!visiting.add(serviceType)) {
throw new ServiceValidationException(String.format("Cycle in dependencies of service of type %s.", format(serviceType)));
}
try {
return getServiceProvider(serviceType, provider);
} finally {
visiting.remove(serviceType);
}
}
public ServiceProvider getServiceProvider(Type serviceType, Provider provider) {
BiFunction<ServiceProvider, LookupContext, Provider> function = SERVICE_TYPE_PROVIDER_CACHE.get(serviceType);
if (function == null) {
function = createServiceProviderFactory(serviceType);
SERVICE_TYPE_PROVIDER_CACHE.putIfAbsent(serviceType, function);
}
return function.apply(this, provider);
}
private static BiFunction<ServiceProvider, LookupContext, Provider> createServiceProviderFactory(final Type serviceType) {
if (serviceType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) serviceType;
Type rawType = parameterizedType.getRawType();
if (rawType.equals(Factory.class)) {
final Type typeArg = parameterizedType.getActualTypeArguments()[0];
if (typeArg instanceof Class) {
return new BiFunction<ServiceProvider, LookupContext, Provider>() {
@Override
public ServiceProvider apply(LookupContext lookupContext, Provider provider) {
return provider.getFactory(lookupContext, (Class) typeArg);
}
};
}
if (typeArg instanceof WildcardType) {
final WildcardType wildcardType = (WildcardType) typeArg;
if (wildcardType.getLowerBounds().length == 1 && wildcardType.getUpperBounds().length == 1) {
if (wildcardType.getLowerBounds()[0] instanceof Class && wildcardType.getUpperBounds()[0].equals(Object.class)) {
return new BiFunction<ServiceProvider, LookupContext, Provider>() {
@Override
public ServiceProvider apply(LookupContext lookupContext, Provider provider) {
return provider.getFactory(lookupContext, (Class<?>) wildcardType.getLowerBounds()[0]);
}
};
}
}
if (wildcardType.getLowerBounds().length == 0 && wildcardType.getUpperBounds().length == 1) {
if (wildcardType.getUpperBounds()[0] instanceof Class) {
return new BiFunction<ServiceProvider, LookupContext, Provider>() {
@Override
public ServiceProvider apply(LookupContext lookupContext, Provider provider) {
return provider.getFactory(lookupContext, (Class<?>) wildcardType.getUpperBounds()[0]);
}
};
}
}
}
}
if (rawType instanceof Class && ((Class<?>) rawType).isAssignableFrom(List.class)) {
Type typeArg = parameterizedType.getActualTypeArguments()[0];
if (typeArg instanceof Class) {
return new ServicesWithTypeLookup((Class<?>) typeArg);
}
if (typeArg instanceof WildcardType) {
WildcardType wildcardType = (WildcardType) typeArg;
if (wildcardType.getUpperBounds()[0] instanceof Class && wildcardType.getLowerBounds().length == 0) {
return new ServicesWithTypeLookup((Class<?>) wildcardType.getUpperBounds()[0]);
}
}
}
}
final TypeSpec serviceTypeSpec = toSpec(serviceType);
return new BiFunction<ServiceProvider, LookupContext, Provider>() {
@Override
public ServiceProvider apply(LookupContext lookupContext, Provider provider) {
return provider.getService(lookupContext, serviceTypeSpec);
}
};
}
static TypeSpec toSpec(Type serviceType) {
if (serviceType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) serviceType;
List<TypeSpec> paramSpecs = new ArrayList<TypeSpec>();
for (Type paramType : parameterizedType.getActualTypeArguments()) {
paramSpecs.add(toSpec(paramType));
}
return new ParameterizedTypeSpec(serviceType, toSpec(parameterizedType.getRawType()), paramSpecs);
} else if (serviceType instanceof Class) {
Class<?> serviceClass = (Class<?>) serviceType;
assertValidServiceType(serviceClass);
return new ClassSpec(serviceClass);
}
throw new ServiceValidationException(String.format("Locating services with type %s is not supported.", format(serviceType)));
}
private static class CollectionServiceProvider implements ServiceProvider {
private final Type typeArg;
private final List<Object> services;
private final List<ServiceProvider> providers;
public CollectionServiceProvider(Type typeArg, List<Object> services, List<ServiceProvider> providers) {
this.typeArg = typeArg;
this.services = services;
this.providers = providers;
}
@Override
public String getDisplayName() {
return "services with type " + typeArg;
}
@Override
public Object get() {
return services;
}
@Override
public void requiredBy(Provider provider) {
for (ServiceProvider serviceProvider : providers) {
serviceProvider.requiredBy(provider);
}
}
}
private static class ServicesWithTypeLookup implements BiFunction<ServiceProvider, LookupContext, Provider> {
private final Class<?> elementClass;
ServicesWithTypeLookup(Class<?> elementClass) {
this.elementClass = elementClass;
}
@Override
public ServiceProvider apply(final LookupContext lookupContext, final Provider provider) {
List<ServiceProvider> providers = new ArrayList<ServiceProvider>();
provider.getAll(lookupContext, elementClass, providers);
List<Object> services = new ArrayList<Object>(providers.size());
for (ServiceProvider serviceProvider : providers) {
services.add(serviceProvider.get());
}
return new CollectionServiceProvider(elementClass, services, providers);
}
}
}
private static void assertValidServiceType(Class<?> serviceClass) {
if (serviceClass.isArray()) {
throw new ServiceValidationException("Locating services with array type is not supported.");
}
if (serviceClass.isAnnotation()) {
throw new ServiceValidationException("Locating services with annotation type is not supported.");
}
}
}