Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ArC: add support for custom AlterableContext implementations #34418

Merged
merged 1 commit into from
Jul 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import io.quarkus.arc.processor.BeanDeploymentValidator.ValidationContext;
import io.quarkus.arc.processor.BuildExtension.BuildContext;
import io.quarkus.arc.processor.BuildExtension.Key;
import io.quarkus.arc.processor.CustomAlterableContexts.CustomAlterableContextInfo;
import io.quarkus.arc.processor.ResourceOutput.Resource;
import io.quarkus.arc.processor.ResourceOutput.Resource.SpecialType;
import io.quarkus.arc.processor.bcextensions.ExtensionsEntryPoint;
Expand Down Expand Up @@ -86,11 +87,13 @@ public static Builder builder() {
protected final Predicate<DotName> injectionPointAnnotationsPredicate;

private final ExtensionsEntryPoint buildCompatibleExtensions;
private final CustomAlterableContexts customAlterableContexts; // generic but currently only used for BCE

private BeanProcessor(Builder builder) {
this.buildCompatibleExtensions = builder.buildCompatibleExtensions;
this.customAlterableContexts = new CustomAlterableContexts(builder.applicationClassPredicate);
if (buildCompatibleExtensions != null) {
buildCompatibleExtensions.registerMetaAnnotations(builder);
buildCompatibleExtensions.registerMetaAnnotations(builder, customAlterableContexts);
buildCompatibleExtensions.runEnhancement(builder.beanArchiveComputingIndex, builder);
}

Expand Down Expand Up @@ -162,6 +165,7 @@ public void initialize(Consumer<BytecodeTransformer> bytecodeTransformerConsumer
*/
public BeanDeploymentValidator.ValidationContext validate(Consumer<BytecodeTransformer> bytecodeTransformerConsumer) {
ValidationContext validationContext = beanDeployment.validate(beanDeploymentValidators, bytecodeTransformerConsumer);
customAlterableContexts.validate(validationContext, transformUnproxyableClasses, bytecodeTransformerConsumer);
if (buildCompatibleExtensions != null) {
buildCompatibleExtensions.runValidation(beanDeployment.getBeanArchiveIndex(),
validationContext.get(Key.BEANS), validationContext.get(Key.OBSERVERS));
Expand Down Expand Up @@ -225,6 +229,9 @@ public List<Resource> generateResources(ReflectionRegistration reflectionRegistr
observerGenerator.precomputeGeneratedName(observer);
}

CustomAlterableContextsGenerator alterableContextsGenerator = new CustomAlterableContextsGenerator(generateSources);
List<CustomAlterableContextInfo> alterableContexts = customAlterableContexts.getRegistered();

List<Resource> resources = new ArrayList<>();

if (executor != null) {
Expand Down Expand Up @@ -333,6 +340,16 @@ public Collection<Resource> call() throws Exception {
}));
}

// Generate `_InjectableContext` subclasses for custom `AlterableContext`s
for (CustomAlterableContextInfo info : alterableContexts) {
primaryTasks.add(executor.submit(new Callable<Collection<Resource>>() {
@Override
public Collection<Resource> call() throws Exception {
return alterableContextsGenerator.generate(info);
}
}));
}

for (Future<Collection<Resource>> future : primaryTasks) {
resources.addAll(future.get());
}
Expand Down Expand Up @@ -391,6 +408,11 @@ public Collection<Resource> call() throws Exception {
resources.addAll(observerGenerator.generate(observer));
}

// Generate `_InjectableContext` subclasses for custom `AlterableContext`s
for (CustomAlterableContextInfo info : alterableContexts) {
resources.addAll(alterableContextsGenerator.generate(info));
}

// Generate _ComponentsProvider
resources.addAll(
new ComponentsProviderGenerator(annotationLiterals, generateSources, detectUnusedFalsePositives).generate(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package io.quarkus.arc.processor;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;

import jakarta.enterprise.context.spi.AlterableContext;
import jakarta.enterprise.inject.spi.DeploymentException;

import org.jboss.jandex.DotName;

public class CustomAlterableContexts {
Ladicek marked this conversation as resolved.
Show resolved Hide resolved
private final List<CustomAlterableContextInfo> registered = new ArrayList<>();
private final Predicate<DotName> applicationClassPredicate;

CustomAlterableContexts(Predicate<DotName> applicationClassPredicate) {
this.applicationClassPredicate = applicationClassPredicate;
}

public CustomAlterableContextInfo add(Class<? extends AlterableContext> contextClass, Boolean isNormal) {
String generatedName = contextClass.getName() + "_InjectableContext";
boolean isApplicationClass = applicationClassPredicate.test(DotName.createSimple(contextClass));
CustomAlterableContextInfo result = new CustomAlterableContextInfo(contextClass, isNormal, generatedName,
isApplicationClass);
registered.add(result);
return result;
}

void validate(BeanDeploymentValidator.ValidationContext validationContext, boolean transformUnproxyableClasses,
Consumer<BytecodeTransformer> bytecodeTransformerConsumer) {
for (CustomAlterableContextInfo info : registered) {
if (Modifier.isFinal(info.contextClass.getModifiers())) {
if (transformUnproxyableClasses) {
bytecodeTransformerConsumer.accept(new BytecodeTransformer(info.contextClass.getName(),
new Beans.FinalClassTransformFunction()));
} else {
validationContext.addDeploymentProblem(
new DeploymentException("Custom context class may not be final: " + info.contextClass));
}
}
}
}

List<CustomAlterableContextInfo> getRegistered() {
return registered;
}

public static class CustomAlterableContextInfo {
public final Class<? extends AlterableContext> contextClass;
public final Boolean isNormal;
public final String generatedName;
public final boolean isApplicationClass;

CustomAlterableContextInfo(Class<? extends AlterableContext> contextClass, Boolean isNormal,
String generatedName, boolean isApplicationClass) {
this.contextClass = contextClass;
this.isNormal = isNormal;
this.generatedName = generatedName;
this.isApplicationClass = isApplicationClass;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package io.quarkus.arc.processor;

import java.util.Collection;

import org.jboss.logging.Logger;

import io.quarkus.arc.InjectableContext;
import io.quarkus.arc.processor.CustomAlterableContexts.CustomAlterableContextInfo;
import io.quarkus.arc.processor.ResourceOutput.Resource;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.ClassOutput;
import io.quarkus.gizmo.MethodCreator;
import io.quarkus.gizmo.MethodDescriptor;

/**
* This is an internal companion of {@link CustomAlterableContexts} that handles generating
* subclasses of given context classes that implement {@code InjectableContext}.
*/
class CustomAlterableContextsGenerator extends AbstractGenerator {
private static final Logger LOGGER = Logger.getLogger(CustomAlterableContextsGenerator.class);

CustomAlterableContextsGenerator(boolean generateSources) {
super(generateSources);
}

/**
* Creator of an {@link CustomAlterableContexts} must call this method at an appropriate point
* in time and write the result to an appropriate output. If not, the bytecode sequences generated
* using the result of {@code CustomAlterableContexts.add()} will refer to non-existing classes.
*
* @return the generated classes, never {@code null}
*/
Collection<Resource> generate(CustomAlterableContexts.CustomAlterableContextInfo info) {
ResourceClassOutput classOutput = new ResourceClassOutput(info.isApplicationClass, generateSources);
createInjectableContextSubclass(classOutput, info);
return classOutput.getResources();
}

private void createInjectableContextSubclass(ClassOutput classOutput, CustomAlterableContextInfo info) {
String generatedName = info.generatedName.replace('.', '/');

ClassCreator injectableContextSubclass = ClassCreator.builder()
.classOutput(classOutput)
.className(generatedName)
.superClass(info.contextClass)
.interfaces(InjectableContext.class)
.build();

// constructor
MethodCreator constructor = injectableContextSubclass.getMethodCreator(Methods.INIT, void.class);
constructor.invokeSpecialMethod(MethodDescriptor.ofConstructor(info.contextClass), constructor.getThis());
constructor.returnVoid();

// implement `isNormal()` if needed
if (info.isNormal != null) {
MethodCreator isNormal = injectableContextSubclass.getMethodCreator("isNormal", boolean.class);
isNormal.returnBoolean(info.isNormal);
}

// implement `destroy()`
MethodCreator destroy = injectableContextSubclass.getMethodCreator("destroy", void.class);
destroy.throwException(UnsupportedOperationException.class, "Custom AlterableContext cannot destroy all instances");
destroy.returnVoid();

// implement `getState()`
MethodCreator getState = injectableContextSubclass.getMethodCreator("getState", InjectableContext.ContextState.class);
getState.throwException(UnsupportedOperationException.class, "Custom AlterableContext has no state");
getState.returnNull();

// implement `destroy(ContextState)`
MethodCreator destroyState = injectableContextSubclass.getMethodCreator("destroy", void.class,
InjectableContext.ContextState.class);
destroyState.throwException(UnsupportedOperationException.class, "Custom AlterableContext has no state");
destroyState.returnVoid();

injectableContextSubclass.close();
LOGGER.debugf("InjectableContext subclass generated: %s", info.generatedName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.util.stream.Collectors;

import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.context.spi.AlterableContext;
import jakarta.enterprise.context.spi.CreationalContext;
import jakarta.enterprise.event.TransactionPhase;
import jakarta.enterprise.inject.Instance;
Expand Down Expand Up @@ -45,6 +46,8 @@
import io.quarkus.arc.processor.ConfiguratorBase;
import io.quarkus.arc.processor.ContextConfigurator;
import io.quarkus.arc.processor.ContextRegistrar;
import io.quarkus.arc.processor.CustomAlterableContexts;
import io.quarkus.arc.processor.CustomAlterableContexts.CustomAlterableContextInfo;
import io.quarkus.arc.processor.InterceptorBindingRegistrar;
import io.quarkus.arc.processor.ObserverConfigurator;
import io.quarkus.arc.processor.ObserverInfo;
Expand Down Expand Up @@ -144,7 +147,7 @@ public void runDiscovery(org.jboss.jandex.IndexView applicationIndex, Set<String
* <p>
* It is a no-op if no {@link BuildCompatibleExtension} was found.
*/
public void registerMetaAnnotations(BeanProcessor.Builder builder) {
public void registerMetaAnnotations(BeanProcessor.Builder builder, CustomAlterableContexts customAlterableContexts) {
if (invoker.isEmpty()) {
return;
}
Expand Down Expand Up @@ -212,11 +215,17 @@ public Set<DotName> getAdditionalStereotypes() {
@Override
public void register(RegistrationContext registrationContext) {
Class<? extends Annotation> scopeAnnotation = context.scopeAnnotation;
// TODO how many changes in ArC will be needed to support AlterableContext?
Class<? extends InjectableContext> contextClass = (Class) context.contextClass;

ContextConfigurator config = registrationContext.configure(scopeAnnotation)
.contextClass(contextClass);
Class<? extends AlterableContext> contextClass = context.contextClass;

ContextConfigurator config = registrationContext.configure(scopeAnnotation);
if (InjectableContext.class.isAssignableFrom(contextClass)) {
config.contextClass((Class<? extends InjectableContext>) contextClass);
} else {
CustomAlterableContextInfo info = customAlterableContexts.add(contextClass, context.isNormal);
config.creator(bytecode -> {
return bytecode.newInstance(MethodDescriptor.ofConstructor(info.generatedName));
});
}
if (context.isNormal != null) {
config.normal(context.isNormal);
}
Expand Down
Loading