NonInjectionManager.java
/*
* Copyright (c) 2023, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package org.glassfish.jersey.client.innate.inject;
import org.glassfish.jersey.client.internal.LocalizationMessages;
import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.internal.inject.Binder;
import org.glassfish.jersey.internal.inject.Binding;
import org.glassfish.jersey.internal.inject.ClassBinding;
import org.glassfish.jersey.internal.inject.DisposableSupplier;
import org.glassfish.jersey.internal.inject.ForeignDescriptor;
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.internal.inject.InstanceBinding;
import org.glassfish.jersey.internal.inject.PerThread;
import org.glassfish.jersey.internal.inject.ServiceHolder;
import org.glassfish.jersey.internal.inject.ServiceHolderImpl;
import org.glassfish.jersey.internal.inject.SupplierClassBinding;
import org.glassfish.jersey.internal.inject.SupplierInstanceBinding;
import org.glassfish.jersey.internal.util.collection.LazyValue;
import org.glassfish.jersey.internal.util.collection.Ref;
import org.glassfish.jersey.internal.util.collection.Value;
import org.glassfish.jersey.internal.util.collection.Values;
import org.glassfish.jersey.process.internal.RequestScope;
import org.glassfish.jersey.process.internal.RequestScoped;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import javax.ws.rs.ConstrainedTo;
import javax.ws.rs.RuntimeType;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
@ConstrainedTo(RuntimeType.CLIENT)
public final class NonInjectionManager implements InjectionManager {
private static final Logger logger = Logger.getLogger(NonInjectionManager.class.getName());
private final MultivaluedMap<Class<?>, InstanceBinding<?>> instanceBindings = new MultivaluedHashMap<>();
private final MultivaluedMap<Class<?>, ClassBinding<?>> contractBindings = new MultivaluedHashMap<>();
private final MultivaluedMap<Class<?>, SupplierInstanceBinding<?>> supplierInstanceBindings = new MultivaluedHashMap<>();
private final MultivaluedMap<Class<?>, SupplierClassBinding<?>> supplierClassBindings = new MultivaluedHashMap<>();
private final MultivaluedMap<Type, InstanceBinding<?>> instanceTypeBindings = new MultivaluedHashMap<>();
private final MultivaluedMap<Type, ClassBinding<?>> contractTypeBindings = new MultivaluedHashMap<>();
private final MultivaluedMap<Type, SupplierInstanceBinding<?>> supplierTypeInstanceBindings = new MultivaluedHashMap<>();
private final MultivaluedMap<Type, SupplierClassBinding<?>> supplierTypeClassBindings = new MultivaluedHashMap<>();
private final Instances instances = new Instances();
private final Types types = new Types();
private volatile boolean isRequestScope = false;
private volatile boolean shutdown = false;
/**
* A class that holds singleton instances and thread-scope instances. Provides thread safe access to singletons
* and thread-scope instances. The instances are created for Type (ParametrizedType) and for a Class.
* @param <TYPE> the type for which the instance is created, either Class, or ParametrizedType (for instance
* Provider<SomeClass>).
*/
private class TypedInstances<TYPE extends Type> {
private final MultivaluedMap<TYPE, InstanceContext<?>> singletonInstances = new MultivaluedHashMap<>();
private ThreadLocal<MultivaluedMap<TYPE, InstanceContext<?>>> threadInstances = new ThreadLocal<>();
private ThreadLocal<MultivaluedMap<DisposableSupplier, Object>> disposableSupplierObjects =
ThreadLocal.withInitial(() -> new MultivaluedHashMap<>());
private <T> List<InstanceContext<?>> _getSingletons(TYPE clazz) {
List<InstanceContext<?>> si;
synchronized (singletonInstances) {
si = singletonInstances.get(clazz);
}
return si;
}
@SuppressWarnings("unchecked")
<T> T _addSingleton(TYPE clazz, T instance, Binding<?, ?> binding, Annotation[] qualifiers, boolean destroy) {
synchronized (singletonInstances) {
// check existing singleton with a qualifier already created by another thread io a meantime
List<InstanceContext<?>> values = singletonInstances.get(clazz);
if (values != null) {
List<InstanceContext<?>> qualified
= values.stream()
.filter(ctx -> ctx.hasQualifiers(qualifiers))
.collect(Collectors.toList());
if (!qualified.isEmpty()) {
return (T) qualified.get(0).instance;
}
}
InstanceContext<?> instanceContext = new InstanceContext<>(instance, binding, qualifiers, !destroy);
singletonInstances.add(clazz, instanceContext);
return instance;
}
}
@SuppressWarnings("unchecked")
<T> T addSingleton(TYPE clazz, T t, Binding<?, ?> binding, Annotation[] instanceQualifiers) {
T t2 = _addSingleton(clazz, t, binding, instanceQualifiers, true);
if (t2 == t) {
for (Type contract : binding.getContracts()) {
if (!clazz.equals(contract) && isClass(contract)) {
_addSingleton((TYPE) contract, t, binding, instanceQualifiers, false);
}
}
}
return t2;
}
private List<InstanceContext<?>> _getThreadInstances(TYPE clazz) {
MultivaluedMap<TYPE, InstanceContext<?>> ti = threadInstances.get();
List<InstanceContext<?>> list = ti == null ? null : new LinkedList<>();
if (ti != null) {
return ti.get(clazz);
}
return list;
}
private <T> void _addThreadInstance(
TYPE clazz, T instance, Binding<T, ?> binding, Annotation[] qualifiers, boolean destroy) {
MultivaluedMap<TYPE, InstanceContext<?>> map = threadInstances.get();
if (map == null) {
map = new MultivaluedHashMap<>();
threadInstances.set(map);
}
InstanceContext<?> instanceContext = new InstanceContext<>(instance, binding, qualifiers, !destroy);
map.add(clazz, instanceContext);
}
<T> void addThreadInstance(TYPE clazz, T t, Binding<T, ?> binding, Annotation[] instanceQualifiers) {
_addThreadInstance(clazz, t, binding, instanceQualifiers, true);
for (Type contract : binding.getContracts()) {
if (!clazz.equals(contract) && isClass(contract)) {
_addThreadInstance((TYPE) contract, t, binding, instanceQualifiers, false);
}
}
}
private <T> List<T> getInstances(TYPE clazz, Annotation[] annotations) {
List<InstanceContext<?>> i = _getContexts(clazz);
return InstanceContext.toInstances(i, annotations);
}
<T> List<InstanceContext<?>> getContexts(TYPE clazz, Annotation[] annotations) {
List<InstanceContext<?>> i = _getContexts(clazz);
return InstanceContext.filterInstances(i, annotations);
}
private <T> List<InstanceContext<?>> _getContexts(TYPE clazz) {
List<InstanceContext<?>> si = _getSingletons(clazz);
List<InstanceContext<?>> ti = _getThreadInstances(clazz);
return InstanceContext.merge(si, ti);
}
<T> T getInstance(TYPE clazz, Annotation[] annotations) {
List<T> i = getInstances(clazz, annotations);
if (i != null) {
checkUnique(i);
return instanceOrSupply(clazz, i.get(0));
}
return null;
}
private <T> T instanceOrSupply(TYPE clazz, T t) {
if (!Class.class.isInstance(clazz) || ((Class) clazz).isInstance(t)) {
return t;
} else if (Supplier.class.isInstance(t)) {
return (T) registerDisposableSupplierAndGet((Supplier) t, this);
} else if (Provider.class.isInstance(t)) {
return (T) ((Provider) t).get();
} else {
return t;
}
}
void dispose() {
singletonInstances.forEach((clazz, instances) -> instances.forEach(instance -> preDestroy(instance.getInstance())));
disposeThreadInstances(true);
/* The java.lang.ThreadLocal$ThreadLocalMap$Entry[] keeps references to this NonInjectionManager */
threadInstances = null;
disposableSupplierObjects = null;
}
void disposeThreadInstances(boolean allThreadInstances) {
MultivaluedMap<TYPE, InstanceContext<?>> ti = threadInstances.get();
if (ti == null) {
return;
}
Set<Map.Entry<TYPE, List<InstanceContext<?>>>> tiSet = ti.entrySet();
Iterator<Map.Entry<TYPE, List<InstanceContext<?>>>> tiSetIt = tiSet.iterator();
while (tiSetIt.hasNext()) {
Map.Entry<TYPE, List<InstanceContext<?>>> entry = tiSetIt.next();
Iterator<InstanceContext<?>> listIt = entry.getValue().iterator();
while (listIt.hasNext()) {
InstanceContext<?> instanceContext = listIt.next();
if (allThreadInstances || instanceContext.getBinding().getScope() != PerThread.class) {
listIt.remove();
if (DisposableSupplier.class.isInstance(instanceContext.getInstance())) {
MultivaluedMap<DisposableSupplier, Object> disposeMap = disposableSupplierObjects.get();
Iterator<Map.Entry<DisposableSupplier, List<Object>>> disposeMapIt = disposeMap.entrySet().iterator();
while (disposeMapIt.hasNext()) {
Map.Entry<DisposableSupplier, List<Object>> disposeMapEntry = disposeMapIt.next();
if (disposeMapEntry.getKey() == /* identity */ instanceContext.getInstance()) {
Iterator<Object> disposeMapEntryIt = disposeMapEntry.getValue().iterator();
while (disposeMapEntryIt.hasNext()) {
Object disposeInstance = disposeMapEntryIt.next();
((DisposableSupplier) instanceContext.getInstance()).dispose(disposeInstance);
disposeMapEntryIt.remove();
}
}
if (disposeMapEntry.getValue().isEmpty()) {
disposeMapIt.remove();
}
}
}
instanceContext.destroy(NonInjectionManager.this);
}
if (entry.getValue().isEmpty()) {
tiSetIt.remove();
}
}
}
disposableSupplierObjects.remove();
}
}
private class Instances extends TypedInstances<Class<?>> {
}
private class Types extends TypedInstances<Type> {
}
public NonInjectionManager() {
Binding binding = new AbstractBinder() {
@Override
protected void configure() {
bind(NonInjectionRequestScope.class).to(RequestScope.class).in(Singleton.class);
}
}.getBindings().iterator().next();
RequestScope scope = new NonInjectionRequestScope(this);
instances.addSingleton(RequestScope.class, scope, binding, null);
types.addSingleton(RequestScope.class, scope, binding, null);
}
public NonInjectionManager(boolean warning) {
this();
if (warning) {
logger.warning(LocalizationMessages.NONINJECT_FALLBACK());
} else {
logger.log(Level.FINER, LocalizationMessages.NONINJECT_FALLBACK());
}
}
@Override
public void completeRegistration() {
instances._addSingleton(InjectionManager.class, this, new InjectionManagerBinding(), null, false);
}
@Override
public void shutdown() {
shutdown = true;
instances.dispose();
types.dispose();
}
void disposeRequestScopedInstances() {
instances.disposeThreadInstances(false);
types.disposeThreadInstances(false);
}
@Override
public boolean isShutdown() {
return shutdown;
}
private void checkShutdown() {
if (shutdown) {
throw new IllegalStateException(LocalizationMessages.NONINJECT_SHUTDOWN());
}
}
@Override
public void register(Binding binding) {
checkShutdown();
if (InstanceBinding.class.isInstance(binding)) {
InstanceBinding instanceBinding = (InstanceBinding) binding;
Class<?> mainType = binding.getImplementationType();
if (!instanceBindings.containsKey(mainType)) { // the class could be registered twice, for reader & for writer
instanceBindings.add(mainType, (InstanceBinding) binding);
}
for (Type type : (Iterable<Type>) instanceBinding.getContracts()) {
if (isClass(type)) {
if (!mainType.equals(type)) {
instanceBindings.add((Class<?>) type, instanceBinding);
}
} else {
instanceTypeBindings.add(type, instanceBinding);
}
}
} else if (ClassBinding.class.isInstance(binding)) {
ClassBinding<?> contractBinding = (ClassBinding<?>) binding;
Class<?> mainType = binding.getImplementationType();
if (!contractBindings.containsKey(mainType)) { // the class could be registered twice, for reader & for writer
contractBindings.add(mainType, contractBinding);
}
for (Type type : contractBinding.getContracts()) {
if (isClass(type)) {
if (!mainType.equals(type)) {
contractBindings.add((Class<?>) type, contractBinding);
}
} else {
contractTypeBindings.add(type, contractBinding);
}
}
} else if (SupplierInstanceBinding.class.isInstance(binding)) {
SupplierInstanceBinding<?> supplierBinding = (SupplierInstanceBinding<?>) binding;
for (Type type : supplierBinding.getContracts()) {
if (isClass(type)) {
supplierInstanceBindings.add((Class<?>) type, supplierBinding);
} else {
supplierTypeInstanceBindings.add(type, supplierBinding);
}
}
} else if (SupplierClassBinding.class.isInstance(binding)) {
SupplierClassBinding<?> supplierBinding = (SupplierClassBinding<?>) binding;
for (Type type : supplierBinding.getContracts()) {
if (isClass(type)) {
supplierClassBindings.add((Class<?>) type, supplierBinding);
} else {
supplierTypeClassBindings.add(type, supplierBinding);
}
}
}
}
@Override
public void register(Iterable<Binding> descriptors) {
checkShutdown();
for (Binding binding : descriptors) {
register(binding);
}
}
@Override
public void register(Binder binder) {
checkShutdown();
binder.getBindings().stream().iterator().forEachRemaining(this::register);
}
@Override
public void register(Object provider) throws IllegalArgumentException {
throw new UnsupportedOperationException("Register " + provider);
}
@Override
public boolean isRegistrable(Class<?> clazz) {
return false; // for external creators
}
@Override
public <T> List<ServiceHolder<T>> getAllServiceHolders(Class<T> contractOrImpl, Annotation... qualifiers) {
checkShutdown();
ClassBindings<T> classBindings = classBindings(contractOrImpl, qualifiers);
return classBindings.getAllServiceHolders(qualifiers);
}
@Override
public <T> T getInstance(Class<T> contractOrImpl, Annotation... qualifiers) {
checkShutdown();
ClassBindings<T> classBindings = classBindings(contractOrImpl, qualifiers);
classBindings.matchQualifiers(qualifiers);
return classBindings.getInstance();
}
@Override
public <T> T getInstance(Class<T> contractOrImpl, String classAnalyzer) {
throw new UnsupportedOperationException("getInstance(Class, String)");
}
@Override
public <T> T getInstance(Class<T> contractOrImpl) {
checkShutdown();
T instance = instances.getInstance(contractOrImpl, null);
if (instance != null) {
return instance;
}
return create(contractOrImpl);
}
@Override
@SuppressWarnings("unchecked")
public <T> T getInstance(Type contractOrImpl) {
checkShutdown();
if (ParameterizedType.class.isInstance(contractOrImpl)) {
T instance = types.getInstance(contractOrImpl, null);
if (instance != null) {
return instance;
}
TypeBindings<T> typeBindings = typeBindings(contractOrImpl);
return typeBindings.getInstance();
} else if (isClass(contractOrImpl)) {
return getInstance((Class<? extends T>) contractOrImpl);
}
throw new IllegalStateException(LocalizationMessages.NONINJECT_UNSATISFIED(contractOrImpl));
}
private static boolean isClass(Type type) {
return Class.class.isAssignableFrom(type.getClass());
}
@Override
public Object getInstance(ForeignDescriptor foreignDescriptor) {
throw new UnsupportedOperationException("getInstance(ForeignDescriptor foreignDescriptor) ");
}
@Override
public ForeignDescriptor createForeignDescriptor(Binding binding) {
throw new UnsupportedOperationException("createForeignDescriptor(Binding binding) ");
}
@Override
public <T> List<T> getAllInstances(Type contractOrImpl) {
checkShutdown();
if (!isClass(contractOrImpl)) {
TypeBindings<T> typeBindings = typeBindings(contractOrImpl);
return typeBindings.allInstances();
}
@SuppressWarnings("unchecked")
ClassBindings<T> classBindings = classBindings((Class<T>) contractOrImpl);
return classBindings.allInstances();
}
@SuppressWarnings("unchecked")
@Override
public <T> T create(Class<T> createMe) {
checkShutdown();
if (InjectionManager.class.equals(createMe)) {
return (T) this;
}
if (RequestScope.class.equals(createMe)) {
throw new IllegalStateException(LocalizationMessages.NONINJECT_REQUESTSCOPE_CREATED());
}
ClassBindings<T> classBindings = classBindings(createMe);
return classBindings.create(true);
}
@Override
public <T> T createAndInitialize(Class<T> createMe) {
checkShutdown();
if (InjectionManager.class.equals(createMe)) {
return (T) this;
}
if (RequestScope.class.equals(createMe)) {
throw new IllegalStateException(LocalizationMessages.NONINJECT_REQUESTSCOPE_CREATED());
}
ClassBindings<T> classBindings = classBindings(createMe);
T t = classBindings.create(false);
return t != null ? t : justCreate(createMe);
}
public <T> T justCreate(Class<T> createMe) {
T result = null;
try {
Constructor<T> mostArgConstructor = findConstructor(createMe);
if (mostArgConstructor != null) {
int argCount = mostArgConstructor.getParameterCount();
if (argCount == 0) {
ensureAccessible(mostArgConstructor);
result = mostArgConstructor.newInstance();
} else if (argCount > 0) {
Object[] args = getArguments(mostArgConstructor, argCount);
if (args != null) {
ensureAccessible(mostArgConstructor);
result = mostArgConstructor.newInstance(args);
}
}
}
if (result == null) {
throw new IllegalStateException(LocalizationMessages.NONINJECT_NO_CONSTRUCTOR(createMe.getName()));
}
} catch (Exception e) {
throw new IllegalStateException(e);
}
inject(result);
return result;
}
private static <T> Constructor<T> findConstructor(Class<T> forClass) {
Constructor<T>[] constructors = (Constructor<T>[]) forClass.getDeclaredConstructors();
Constructor<T> mostArgConstructor = null;
int argCount = -1;
for (Constructor<T> constructor : constructors) {
if (constructor.isAnnotationPresent(Inject.class) || constructor.getParameterCount() == 0) {
if (constructor.getParameterCount() > argCount) {
mostArgConstructor = constructor;
argCount = constructor.getParameterCount();
}
}
}
return mostArgConstructor;
}
private Object[] getArguments(Executable executable, int argCount) {
if (executable == null) {
return null;
}
Object[] args = new Object[argCount];
for (int i = 0; i != argCount; i++) {
Type type = executable.getAnnotatedParameterTypes()[i].getType();
args[i] = isClass(type) ? getInstance((Class<?>) type) : getInstance(type);
}
return args;
}
private static void ensureAccessible(Executable executable) {
try {
if (!executable.isAccessible()) {
executable.setAccessible(true);
}
} catch (Exception e) {
// consume. It will fail later with invoking the executable
}
}
private void checkUnique(List<?> list) {
if (list.size() != 1) {
throw new IllegalStateException(LocalizationMessages.NONINJECT_AMBIGUOUS_SERVICES(list.get(0)));
}
}
@Override
public void inject(Object injectMe) {
Method postConstruct = getAnnotatedMethod(injectMe, PostConstruct.class);
if (postConstruct != null) {
ensureAccessible(postConstruct);
try {
postConstruct.invoke(injectMe);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
@Override
public void inject(Object injectMe, String classAnalyzer) {
throw new UnsupportedOperationException("inject(Object injectMe, String classAnalyzer)");
}
@Override
public void preDestroy(Object preDestroyMe) {
Method preDestroy = Method.class.isInstance(preDestroyMe)
? (Method) preDestroyMe
: getAnnotatedMethod(preDestroyMe, PreDestroy.class);
if (preDestroy != null) {
ensureAccessible(preDestroy);
try {
preDestroy.invoke(preDestroyMe);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
private static Method getAnnotatedMethod(Object object, Class<? extends Annotation> annotation) {
Class<?> clazz = object.getClass();
for (Method method : clazz.getMethods()) {
if (method.isAnnotationPresent(annotation)
&& /* do not invoke interceptors */ method.getParameterCount() == 0) {
return method;
}
}
return null;
}
/**
* Some {@link Binding} requires the proxy to be created rather than just the instance,
* for instance a proxy of an instance supplied by a supplier that is not known at a time of the proxy creation.
* @param createProxy the nullable {@link Binding#isProxiable()} information
* @param iface the type of which the proxy is created
* @param supplier the reference to the supplier
* @param <T> the type the supplier should supply
* @return The proxy for the instance supplied by a supplier or the instance if not required to be proxied.
*/
@SuppressWarnings("unchecked")
private <T> T createSupplierProxyIfNeeded(
Boolean createProxy, Class<T> iface, Supplier<Supplier<T>> supplier, TypedInstances<?> typedInstances) {
if (createProxy != null && createProxy && iface.isInterface()) {
T proxy = (T) Proxy.newProxyInstance(iface.getClassLoader(), new Class[]{iface}, new InvocationHandler() {
final Set<Object> instances = new HashSet<>();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Supplier<T> supplierT = supplier.get();
T t = supplierT.get();
if (DisposableSupplier.class.isInstance(supplierT) && !instances.contains(t)) {
MultivaluedMap<DisposableSupplier, Object> map = typedInstances.disposableSupplierObjects.get();
map.add((DisposableSupplier) supplierT, t);
}
Object ret = method.invoke(t, args);
return ret;
}
});
return proxy;
} else {
return registerDisposableSupplierAndGet(supplier.get(), typedInstances);
}
}
/**
* A holder class making sure the Supplier (especially the {@link DisposableSupplier}) supplying the instance
* supplies (and is registered for being disposed at the end of the lifecycle) only once.
* @param <T>
*/
private class SingleRegisterSupplier<T> {
private final LazyValue<T> once;
private SingleRegisterSupplier(Supplier<T> supplier, TypedInstances<?> instances) {
once = Values.lazy((Value<T>) () -> registerDisposableSupplierAndGet(supplier, instances));
}
T get() {
return once.get();
}
}
private <T> T registerDisposableSupplierAndGet(Supplier<T> supplier, TypedInstances<?> typedInstances) {
T instance = supplier.get();
if (DisposableSupplier.class.isInstance(supplier)) {
typedInstances.disposableSupplierObjects.get().add((DisposableSupplier<T>) supplier, instance);
}
return instance;
}
/**
* Create {@link ClassBindings} instance containing bindings and instances for the given Type.
* @param clazz the given class.
* @param instancesQualifiers The qualifiers the expected instances of the given class should have.
* @param <T> Expected return class type.
* @return the {@link ClassBindings}.
*/
@SuppressWarnings("unchecked")
private <T> ClassBindings<T> classBindings(Class<T> clazz, Annotation... instancesQualifiers) {
ClassBindings<T> classBindings = new ClassBindings<>(clazz, instancesQualifiers);
List<InstanceBinding<?>> ib = instanceBindings.get(clazz);
if (ib != null) {
ib.forEach(binding -> classBindings.instanceBindings.add((InstanceBinding<T>) binding));
}
List<SupplierInstanceBinding<?>> sib = supplierInstanceBindings.get(clazz);
if (sib != null) {
sib.forEach(binding -> classBindings.supplierInstanceBindings.add((SupplierInstanceBinding<T>) binding));
}
List<ClassBinding<?>> cb = contractBindings.get(clazz);
if (cb != null) {
cb.forEach(binding -> classBindings.classBindings.add((ClassBinding<T>) binding));
}
List<SupplierClassBinding<?>> scb = supplierClassBindings.get(clazz);
if (scb != null) {
scb.forEach(binding -> classBindings.supplierClassBindings.add((SupplierClassBinding<T>) binding));
}
return classBindings;
}
/**
* Create {@link TypeBindings} instance containing bindings and instances for the given Type.
* @param type the given type.
* @param <T> Expected return type.
* @return the {@link TypeBindings}.
*/
@SuppressWarnings("unchecked")
private <T> TypeBindings<T> typeBindings(Type type) {
TypeBindings<T> typeBindings = new TypeBindings<>(type);
List<InstanceBinding<?>> ib = instanceTypeBindings.get(type);
if (ib != null) {
ib.forEach(binding -> typeBindings.instanceBindings.add((InstanceBinding<T>) binding));
}
List<SupplierInstanceBinding<?>> sib = supplierTypeInstanceBindings.get(type);
if (sib != null) {
sib.forEach(binding -> typeBindings.supplierInstanceBindings.add((SupplierInstanceBinding<T>) binding));
}
List<ClassBinding<?>> cb = contractTypeBindings.get(type);
if (cb != null) {
cb.forEach(binding -> typeBindings.classBindings.add((ClassBinding<T>) binding));
}
List<SupplierClassBinding<?>> scb = supplierTypeClassBindings.get(type);
if (scb != null) {
scb.forEach(binding -> typeBindings.supplierClassBindings.add((SupplierClassBinding<T>) binding));
}
return typeBindings;
}
/**
* <p>
* A class that contains relevant bindings for a given TYPE, filtered from all registered bindings.
* The TYPE is either Type (ParametrizedType) or Class.
* </p>
* <p>
* The class also filters any bindings for which the singleton or thread-scoped instance already is created.
* The class either provides the existing instance, or all instances of the TYPE, or {@link ServiceHolder}s.
* </p>
* @param <X> The expected return type for the TYPE.
* @param <TYPE> The Type for which a {@link Binding} has been created.
*/
private abstract class XBindings<X, TYPE extends Type> {
protected final List<InstanceBinding<X>> instanceBindings = new LinkedList<>();
protected final List<SupplierInstanceBinding<X>> supplierInstanceBindings = new LinkedList<>();
protected final List<ClassBinding<X>> classBindings = new LinkedList<>();
protected final List<SupplierClassBinding<X>> supplierClassBindings = new LinkedList<>();
protected final TYPE type;
protected final Annotation[] instancesQualifiers;
protected final TypedInstances<TYPE> instances;
protected XBindings(TYPE type, Annotation[] instancesQualifiers, TypedInstances<TYPE> instances) {
this.type = type;
this.instancesQualifiers = instancesQualifiers;
this.instances = instances;
}
int size() {
return instanceBindings.size()
+ supplierInstanceBindings.size()
+ classBindings.size()
+ supplierClassBindings.size();
}
private void _checkUnique() {
if (size() > 1) {
throw new IllegalStateException(LocalizationMessages.NONINJECT_AMBIGUOUS_SERVICES(type));
}
}
void filterBinding(Binding binding) {
if (InstanceBinding.class.isInstance(binding)) {
instanceBindings.remove(binding);
} else if (ClassBinding.class.isInstance(binding)) {
classBindings.remove(binding);
} else if (SupplierInstanceBinding.class.isInstance(binding)) {
supplierInstanceBindings.remove(binding);
} else if (SupplierClassBinding.class.isInstance(binding)) {
supplierClassBindings.remove(binding);
}
}
/**
* Match the binging qualifiers
* @param bindingQualifiers the qualifiers registered with the bindings
*/
void matchQualifiers(Annotation... bindingQualifiers) {
if (bindingQualifiers != null) {
_filterRequested(instanceBindings, bindingQualifiers);
_filterRequested(classBindings, bindingQualifiers);
_filterRequested(supplierInstanceBindings, bindingQualifiers);
_filterRequested(supplierClassBindings, bindingQualifiers);
}
}
@SuppressWarnings("unchecked")
private void _filterRequested(List<? extends Binding<?, ?>> bindingList, Annotation... requestedQualifiers) {
for (Iterator<? extends Binding> bindingIterator = bindingList.iterator(); bindingIterator.hasNext();) {
Binding<X, ?> binding = bindingIterator.next();
classLoop:
for (Annotation requestedQualifier : requestedQualifiers) {
for (Annotation bindingQualifier : binding.getQualifiers()) {
if (requestedQualifier.annotationType().isInstance(bindingQualifier)) {
continue classLoop;
}
}
bindingIterator.remove();
}
}
}
protected boolean _isPerThread(Class<? extends Annotation> scope) {
return RequestScoped.class.equals(scope) || PerThread.class.equals(scope);
}
private X _getInstance(InstanceBinding<X> instanceBinding) {
return instanceBinding.getService();
}
private X _create(SupplierInstanceBinding<X> binding) {
Supplier<X> supplier = binding.getSupplier();
X t = registerDisposableSupplierAndGet(supplier, instances);
t = addInstance(type, t, binding);
return t;
}
X create(boolean throwWhenNoBinding) {
_checkUnique();
if (!instanceBindings.isEmpty()) {
return _getInstance(instanceBindings.get(0));
} else if (!supplierInstanceBindings.isEmpty()) {
return _create(supplierInstanceBindings.get(0));
} else if (!classBindings.isEmpty()) {
return _createAndStore(classBindings.get(0));
} else if (!supplierClassBindings.isEmpty()) {
return _create(supplierClassBindings.get(0));
}
if (throwWhenNoBinding) {
throw new IllegalStateException(LocalizationMessages.NONINJECT_NO_BINDING(type));
} else {
return null;
}
}
protected X getInstance() {
X instance = instances.getInstance(type, instancesQualifiers);
if (instance != null) {
return instance;
}
return create(true);
}
List<X> allInstances() {
List<X> list = new LinkedList<>();
List<InstanceContext<?>> instanceContextList;
instanceContextList = instances.getContexts(type, instancesQualifiers);
if (instanceContextList != null) {
instanceContextList.forEach(instanceContext -> filterBinding(instanceContext.getBinding()));
instanceContextList.forEach(instanceContext -> list.add((X) instanceContext.getInstance()));
}
list.addAll(instanceBindings.stream()
.map(this::_getInstance)
.collect(Collectors.toList()));
list.addAll(classBindings.stream()
.map(this::_createAndStore)
.collect(Collectors.toList()));
list.addAll(supplierInstanceBindings.stream()
.map(this::_create)
.collect(Collectors.toList()));
list.addAll(supplierClassBindings.stream()
.map(this::_create)
.collect(Collectors.toList()));
return list;
}
protected abstract X _create(SupplierClassBinding<X> binding);
protected abstract X _createAndStore(ClassBinding<X> binding);
protected <T> T _addSingletonInstance(TYPE type, T instance, Binding<?, ?> binding) {
return instances.addSingleton(type, instance, binding, instancesQualifiers);
}
protected <T> T addInstance(TYPE type, T instance, Binding binding) {
if (Singleton.class.equals(binding.getScope())) {
instance = instances.addSingleton(type, instance, binding, instancesQualifiers);
} else if (_isPerThread(binding.getScope())) {
instances.addThreadInstance(type, instance, binding, instancesQualifiers);
}
return instance;
}
}
private class ClassBindings<T> extends XBindings<T, Class<?>> {
private ClassBindings(Class<T> clazz, Annotation[] instancesQualifiers) {
super(clazz, instancesQualifiers, NonInjectionManager.this.instances);
}
@SuppressWarnings("unchecked")
List<ServiceHolder<T>> getAllServiceHolders(Annotation... qualifiers) {
matchQualifiers(qualifiers);
List<ServiceHolder<T>> holders = new LinkedList<>();
List<InstanceContext<?>> instanceContextList;
instanceContextList = instances.getContexts(type, qualifiers);
if (instanceContextList != null) {
instanceContextList.forEach(instanceContext -> filterBinding(instanceContext.getBinding()));
instanceContextList.forEach(instanceContext -> holders.add(new ServiceHolderImpl<T>(
(T) instanceContext.getInstance(),
(Class<T>) instanceContext.getInstance().getClass(),
instanceContext.getBinding().getContracts(),
instanceContext.getBinding().getRank() == null ? 0 : instanceContext.getBinding().getRank())
));
}
List<ServiceHolder<T>> instanceBindingHolders = instanceBindings.stream()
.map(this::_serviceHolder)
.collect(Collectors.toList());
holders.addAll(instanceBindingHolders);
List<ServiceHolder<T>> classBindingHolders = classBindings.stream()
.filter(binding -> NonInjectionManager.this.findConstructor(binding.getService()) != null)
.map(this::_serviceHolder)
.collect(Collectors.toList());
holders.addAll(classBindingHolders);
return holders;
}
private <T> ServiceHolderImpl<T> _serviceHolder(InstanceBinding<T> binding) {
return new ServiceHolderImpl<T>(
binding.getService(),
binding.getImplementationType(),
binding.getContracts(),
binding.getRank() == null ? 0 : binding.getRank());
}
private <T> ServiceHolderImpl<T> _serviceHolder(ClassBinding<T> binding) {
return new ServiceHolderImpl<T>(
NonInjectionManager.this.create(binding.getService()),
binding.getImplementationType(),
binding.getContracts(),
binding.getRank() == null ? 0 : binding.getRank());
}
protected T _create(SupplierClassBinding<T> binding) {
Supplier<Supplier<T>> supplierSupplier = () -> {
Supplier<T> supplier = instances.getInstance(binding.getSupplierClass(), null);
if (supplier == null) {
supplier = justCreate(binding.getSupplierClass());
if (Singleton.class.equals(binding.getSupplierScope())) {
supplier = instances.addSingleton(binding.getSupplierClass(), supplier, binding, null);
} else if (_isPerThread(binding.getSupplierScope()) || binding.getSupplierScope() == null) {
instances.addThreadInstance(binding.getSupplierClass(), supplier, binding, null);
}
}
return supplier;
};
T t = createSupplierProxyIfNeeded(binding.isProxiable(), (Class<T>) type, supplierSupplier, instances);
// t = addInstance(type, t, binding); The supplier here creates instances that ought not to be registered as beans
return t;
}
protected T _createAndStore(ClassBinding<T> binding) {
T result = justCreate(binding.getService());
result = addInstance(binding.getService(), result, binding);
return result;
}
}
private class TypeBindings<T> extends XBindings<T, Type> {
private TypeBindings(Type type) {
super(type, null, types);
}
protected T _create(SupplierClassBinding<T> binding) {
Supplier<T> supplier = justCreate(binding.getSupplierClass());
T t = registerDisposableSupplierAndGet(supplier, instances);
t = addInstance(type, t, binding);
return t;
}
@Override
protected T _createAndStore(ClassBinding<T> binding) {
T result = justCreate(binding.getService());
result = addInstance(type, result, binding);
return result;
}
@SuppressWarnings("unchecked")
@Override
T create(boolean throwWhenNoBinding) {
if (ParameterizedType.class.isInstance(type)) {
ParameterizedType pt = (ParameterizedType) type;
if (Provider.class.equals(pt.getRawType())) {
return (T) new Provider<Object>() {
final SingleRegisterSupplier<Object> supplier = new SingleRegisterSupplier<>(new Supplier<Object>() {
@Override
public Object get() {
Type actualTypeArgument = pt.getActualTypeArguments()[0];
if (isClass(actualTypeArgument)) {
return NonInjectionManager.this.getInstance((Class<? extends T>) actualTypeArgument);
} else {
return NonInjectionManager.this.getInstance(actualTypeArgument);
}
}
}, instances);
@Override
public Object get() {
return supplier.get(); //Not disposable
}
};
}
}
return super.create(throwWhenNoBinding);
}
}
/**
* A triplet of created instance, the registered {@link Binding} that prescribed the creation of the instance
* and {@link Annotation qualifiers} the instance was created with.
* @param <T> type of the instance.
* @see NonInjectionManager#getInstance(Class, Annotation[])
*/
private static class InstanceContext<T> {
private final T instance;
private final Binding<?, ?> binding;
private final Annotation[] createdWithQualifiers;
private boolean destroyed = false;
private InstanceContext(T instance, Binding<?, ?> binding, Annotation[] qualifiers, boolean destroyed) {
this.instance = instance;
this.binding = binding;
this.createdWithQualifiers = qualifiers;
this.destroyed = destroyed;
}
public Binding<?, ?> getBinding() {
return binding;
}
public T getInstance() {
return instance;
}
public void destroy(NonInjectionManager nonInjectionManager) {
if (!destroyed) {
destroyed = true;
nonInjectionManager.preDestroy(instance);
}
}
@SuppressWarnings("unchecked")
static <T> List<T> toInstances(List<InstanceContext<?>> instances, Annotation[] qualifiers) {
return instances != null
? instances.stream()
.filter(instance -> instance.hasQualifiers(qualifiers))
.map(pair -> (T) pair.getInstance())
.collect(Collectors.toList())
: null;
}
private static List<InstanceContext<?>> filterInstances(List<InstanceContext<?>> instances, Annotation... qualifiers) {
return instances != null
? instances.stream()
.filter(instance -> instance.hasQualifiers(qualifiers))
.collect(Collectors.toList())
: null;
}
private static List<InstanceContext<?>> merge(List<InstanceContext<?>> i1, List<InstanceContext<?>> i2) {
if (i1 == null) {
i1 = i2;
} else if (i2 != null) {
i1.addAll(i2);
}
return i1;
}
private boolean hasQualifiers(Annotation[] requested) {
if (requested != null) {
classLoop:
for (Annotation req : requested) {
if (createdWithQualifiers != null) {
for (Annotation cur : createdWithQualifiers) {
if (cur.annotationType().isInstance(req)) {
continue classLoop;
}
}
return false;
}
}
}
return true;
}
}
/**
* Singleton Binding this {@link NonInjectionManager} was supposed to be created based upon.
*/
private static final class InjectionManagerBinding extends Binding<InjectionManager, Binding<?, ?>> {
}
}