Skip to content

Commit

Permalink
support @decorated bean injection in decorator
Browse files Browse the repository at this point in the history
  • Loading branch information
rmanibus committed Oct 14, 2024
1 parent 8d1764e commit 2689132
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import jakarta.enterprise.context.Initialized;
import jakarta.enterprise.context.control.ActivateRequestContext;
import jakarta.enterprise.inject.Any;
import jakarta.enterprise.inject.Decorated;
import jakarta.enterprise.inject.Default;
import jakarta.enterprise.inject.Intercepted;
import jakarta.enterprise.inject.Model;
Expand Down Expand Up @@ -82,6 +83,7 @@ private static IndexView buildAdditionalIndex() {
index(indexer, BeforeDestroyed.class.getName());
index(indexer, Destroyed.class.getName());
index(indexer, Intercepted.class.getName());
index(indexer, Decorated.class.getName());
index(indexer, Model.class.getName());
index(indexer, Lock.class.getName());
index(indexer, All.class.getName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.util.function.Predicate;
import java.util.function.Supplier;

import io.quarkus.arc.impl.DecoratedBeanMetadataProvider;
import jakarta.enterprise.inject.spi.DefinitionException;
import jakarta.enterprise.inject.spi.InjectionPoint;

Expand Down Expand Up @@ -57,6 +58,11 @@ public enum BuiltinBean {
&& ip.getRequiredQualifiers().size() == 1
&& ip.getRequiredQualifiers().iterator().next().name().equals(DotNames.INTERCEPTED),
BuiltinBean::validateInterceptedBean, DotNames.BEAN),
DECORATED_BEAN(BuiltinBean::generateDecoratedBeanBytecode,
(ip, names) -> cdiAndRawTypeMatches(ip, DotNames.BEAN, DotNames.INJECTABLE_BEAN) && !ip.hasDefaultedQualifier()
&& ip.getRequiredQualifiers().size() == 1
&& ip.getRequiredQualifiers().iterator().next().name().equals(DotNames.DECORATED),
BuiltinBean::validateDecoratedBean, DotNames.BEAN),
BEAN_MANAGER(BuiltinBean::generateBeanManagerBytecode, DotNames.BEAN_MANAGER, DotNames.BEAN_CONTAINER),
EVENT(BuiltinBean::generateEventBytecode, DotNames.EVENT),
RESOURCE(BuiltinBean::generateResourceBytecode, (ip, names) -> ip.getKind() == InjectionPointKind.RESOURCE,
Expand Down Expand Up @@ -321,6 +327,19 @@ private static void generateInterceptedBeanBytecode(GeneratorContext ctx) {
interceptedBeanMetadataProviderSupplier);
}

private static void generateDecoratedBeanBytecode(GeneratorContext ctx) {
ResultHandle decoratedBeanMetadataProvider = ctx.constructor
.newInstance(MethodDescriptor.ofConstructor(DecoratedBeanMetadataProvider.class));

ResultHandle decoratedBeanMetadataProviderSupplier = ctx.constructor.newInstance(
MethodDescriptors.FIXED_VALUE_SUPPLIER_CONSTRUCTOR, decoratedBeanMetadataProvider);
ctx.constructor.writeInstanceField(
FieldDescriptor.of(ctx.clazzCreator.getClassName(), ctx.providerName,
Supplier.class.getName()),
ctx.constructor.getThis(),
decoratedBeanMetadataProviderSupplier);
}

private static void generateBeanManagerBytecode(GeneratorContext ctx) {
ResultHandle beanManagerProvider = ctx.constructor.newInstance(
MethodDescriptor.ofConstructor(BeanManagerProvider.class));
Expand Down Expand Up @@ -515,6 +534,13 @@ private static void validateInterceptedBean(ValidatorContext ctx) {
}
}

private static void validateDecoratedBean(ValidatorContext ctx) {
if (ctx.injectionTarget.kind() != InjectionTargetInfo.TargetKind.BEAN
|| !ctx.injectionTarget.asBean().isDecorator()) {
ctx.errors.accept(new DefinitionException("Only interceptors can access intercepted bean metadata"));
}
}

private static void validateEventMetadata(ValidatorContext ctx) {
if (ctx.injectionTarget.kind() != TargetKind.OBSERVER) {
ctx.errors.accept(new DefinitionException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import jakarta.enterprise.event.TransactionPhase;
import jakarta.enterprise.inject.Alternative;
import jakarta.enterprise.inject.Any;
import jakarta.enterprise.inject.Decorated;
import jakarta.enterprise.inject.Default;
import jakarta.enterprise.inject.Disposes;
import jakarta.enterprise.inject.Instance;
Expand Down Expand Up @@ -133,6 +134,7 @@ public final class DotNames {
public static final DotName INVOCATION_CONTEXT = create(InvocationContext.class);
public static final DotName ARC_INVOCATION_CONTEXT = create(ArcInvocationContext.class);
public static final DotName DECORATOR = create(Decorator.class);
public static final DotName DECORATED = create(Decorated.class);
public static final DotName DELEGATE = create(Delegate.class);
public static final DotName SERIALIZABLE = create(Serializable.class);
public static final DotName UNREMOVABLE = create(Unremovable.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@ private static void validateInjections(InjectionPointInfo injectionPointInfo, Be
"but was detected in: " + injectionPointInfo.getTargetInfo());
}

// If a Bean instance with qualifier @Decorated is injected into a bean instance other than an decorator
// instance, the container automatically detects the problem and treats it as a definition error.
if (injectionPointInfo.getType().name().equals(DotNames.BEAN)
&& injectionPointInfo.getRequiredQualifier(DotNames.DECORATED) != null) {
throw new DefinitionException(
"Invalid injection of @Decorated Bean<T>, can only be injected into decorators " +
"but was detected in: " + injectionPointInfo.getTargetInfo());
}

// the injection point is a field, an initializer method parameter or a bean constructor, with qualifier
// @Default, then the type parameter of the injected Bean, or Interceptor must be the same as the type
// declaring the injection point
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.quarkus.arc.impl;

import io.quarkus.arc.InjectableReferenceProvider;
import jakarta.enterprise.context.spi.Contextual;
import jakarta.enterprise.context.spi.CreationalContext;
import jakarta.enterprise.inject.Decorated;
import jakarta.enterprise.inject.Intercepted;
import jakarta.enterprise.inject.spi.Bean;

import static io.quarkus.arc.impl.CreationalContextImpl.unwrap;

/**
* {@link Decorated} {@link Bean} metadata provider.
*/
public class DecoratedBeanMetadataProvider implements InjectableReferenceProvider<Contextual<?>> {

@Override
public Contextual<?> get(CreationalContext<Contextual<?>> creationalContext) {
// First attempt to obtain the creational context of the decorator bean and then the creational context of the decorated bean
CreationalContextImpl<?> parent = unwrap(creationalContext).getParent();
if (parent != null) {
parent = parent.getParent();
return parent != null ? parent.getContextual() : null;
}
return null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package io.quarkus.arc.test.decorators.decorated;

import io.quarkus.arc.Arc;
import io.quarkus.arc.test.ArcTestContainer;
import io.quarkus.arc.test.MyQualifier;
import jakarta.annotation.Priority;
import jakarta.decorator.Decorator;
import jakarta.decorator.Delegate;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.inject.Any;
import jakarta.enterprise.inject.Decorated;
import jakarta.enterprise.inject.spi.Bean;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import java.util.Comparator;

public class DecoratedTest {

@RegisterExtension
public ArcTestContainer container = new ArcTestContainer(Converter.class, DecoratedBean.class,
TrimConverterDecorator.class, MyQualifier.class);

interface Converter<T> {

T convert(T value);

}

@Test
public void testDecoration() {
DecoratedBean bean = Arc.container().instance(DecoratedBean.class, new MyQualifier.Literal()).get();
Assertions.assertEquals(
DecoratedBean.class.getName() + " [@io.quarkus.arc.test.MyQualifier(), @jakarta.enterprise.inject.Any()]",
bean.convert("any value"));
}

@ApplicationScoped
@MyQualifier
static class DecoratedBean implements Converter<String> {

@Override
public String convert(String value) {
return "Replaced by the decorator";
}

}

@Dependent
@Priority(1)
@Decorator
static class TrimConverterDecorator implements Converter<String> {

@Inject
@Any
@Delegate
Converter<String> delegate;

@Inject
@Decorated
Bean<?> decorated;

@Override
public String convert(String value) {
return decorated.getBeanClass().getName() + " " + decorated.getQualifiers().stream()
.sorted(Comparator.comparing(a -> a.annotationType().getName())).toList();
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package io.quarkus.arc.test.decorators.decorated;

import io.quarkus.arc.Arc;
import io.quarkus.arc.test.ArcTestContainer;
import io.quarkus.arc.test.MyQualifier;
import io.quarkus.arc.test.decorators.validation.DecoratorWithProducerMethodTest;
import jakarta.annotation.Priority;
import jakarta.decorator.Decorator;
import jakarta.decorator.Delegate;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.inject.Any;
import jakarta.enterprise.inject.Decorated;
import jakarta.enterprise.inject.spi.Bean;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import java.util.Comparator;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class InvalidDecoratedTest {

@RegisterExtension
public ArcTestContainer container = ArcTestContainer.builder()
.beanClasses(InvalidBean.class)
.shouldFail()
.build();

@Test
public void testDecoration() {
assertNotNull(container.getFailure());
assertTrue(container.getFailure().getMessage().startsWith(
"Invalid injection of @Decorated Bean<T>, can only be injected into decorators but was detected in: "
+ InvalidBean.class.getName() + "#decorated"));
}

@ApplicationScoped
static class InvalidBean {

@Inject
@Decorated
Bean<?> decorated;

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ public void handle(AsyncResult<Void> event) {
});
}


private Buffer createBuffer(ByteBuf data) {
return new NoBoundChecksBuffer(data);
}
Expand Down

0 comments on commit 2689132

Please sign in to comment.