Skip to content

Commit

Permalink
ArC: initial support for inactive synthetic beans
Browse files Browse the repository at this point in the history
  • Loading branch information
Ladicek committed Jul 24, 2024
1 parent ec8e234 commit 980947b
Show file tree
Hide file tree
Showing 14 changed files with 406 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.jboss.jandex.MethodInfo;
import org.jboss.logging.Logger;

import io.quarkus.arc.ActiveResult;
import io.quarkus.arc.Arc;
import io.quarkus.arc.ArcContainer;
import io.quarkus.arc.ClientProxy;
Expand All @@ -37,6 +38,7 @@
import io.quarkus.arc.processor.BuildExtension;
import io.quarkus.arc.processor.BuiltinScope;
import io.quarkus.arc.processor.DotNames;
import io.quarkus.arc.processor.InjectionPointInfo;
import io.quarkus.arc.processor.ObserverConfigurator;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
Expand Down Expand Up @@ -179,6 +181,30 @@ private void registerStartupObserver(ObserverRegistrationPhaseBuildItem observer
ResultHandle containerHandle = mc.invokeStaticMethod(ARC_CONTAINER);
ResultHandle beanHandle = mc.invokeInterfaceMethod(ARC_CONTAINER_BEAN, containerHandle,
mc.load(bean.getIdentifier()));

// if the [synthetic] bean is not active and is not injected in a user bean, skip obtaining the instance
// this means that an inactive bean that is injected into a user bean will end up with an error
if (bean.canBeInactive()) {
boolean isInjectedInUserBean = false;
for (InjectionPointInfo ip : observerRegistration.getBeanProcessor().getBeanDeployment().getInjectionPoints()) {
if (bean.equals(ip.getResolvedBean()) && ip.getTargetBean().isPresent()
&& !ip.getTargetBean().get().isSynthetic()) {
isInjectedInUserBean = true;
break;
}
}

if (!isInjectedInUserBean) {
ResultHandle activeResult = mc.invokeInterfaceMethod(
MethodDescriptor.ofMethod(InjectableBean.class, "isActive", ActiveResult.class),
beanHandle);
ResultHandle isActive = mc.invokeVirtualMethod(
MethodDescriptor.ofMethod(ActiveResult.class, "result", boolean.class),
activeResult);
mc.ifFalse(isActive).trueBranch().returnVoid();
}
}

if (BuiltinScope.DEPENDENT.is(bean.getScope())) {
// It does not make a lot of sense to support @Startup dependent beans but it's still a valid use case
ResultHandle creationalContext = mc.newInstance(
Expand Down Expand Up @@ -212,7 +238,7 @@ private void registerStartupObserver(ObserverRegistrationPhaseBuildItem observer
mc.invokeInterfaceMethod(CLIENT_PROXY_CONTEXTUAL_INSTANCE, proxyHandle);
}
}
mc.returnValue(null);
mc.returnVoid();
});
configurator.done();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

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

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

import io.quarkus.arc.ActiveResult;
import io.quarkus.arc.SyntheticCreationalContext;
import io.quarkus.arc.processor.BeanConfiguratorBase;
import io.quarkus.arc.processor.BeanRegistrar;
Expand Down Expand Up @@ -68,6 +70,10 @@ boolean hasRecorderInstance() {
|| configurator.runtimeProxy != null;
}

boolean hasIsActiveSupplier() {
return configurator.isActive != null;
}

/**
* This construct is not thread-safe and should not be reused.
*/
Expand All @@ -79,6 +85,8 @@ public static class ExtendedBeanConfigurator extends BeanConfiguratorBase<Extend
private Function<SyntheticCreationalContext<?>, ?> fun;
private boolean staticInit;

private Supplier<ActiveResult> isActive;

ExtendedBeanConfigurator(DotName implClazz) {
super(implClazz);
this.staticInit = true;
Expand All @@ -92,7 +100,11 @@ public static class ExtendedBeanConfigurator extends BeanConfiguratorBase<Extend
public SyntheticBeanBuildItem done() {
if (supplier == null && runtimeValue == null && fun == null && runtimeProxy == null && creatorConsumer == null) {
throw new IllegalStateException(
"Synthetic bean does not provide a creation method, use ExtendedBeanConfigurator#creator(), ExtendedBeanConfigurator#supplier(), ExtendedBeanConfigurator#createWith() or ExtendedBeanConfigurator#runtimeValue()");
"Synthetic bean does not provide a creation method, use ExtendedBeanConfigurator#creator(), ExtendedBeanConfigurator#supplier(), ExtendedBeanConfigurator#createWith() or ExtendedBeanConfigurator#runtimeValue()");
}
if (isActive != null && supplier == null && runtimeValue == null && fun == null && runtimeProxy == null) {
throw new IllegalStateException(
"Synthetic bean has ExtendedBeanConfigurator#isActive(), but does not have ExtendedBeanConfigurator#supplier() / createWith() / runtimeValue() / runtimeProxy()");
}
return new SyntheticBeanBuildItem(this);
}
Expand Down Expand Up @@ -181,6 +193,20 @@ public ExtendedBeanConfigurator setRuntimeInit() {
return this;
}

/**
* The {@link #isActive(Consumer)} procedure is a {@code Supplier<ActiveResult>} proxy
* returned from a recorder method.
*
* @param isActive a {@code Supplier<ActiveResult>} returned from a recorder method
* @return self
* @throws IllegalArgumentException if the {@code isActive} argument is not a proxy returned from a recorder method
*/
public ExtendedBeanConfigurator isActive(Supplier<ActiveResult> isActive) {
checkReturnedProxy(isActive);
this.isActive = Objects.requireNonNull(isActive);
return this;
}

DotName getImplClazz() {
return implClazz;
}
Expand Down Expand Up @@ -213,6 +239,10 @@ Object getRuntimeProxy() {
return runtimeProxy;
}

Supplier<ActiveResult> getIsActive() {
return isActive;
}

private void checkMultipleCreationMethods() {
if (runtimeProxy == null && runtimeValue == null && supplier == null && fun == null) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

import jakarta.enterprise.inject.CreationException;

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

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

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

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

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

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

Expand All @@ -66,29 +70,34 @@ void initRegular(List<SyntheticBeanBuildItem> syntheticBeans,

for (SyntheticBeanBuildItem bean : syntheticBeans) {
if (!bean.hasRecorderInstance()) {
configureSyntheticBean(null, null, beanRegistration, bean);
configureSyntheticBean(null, null, null, beanRegistration, bean);
}
}
}

private void configureSyntheticBean(ArcRecorder recorder,
Map<String, Function<SyntheticCreationalContext<?>, ?>> functionsMap,
BeanRegistrationPhaseBuildItem beanRegistration, SyntheticBeanBuildItem bean) {
Map<String, Function<SyntheticCreationalContext<?>, ?>> creationFunctions,
Map<String, Supplier<ActiveResult>> isActiveSuppliers, BeanRegistrationPhaseBuildItem beanRegistration,
SyntheticBeanBuildItem bean) {
String name = createName(bean.configurator());
if (bean.configurator().getRuntimeValue() != null) {
functionsMap.put(name, recorder.createFunction(bean.configurator().getRuntimeValue()));
creationFunctions.put(name, recorder.createFunction(bean.configurator().getRuntimeValue()));
} else if (bean.configurator().getSupplier() != null) {
functionsMap.put(name, recorder.createFunction(bean.configurator().getSupplier()));
creationFunctions.put(name, recorder.createFunction(bean.configurator().getSupplier()));
} else if (bean.configurator().getFunction() != null) {
functionsMap.put(name, bean.configurator().getFunction());
creationFunctions.put(name, bean.configurator().getFunction());
} else if (bean.configurator().getRuntimeProxy() != null) {
functionsMap.put(name, recorder.createFunction(bean.configurator().getRuntimeProxy()));
creationFunctions.put(name, recorder.createFunction(bean.configurator().getRuntimeProxy()));
}
BeanConfigurator<?> configurator = beanRegistration.getContext().configure(bean.configurator().getImplClazz())
.read(bean.configurator());
if (bean.hasRecorderInstance()) {
configurator.creator(creator(name, bean));
}
if (bean.hasIsActiveSupplier()) {
configurator.isActive(isActive(name, bean));
isActiveSuppliers.put(name, bean.configurator().getIsActive());
}
configurator.done();
}

Expand Down Expand Up @@ -118,6 +127,25 @@ public void accept(MethodCreator m) {
};
}

private Consumer<MethodCreator> isActive(String name, SyntheticBeanBuildItem bean) {
return new Consumer<MethodCreator>() {
@Override
public void accept(MethodCreator mc) {
ResultHandle staticMap = mc.readStaticField(
FieldDescriptor.of(ArcRecorder.class, "syntheticBeanIsActive", Map.class));
ResultHandle supplier = mc.invokeInterfaceMethod(
MethodDescriptor.ofMethod(Map.class, "get", Object.class, Object.class),
staticMap, mc.load(name));
// TODO different message, possibly even different exception
mc.ifNull(supplier).trueBranch().throwException(CreationException.class,
createMessage(name, bean));
mc.returnValue(mc.invokeInterfaceMethod(
MethodDescriptor.ofMethod(Supplier.class, "get", Object.class),
supplier));
}
};
}

private String createMessage(String name, SyntheticBeanBuildItem bean) {
StringBuilder builder = new StringBuilder();
builder.append("Synthetic bean instance for ");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import org.jboss.logging.Logger;

import io.quarkus.arc.ActiveResult;
import io.quarkus.arc.Arc;
import io.quarkus.arc.ArcContainer;
import io.quarkus.arc.ArcInitConfig;
Expand Down Expand Up @@ -41,6 +42,8 @@ public class ArcRecorder {
*/
public static volatile Map<String, Function<SyntheticCreationalContext<?>, ?>> syntheticBeanProviders;

public static volatile Map<String, Supplier<ActiveResult>> syntheticBeanIsActive;

public ArcContainer initContainer(ShutdownContext shutdown, RuntimeValue<CurrentContextFactory> currentContextFactory,
boolean strictCompatibility) throws Exception {
ArcInitConfig.Builder builder = ArcInitConfig.builder();
Expand All @@ -61,12 +64,16 @@ public void initExecutor(ExecutorService executor) {
Arc.setExecutor(executor);
}

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

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

public BeanContainer initBeanContainer(ArcContainer container, List<BeanContainerListener> listeners)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ public void done() {
.forceApplicationClass(forceApplicationClass)
.targetPackageName(targetPackageName)
.startupPriority(startupPriority)
.interceptionProxy(interceptionProxy);
.interceptionProxy(interceptionProxy)
.isActive(isActiveConsumer);

if (!injectionPoints.isEmpty()) {
builder.injections(Collections.singletonList(Injection.forSyntheticBean(injectionPoints)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;

import jakarta.enterprise.context.NormalScope;
import jakarta.enterprise.context.spi.CreationalContext;
Expand All @@ -22,6 +23,7 @@
import org.jboss.jandex.Type;
import org.jboss.jandex.Type.Kind;

import io.quarkus.arc.ActiveResult;
import io.quarkus.arc.BeanCreator;
import io.quarkus.arc.BeanDestroyer;
import io.quarkus.arc.InjectableBean;
Expand Down Expand Up @@ -59,6 +61,7 @@ public abstract class BeanConfiguratorBase<THIS extends BeanConfiguratorBase<THI
protected final Set<TypeAndQualifiers> injectionPoints;
protected Integer startupPriority;
protected InterceptionProxyInfo interceptionProxy;
protected Consumer<MethodCreator> isActiveConsumer;

protected BeanConfiguratorBase(DotName implClazz) {
this.implClazz = implClazz;
Expand Down Expand Up @@ -101,6 +104,7 @@ public THIS read(BeanConfiguratorBase<?, ?> base) {
injectionPoints.addAll(base.injectionPoints);
startupPriority = base.startupPriority;
interceptionProxy = base.interceptionProxy;
isActiveConsumer = base.isActiveConsumer;
return self();
}

Expand Down Expand Up @@ -391,6 +395,20 @@ public THIS destroyer(Consumer<MethodCreator> methodCreatorConsumer) {
return cast(this);
}

public THIS isActive(Class<? extends Supplier<ActiveResult>> isActiveClazz) {
return isActive(mc -> {
// return new FooActiveResultSupplier().get()
ResultHandle supplierHandle = mc.newInstance(MethodDescriptor.ofConstructor(isActiveClazz));
mc.returnValue(mc.invokeInterfaceMethod(MethodDescriptor.ofMethod(Supplier.class, "get", Object.class),
supplierHandle));
});
}

public THIS isActive(Consumer<MethodCreator> methodCreatorConsumer) {
this.isActiveConsumer = methodCreatorConsumer;
return cast(this);
}

/**
* The identifier becomes part of the {@link BeanInfo#getIdentifier()} and {@link InjectableBean#getIdentifier()}.
* <p>
Expand Down
Loading

0 comments on commit 980947b

Please sign in to comment.