Skip to content

Commit

Permalink
ArC - introduce the concept of synthetic injection points
Browse files Browse the repository at this point in the history
- a synthetic injection point can be registered by a synthetic bean
- this injection point is validated at build time and considered when
detecting unused beans
- at runtime an injected reference is accessible through the
SyntheticCreationalContext
  • Loading branch information
mkouba committed Feb 15, 2023
1 parent 4179ed2 commit cc8074d
Show file tree
Hide file tree
Showing 27 changed files with 965 additions and 152 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package io.quarkus.arc.deployment;

import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.DotName;

import io.quarkus.arc.SyntheticCreationalContext;
import io.quarkus.arc.processor.BeanConfiguratorBase;
import io.quarkus.arc.processor.BeanRegistrar;
import io.quarkus.builder.item.MultiBuildItem;
Expand All @@ -15,8 +18,8 @@
/**
* Makes it possible to register a synthetic bean.
* <p>
* Bean instances can be easily produced through a recorder and set via {@link ExtendedBeanConfigurator#supplier(Supplier)} and
* {@link ExtendedBeanConfigurator#runtimeValue(RuntimeValue)}.
* Bean instances can be easily produced through a recorder and set via {@link ExtendedBeanConfigurator#supplier(Supplier)},
* {@link ExtendedBeanConfigurator#runtimeValue(RuntimeValue)} and {@link ExtendedBeanConfigurator#createWith(Function)}.
*
* @see ExtendedBeanConfigurator
* @see BeanRegistrar
Expand Down Expand Up @@ -58,7 +61,7 @@ boolean isStaticInit() {
}

boolean hasRecorderInstance() {
return configurator.supplier != null || configurator.runtimeValue != null;
return configurator.supplier != null || configurator.runtimeValue != null || configurator.fun != null;
}

/**
Expand All @@ -68,6 +71,7 @@ public static class ExtendedBeanConfigurator extends BeanConfiguratorBase<Extend

private Supplier<?> supplier;
private RuntimeValue<?> runtimeValue;
private Function<SyntheticCreationalContext<?>, ?> fun;
private boolean staticInit;

ExtendedBeanConfigurator(DotName implClazz) {
Expand All @@ -81,23 +85,53 @@ public static class ExtendedBeanConfigurator extends BeanConfiguratorBase<Extend
* @return a new build item
*/
public SyntheticBeanBuildItem done() {
if (supplier != null && runtimeValue != null) {
throw new IllegalStateException("It is not possible to specify both - a supplier and a runtime value");
}
if (creatorConsumer == null && supplier == null && runtimeValue == null) {
if (supplier == null && runtimeValue == null && fun == null && creatorConsumer == null) {
throw new IllegalStateException(
"Synthetic bean does not provide a creation method, use ExtendedBeanConfigurator#creator(), ExtendedBeanConfigurator#supplier() or ExtendedBeanConfigurator#runtimeValue()");
"Synthetic bean does not provide a creation method, use ExtendedBeanConfigurator#creator(), ExtendedBeanConfigurator#supplier(), ExtendedBeanConfigurator#createWith() or ExtendedBeanConfigurator#runtimeValue()");
}
return new SyntheticBeanBuildItem(this);
}

/**
* Use {@link #createWith(Function)} if you want to leverage build-time parameters or synthetic injection points.
*
* @param supplier A supplier returned from a recorder
* @return self
*/
public ExtendedBeanConfigurator supplier(Supplier<?> supplier) {
this.supplier = supplier;
if (runtimeValue != null || fun != null) {
throw multipleCreationMethods();
}
this.supplier = Objects.requireNonNull(supplier);
return this;
}

/**
* Use {@link #createWith(Function)} if you want to leverage build-time parameters or synthetic injection points.
*
* @param runtimeValue A runtime value returned from a recorder
* @return self
*/
public ExtendedBeanConfigurator runtimeValue(RuntimeValue<?> runtimeValue) {
this.runtimeValue = runtimeValue;
if (supplier != null || fun != null) {
throw multipleCreationMethods();
}
this.runtimeValue = Objects.requireNonNull(runtimeValue);
return this;
}

/**
* This method is useful if you need to use build-time parameters or synthetic injection points during creation of a
* bean instance.
*
* @param fun A function returned from a recorder
* @return self
*/
public <B> ExtendedBeanConfigurator createWith(Function<SyntheticCreationalContext<B>, B> fun) {
if (supplier != null || runtimeValue != null) {
throw multipleCreationMethods();
}
this.fun = cast(Objects.requireNonNull(fun));
return this;
}

Expand All @@ -107,8 +141,7 @@ public ExtendedBeanConfigurator runtimeValue(RuntimeValue<?> runtimeValue) {
* <p>
* It is possible to change this behavior and initialize the bean during the {@link ExecutionTime#RUNTIME_INIT}.
* However, in such case a client that attempts to obtain such bean during {@link ExecutionTime#STATIC_INIT} or before
* runtime-init synthetic beans are
* initialized will receive an exception.
* runtime-init synthetic beans are initialized will receive an exception.
* <p>
* {@link ExecutionTime#RUNTIME_INIT} build steps that access a runtime-init synthetic bean should consume the
* {@link SyntheticBeansRuntimeInitBuildItem}.
Expand Down Expand Up @@ -136,5 +169,14 @@ Supplier<?> getSupplier() {
RuntimeValue<?> getRuntimeValue() {
return runtimeValue;
}

Function<SyntheticCreationalContext<?>, ?> getFunction() {
return fun;
}

private IllegalStateException multipleCreationMethods() {
return new IllegalStateException("It is not possible to specify multiple creation methods");
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.function.Function;

import jakarta.enterprise.inject.CreationException;

import org.jboss.jandex.DotName;

import io.quarkus.arc.SyntheticCreationalContext;
import io.quarkus.arc.deployment.BeanRegistrationPhaseBuildItem.BeanConfiguratorBuildItem;
import io.quarkus.arc.processor.BeanConfigurator;
import io.quarkus.arc.runtime.ArcRecorder;
Expand All @@ -32,15 +33,15 @@ public class SyntheticBeansProcessor {
void initStatic(ArcRecorder recorder, List<SyntheticBeanBuildItem> syntheticBeans,
BeanRegistrationPhaseBuildItem beanRegistration, BuildProducer<BeanConfiguratorBuildItem> configurators) {

Map<String, Supplier<?>> suppliersMap = new HashMap<>();
Map<String, Function<SyntheticCreationalContext<?>, ?>> functionsMap = new HashMap<>();

for (SyntheticBeanBuildItem bean : syntheticBeans) {
if (bean.hasRecorderInstance() && bean.isStaticInit()) {
configureSyntheticBean(recorder, suppliersMap, beanRegistration, bean);
configureSyntheticBean(recorder, functionsMap, beanRegistration, bean);
}
}
// Init the map of bean instances
recorder.initStaticSupplierBeans(suppliersMap);
recorder.initStaticSupplierBeans(functionsMap);
}

@Record(ExecutionTime.RUNTIME_INIT)
Expand All @@ -49,14 +50,14 @@ void initStatic(ArcRecorder recorder, List<SyntheticBeanBuildItem> syntheticBean
ServiceStartBuildItem initRuntime(ArcRecorder recorder, List<SyntheticBeanBuildItem> syntheticBeans,
BeanRegistrationPhaseBuildItem beanRegistration, BuildProducer<BeanConfiguratorBuildItem> configurators) {

Map<String, Supplier<?>> suppliersMap = new HashMap<>();
Map<String, Function<SyntheticCreationalContext<?>, ?>> functionsMap = new HashMap<>();

for (SyntheticBeanBuildItem bean : syntheticBeans) {
if (bean.hasRecorderInstance() && !bean.isStaticInit()) {
configureSyntheticBean(recorder, suppliersMap, beanRegistration, bean);
configureSyntheticBean(recorder, functionsMap, beanRegistration, bean);
}
}
recorder.initRuntimeSupplierBeans(suppliersMap);
recorder.initRuntimeSupplierBeans(functionsMap);
return new ServiceStartBuildItem("runtime-bean-init");
}

Expand All @@ -71,14 +72,17 @@ void initRegular(List<SyntheticBeanBuildItem> syntheticBeans,
}
}

private void configureSyntheticBean(ArcRecorder recorder, Map<String, Supplier<?>> suppliersMap,
private void configureSyntheticBean(ArcRecorder recorder,
Map<String, Function<SyntheticCreationalContext<?>, ?>> functionsMap,
BeanRegistrationPhaseBuildItem beanRegistration, SyntheticBeanBuildItem bean) {
DotName implClazz = bean.configurator().getImplClazz();
String name = createName(implClazz.toString(), bean.configurator().getQualifiers().toString());
if (bean.configurator().getRuntimeValue() != null) {
suppliersMap.put(name, recorder.createSupplier(bean.configurator().getRuntimeValue()));
functionsMap.put(name, recorder.createFunction(bean.configurator().getRuntimeValue()));
} else if (bean.configurator().getSupplier() != null) {
suppliersMap.put(name, bean.configurator().getSupplier());
functionsMap.put(name, recorder.createFunction(bean.configurator().getSupplier()));
} else if (bean.configurator().getFunction() != null) {
functionsMap.put(name, bean.configurator().getFunction());
}
BeanConfigurator<?> configurator = beanRegistration.getContext().configure(implClazz)
.read(bean.configurator());
Expand All @@ -98,16 +102,16 @@ private Consumer<MethodCreator> creator(String name, SyntheticBeanBuildItem bean
@Override
public void accept(MethodCreator m) {
ResultHandle staticMap = m
.readStaticField(FieldDescriptor.of(ArcRecorder.class, "supplierMap", Map.class));
ResultHandle supplier = m.invokeInterfaceMethod(
.readStaticField(FieldDescriptor.of(ArcRecorder.class, "syntheticBeanProviders", Map.class));
ResultHandle function = m.invokeInterfaceMethod(
MethodDescriptor.ofMethod(Map.class, "get", Object.class, Object.class), staticMap,
m.load(name));
// Throw an exception if no supplier is found
m.ifNull(supplier).trueBranch().throwException(CreationException.class,
m.ifNull(function).trueBranch().throwException(CreationException.class,
createMessage(name, bean));
ResultHandle result = m.invokeInterfaceMethod(
MethodDescriptor.ofMethod(Supplier.class, "get", Object.class),
supplier);
MethodDescriptor.ofMethod(Function.class, "apply", Object.class, Object.class),
function, m.getMethodParam(0));
m.returnValue(result);
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

Expand All @@ -17,6 +18,7 @@
import io.quarkus.arc.CurrentContextFactory;
import io.quarkus.arc.InjectableBean;
import io.quarkus.arc.InjectableBean.Kind;
import io.quarkus.arc.SyntheticCreationalContext;
import io.quarkus.arc.impl.ArcContainerImpl;
import io.quarkus.arc.runtime.test.PreloadedTestApplicationClassPredicate;
import io.quarkus.runtime.ApplicationLifecycleManager;
Expand All @@ -36,7 +38,7 @@ public class ArcRecorder {
/**
* Used to hold the Supplier instances used for synthetic bean declarations.
*/
public static volatile Map<String, Supplier<?>> supplierMap;
public static volatile Map<String, Function<SyntheticCreationalContext<?>, ?>> syntheticBeanProviders;

public ArcContainer initContainer(ShutdownContext shutdown, RuntimeValue<CurrentContextFactory> currentContextFactory)
throws Exception {
Expand All @@ -54,12 +56,12 @@ public void initExecutor(ExecutorService executor) {
Arc.setExecutor(executor);
}

public void initStaticSupplierBeans(Map<String, Supplier<?>> beans) {
supplierMap = new ConcurrentHashMap<>(beans);
public void initStaticSupplierBeans(Map<String, Function<SyntheticCreationalContext<?>, ?>> beans) {
syntheticBeanProviders = new ConcurrentHashMap<>(beans);
}

public void initRuntimeSupplierBeans(Map<String, Supplier<?>> beans) {
supplierMap.putAll(beans);
public void initRuntimeSupplierBeans(Map<String, Function<SyntheticCreationalContext<?>, ?>> beans) {
syntheticBeanProviders.putAll(beans);
}

public BeanContainer initBeanContainer(ArcContainer container, List<BeanContainerListener> listeners)
Expand Down Expand Up @@ -107,15 +109,24 @@ public void run() {
});
}

public Supplier<Object> createSupplier(RuntimeValue<?> value) {
return new Supplier<Object>() {
public Function<SyntheticCreationalContext<?>, Object> createFunction(RuntimeValue<?> value) {
return new Function<SyntheticCreationalContext<?>, Object>() {
@Override
public Object get() {
public Object apply(SyntheticCreationalContext<?> t) {
return value.getValue();
}
};
}

public Function<SyntheticCreationalContext<?>, Object> createFunction(Supplier<?> supplier) {
return new Function<SyntheticCreationalContext<?>, Object>() {
@Override
public Object apply(SyntheticCreationalContext<?> t) {
return supplier.get();
}
};
}

public void initTestApplicationClassPredicate(Set<String> applicationBeanClasses) {
PreloadedTestApplicationClassPredicate predicate = Arc.container()
.instance(PreloadedTestApplicationClassPredicate.class).get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static io.quarkus.arc.processor.IndexClassLookupUtils.getClassByName;

import java.util.Collections;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
Expand Down Expand Up @@ -68,7 +69,7 @@ public void done() {
priority = Beans.initStereotypeAlternativePriority(stereotypes, implClass, beanDeployment);
}

beanConsumer.accept(new BeanInfo.Builder()
BeanInfo.Builder builder = new BeanInfo.Builder()
.implClazz(implClass)
.providerType(providerType)
.beanDeployment(beanDeployment)
Expand All @@ -85,8 +86,13 @@ public void done() {
.defaultBean(defaultBean)
.removable(removable)
.forceApplicationClass(forceApplicationClass)
.targetPackageName(targetPackageName)
.build());
.targetPackageName(targetPackageName);

if (!injectionPoints.isEmpty()) {
builder.injections(Collections.singletonList(Injection.forSyntheticBean(injectionPoints)));
}

beanConsumer.accept(builder.build());
}
}

Expand Down
Loading

0 comments on commit cc8074d

Please sign in to comment.