Skip to content

Commit

Permalink
4.x: Inject fixes (#9515)
Browse files Browse the repository at this point in the history
* Support for wildcard type when using generics - injection of Contract<?>
* Support for factory contracts. If a factory has a direct contract, we can also look it up from the registry.
* Support for explicit instances that honors factory instances (before we always injected the explicit instance, instead of getting factory provided instances).
* Align naming - moving from providers to factory.
  • Loading branch information
tomas-langer authored Nov 20, 2024
1 parent 6e4e3c4 commit 4019838
Show file tree
Hide file tree
Showing 16 changed files with 603 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
Expand Down Expand Up @@ -192,6 +193,11 @@ static void type(TypeName.BuilderBase<?, ?> builder, Type type) {
}
return;
}
if (reflectGenericType instanceof WildcardType) {
builder.className("?");
builder.wildcard(true);
return;
}

throw new IllegalArgumentException("We can only create a type from a class, GenericType, or a ParameterizedType,"
+ " but got: " + reflectGenericType.getClass().getName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ public final class TypeNames {
* Type name for {@link java.lang.annotation.Target}.
*/
public static final TypeName TARGET = TypeName.create(Target.class);
/**
* Wildcard type name, represented in code by {@code ?}.
*/
public static final TypeName WILDCARD = TypeName.builder()
.className("?")
.wildcard(true)
.build();

/*
Primitive types and their boxed counterparts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,14 +210,21 @@ public void addContracts(Set<ResolvedType> contractSet,

if (!resolvedType.type().typeArguments().isEmpty()) {
// we also need to add a contract for the type it implements
// i.e. if this is Circle<Green>, we may want to add Circle<Color> as well
// i.e. if this is Circle<Green>, we may want to add Circle<Color> as well, and Circle<?>
typeInfoFactory.apply(withGenerics.genericTypeName())
.ifPresent(declaration -> {
TypeName tn = declaration.typeName();
for (int i = 0; i < withGenerics.typeArguments().size(); i++) {
TypeName declared = tn.typeArguments().get(i);
if (declared.generic()) {
// this is not ideal (this could be T extends Circle)
// Circle<?>
contractSet.add(ResolvedType.create(
TypeName.builder()
.from(withGenerics)
.typeArguments(List.of(TypeNames.WILDCARD))
.build()));

// Circle<Color>
var asString = declared.toString();
int index = asString.indexOf(" extends ");
if (index != -1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@ default boolean matches(InjectServiceInfo serviceInfo) {
boolean matches = matches(serviceInfo.serviceType(), this.serviceType());
if (matches && this.serviceType().isEmpty()) {
matches = serviceInfo.contracts().containsAll(this.contracts())
|| this.contracts().contains(ResolvedType.create(serviceInfo.serviceType()));
|| this.contracts().contains(ResolvedType.create(serviceInfo.serviceType()))
|| serviceInfo.factoryContracts().containsAll(this.contracts());
}
return matches
&& matchesProviderTypes(factoryTypes(), serviceInfo.factoryType())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,18 @@ private Activators() {

@SuppressWarnings("unchecked")
static <T> Activator<T> create(ServiceProvider<T> provider, T instance) {
if (instance instanceof Supplier<?> supplier) {
return new FixedSupplierActivator<>(provider, (Supplier<T>) supplier);
} else {
return new Activators.FixedActivator<>(provider, instance);
}
return switch (provider.descriptor().factoryType()) {
case NONE, SERVICE, SUPPLIER -> {
if (instance instanceof Supplier<?> supplier) {
yield new FixedSupplierActivator<>(provider, (Supplier<T>) supplier);
} else {
yield new Activators.FixedActivator<>(provider, instance);
}
}
case SERVICES -> new Activators.FixedServicesFactoryActivator<>(provider, (Injection.ServicesFactory<T>) instance);
case INJECTION_POINT -> new Activators.FixedIpFactoryActivator<>(provider, (InjectionPointFactory<T>) instance);
case QUALIFIED -> new Activators.FixedQualifiedFactoryActivator<>(provider, (QualifiedFactory<T, ?>) instance);
};
}

static <T> Supplier<Activator<T>> create(InjectServiceRegistryImpl registry, ServiceProvider<T> provider) {
Expand All @@ -93,11 +100,11 @@ static <T> Supplier<Activator<T>> create(InjectServiceRegistryImpl registry, Ser
yield () -> new ActivatorsPerLookup.SingleServiceActivator<>(provider);
}
case SUPPLIER -> () -> new ActivatorsPerLookup.SupplierActivator<>(provider);
case SERVICES -> () -> new ActivatorsPerLookup.ServicesProviderActivator<>(provider);
case INJECTION_POINT -> () -> new ActivatorsPerLookup.IpProviderActivator<>(provider);
case SERVICES -> () -> new ActivatorsPerLookup.ServicesFactoryActivator<>(provider);
case INJECTION_POINT -> () -> new ActivatorsPerLookup.IpFactoryActivator<>(provider);
case QUALIFIED -> () ->
new ActivatorsPerLookup.QualifiedProviderActivator<>(provider,
(QualifiedFactoryDescriptor) descriptor);
new ActivatorsPerLookup.QualifiedFactoryActivator<>(provider,
(QualifiedFactoryDescriptor) descriptor);
};
} else {
return switch (descriptor.factoryType()) {
Expand All @@ -109,11 +116,11 @@ static <T> Supplier<Activator<T>> create(InjectServiceRegistryImpl registry, Ser
yield () -> new Activators.SingleServiceActivator<>(provider);
}
case SUPPLIER -> () -> new Activators.SupplierActivator<>(provider);
case SERVICES -> () -> new Activators.ServicesProviderActivator<>(provider);
case INJECTION_POINT -> () -> new Activators.IpProviderActivator<>(provider);
case SERVICES -> () -> new ServicesFactoryActivator<>(provider);
case INJECTION_POINT -> () -> new IpFactoryActivator<>(provider);
case QUALIFIED -> () ->
new Activators.QualifiedProviderActivator<>(provider,
(QualifiedFactoryDescriptor) descriptor);
new QualifiedFactoryActivator<>(provider,
(QualifiedFactoryDescriptor) descriptor);
};
}
}
Expand Down Expand Up @@ -283,8 +290,17 @@ protected boolean requestedProvider(Lookup lookup, FactoryType providerType) {
if (lookup.factoryTypes().contains(providerType)) {
return true;
}
if (lookup.contracts().size() == 1 && lookup.contracts().contains(ResolvedType.create(descriptor().serviceType()))) {
return true;
if (lookup.contracts().size() == 1) {
ResolvedType requestedContract = lookup.contracts().iterator().next();
if (requestedContract.equals(ResolvedType.create(descriptor().serviceType()))) {
// requested actual service
return true;
}
if (descriptor().factoryContracts().contains(requestedContract)
&& !descriptor().contracts().contains(requestedContract)) {
// requested a contract satisfied by the factory only
return true;
}
}
if (lookup.serviceType().isPresent() && lookup.serviceType().get().equals(descriptor().serviceType())) {
return true;
Expand Down Expand Up @@ -396,6 +412,31 @@ protected Optional<List<QualifiedInstance<T>>> targetInstances() {

}

static class FixedIpFactoryActivator<T> extends IpFactoryActivator<T> {

FixedIpFactoryActivator(ServiceProvider<T> provider,
InjectionPointFactory<T> instance) {
super(provider);
serviceInstance = InstanceHolder.create(instance);
}
}

static class FixedServicesFactoryActivator<T> extends ServicesFactoryActivator<T> {
FixedServicesFactoryActivator(ServiceProvider<T> provider,
Injection.ServicesFactory<T> factory) {
super(provider);
serviceInstance = InstanceHolder.create(factory);
}
}

static class FixedQualifiedFactoryActivator<T> extends QualifiedFactoryActivator<T> {
FixedQualifiedFactoryActivator(ServiceProvider<T> provider,
Injection.QualifiedFactory<T, ?> factory) {
super(provider, (QualifiedFactoryDescriptor) provider.descriptor());
serviceInstance = InstanceHolder.create(factory);
}
}

/**
* {@code MyService implements Contract}.
* Created for a service within each scope.
Expand Down Expand Up @@ -423,7 +464,10 @@ protected void construct(ActivationResult.Builder response) {
}
try {
lock.lock();
this.serviceInstance = InstanceHolder.create(provider, provider.injectionPlan());
if (serviceInstance == null) {
// it may have been set explicitly when creating registry
this.serviceInstance = InstanceHolder.create(provider, provider.injectionPlan());
}
this.serviceInstance.construct();
} finally {
lock.unlock();
Expand Down Expand Up @@ -497,14 +541,14 @@ protected void setTargetInstances() {
/**
* {@code MyService implements QualifiedProvider}.
*/
static class QualifiedProviderActivator<T> extends SingleServiceActivator<T> {
static class QualifiedFactoryActivator<T> extends SingleServiceActivator<T> {
static final GenericType<?> OBJECT_GENERIC_TYPE = GenericType.create(Object.class);

private final TypeName supportedQualifier;
private final Set<ResolvedType> supportedContracts;
private final boolean anyMatch;

QualifiedProviderActivator(ServiceProvider<T> provider, QualifiedFactoryDescriptor qpd) {
QualifiedFactoryActivator(ServiceProvider<T> provider, QualifiedFactoryDescriptor qpd) {
super(provider);
this.supportedQualifier = qpd.qualifierType();
this.supportedContracts = provider.descriptor()
Expand Down Expand Up @@ -560,8 +604,8 @@ private List<QualifiedInstance<T>> targetInstances(Lookup lookup, Qualifier qual
/**
* {@code MyService implements InjectionPointProvider}.
*/
static class IpProviderActivator<T> extends SingleServiceActivator<T> {
IpProviderActivator(ServiceProvider<T> provider) {
static class IpFactoryActivator<T> extends SingleServiceActivator<T> {
IpFactoryActivator(ServiceProvider<T> provider) {
super(provider);
}

Expand Down Expand Up @@ -596,8 +640,8 @@ protected Optional<List<QualifiedInstance<T>>> targetInstances(Lookup lookup) {
/**
* {@code MyService implements ServicesProvider}.
*/
static class ServicesProviderActivator<T> extends SingleServiceActivator<T> {
ServicesProviderActivator(ServiceProvider<T> provider) {
static class ServicesFactoryActivator<T> extends SingleServiceActivator<T> {
ServicesFactoryActivator(ServiceProvider<T> provider) {
super(provider);
}

Expand Down Expand Up @@ -806,48 +850,91 @@ static <T> QualifiedServiceInstance<T> create(ServiceProvider<T> provider, Servi
}
}

static class InstanceHolder<T> {
interface InstanceHolder<T> {
static <T> InstanceHolder<T> create(ServiceProvider<T> serviceProvider, Map<Dependency, IpPlan<?>> injectionPlan) {
// the same instance is returned for the lifetime of the service provider
return new InstanceHolderImpl<>(InjectionContext.create(injectionPlan),
serviceProvider.interceptionMetadata(),
serviceProvider.descriptor());
}

// we use the instance holder to hold either the actual instance,
// or the factory; this is a place for improvement
@SuppressWarnings("unchecked")
static <T> InstanceHolder<T> create(Object instance) {
return new FixedInstanceHolder<>((T) instance);
}

T get();

default void construct() {
}

default void inject() {
}

default void postConstruct() {
}

default void preDestroy() {
}
}

private static class FixedInstanceHolder<T> implements InstanceHolder<T> {
private final T instance;

private FixedInstanceHolder(T instance) {
this.instance = instance;
}

@Override
public T get() {
return instance;
}
}

private static class InstanceHolderImpl<T> implements InstanceHolder<T> {
private final DependencyContext ctx;
private final InterceptionMetadata interceptionMetadata;
private final InjectServiceDescriptor<T> source;

private volatile T instance;

private InstanceHolder(DependencyContext ctx,
private InstanceHolderImpl(DependencyContext ctx,
InterceptionMetadata interceptionMetadata,
InjectServiceDescriptor<T> source) {
this.ctx = ctx;
this.interceptionMetadata = interceptionMetadata;
this.source = source;
}

static <T> InstanceHolder<T> create(ServiceProvider<T> serviceProvider, Map<Dependency, IpPlan<?>> injectionPlan) {
// the same instance is returned for the lifetime of the service provider
return new InstanceHolder<>(InjectionContext.create(injectionPlan),
serviceProvider.interceptionMetadata(),
serviceProvider.descriptor());
}

T get() {

@Override
public T get() {
return instance;
}

@SuppressWarnings("unchecked")
void construct() {
@Override
public void construct() {
instance = (T) source.instantiate(ctx, interceptionMetadata);
}

void inject() {
@Override
public void inject() {
// using linked set, so we can see in debugging what was injected first
Set<String> injected = new LinkedHashSet<>();
source.inject(ctx, interceptionMetadata, injected, instance);
}

void postConstruct() {
@Override
public void postConstruct() {
source.postConstruct(instance);
}

void preDestroy() {
@Override
public void preDestroy() {
source.preDestroy(instance);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
import io.helidon.service.registry.ServiceInfo;
import io.helidon.service.registry.ServiceRegistryException;

import static io.helidon.service.inject.Activators.QualifiedProviderActivator.OBJECT_GENERIC_TYPE;
import static io.helidon.service.inject.Activators.QualifiedFactoryActivator.OBJECT_GENERIC_TYPE;
import static java.util.function.Predicate.not;

/*
Expand Down Expand Up @@ -138,12 +138,12 @@ protected Optional<List<QualifiedInstance<T>>> targetInstances() {
/**
* {@code MyService implements QualifiedProvider}.
*/
static class QualifiedProviderActivator<T> extends SingleServiceActivator<T> {
static class QualifiedFactoryActivator<T> extends SingleServiceActivator<T> {
private final TypeName supportedQualifier;
private final Set<ResolvedType> supportedContracts;
private final boolean anyMatch;

QualifiedProviderActivator(ServiceProvider<T> provider, GeneratedInjectService.QualifiedFactoryDescriptor qpd) {
QualifiedFactoryActivator(ServiceProvider<T> provider, GeneratedInjectService.QualifiedFactoryDescriptor qpd) {
super(provider);
this.supportedQualifier = qpd.qualifierType();
this.supportedContracts = provider.descriptor().contracts()
Expand Down Expand Up @@ -193,8 +193,8 @@ private List<QualifiedInstance<T>> targetInstances(Lookup lookup, Qualifier qual
/**
* {@code MyService implements InjectionPointProvider}.
*/
static class IpProviderActivator<T> extends SingleServiceActivator<T> {
IpProviderActivator(ServiceProvider<T> provider) {
static class IpFactoryActivator<T> extends SingleServiceActivator<T> {
IpFactoryActivator(ServiceProvider<T> provider) {
super(provider);
}

Expand Down Expand Up @@ -224,8 +224,8 @@ protected Optional<List<QualifiedInstance<T>>> targetInstances(Lookup lookup) {
/**
* {@code MyService implements ServicesProvider}.
*/
static class ServicesProviderActivator<T> extends SingleServiceActivator<T> {
ServicesProviderActivator(ServiceProvider<T> provider) {
static class ServicesFactoryActivator<T> extends SingleServiceActivator<T> {
ServicesFactoryActivator(ServiceProvider<T> provider) {
super(provider);
}

Expand Down
Loading

0 comments on commit 4019838

Please sign in to comment.