diff --git a/docs/src/main/asciidoc/cdi-reference.adoc b/docs/src/main/asciidoc/cdi-reference.adoc index dc617a63ae5d9b..2772b977c47909 100644 --- a/docs/src/main/asciidoc/cdi-reference.adoc +++ b/docs/src/main/asciidoc/cdi-reference.adoc @@ -911,6 +911,50 @@ Alternatively, you can use the `@LookupIfProperty` and `@LookupUnlessProperty` a } ---- +=== Injecting Multiple Bean Instances Intuitively + +In CDI, it's possible to inject multiple bean instances (aka contextual references) via the `javax.enterprise.inject.Instance` which implements `java.lang.Iterable`. +However, it's not exactly intuitive. +Therefore, a new way was introduced in Quarkus - inject a `java.util.List` annotated with the `io.quarkus.arc.All` qualifier. +The type of elements in the list is used a required type when performing the lookup. + +[source,java] +---- +@ApplicationScoped +public class Processor { + + @Inject + @All + List services; <1> <2> +} +---- +<1> The injected instance is an _immutable list_ of the contextual references of the _disambiguated_ beans. +<2> For this injection point the required type is `Service` and no additional qualifiers are declared. + +If an injection point declares no other qualifier than `@All` then `@Any` is used, i.e. the behavior is equivalent to `@Inject @Any Instance`. + +You can also inject a list of bean instances wrapped in `io.quarkus.arc.InstanceHandle`. +This can be useful if you need to inspect the related bean metadata. + +[source,java] +---- +@ApplicationScoped +public class Processor { + + @Inject + @All + List> services; + + public void doSomething() { + for (InstanceHandle handle : services) { + if (handle.getBean().getScope().equals(Dependent.class)) { + handle.get().process(); + break; + } + } + } +} +---- [[build_time_apis]] == Build Time Extensions diff --git a/extensions/arc/deployment/pom.xml b/extensions/arc/deployment/pom.xml index 9d20499b6ff751..954e712e52ae36 100644 --- a/extensions/arc/deployment/pom.xml +++ b/extensions/arc/deployment/pom.xml @@ -35,7 +35,11 @@ quarkus-junit5-internal test - + + org.assertj + assertj-core + test + org.jboss.spec.javax.ejb diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java index b0a77e6b6cb8b6..127e3310b0d003 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java @@ -20,11 +20,15 @@ import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.AnnotationTarget.Kind; +import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.FieldInfo; import org.jboss.jandex.IndexView; import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.MethodParameterInfo; +import org.jboss.jandex.Type; import org.jboss.logging.Logger; import io.quarkus.arc.ArcContainer; @@ -36,23 +40,33 @@ import io.quarkus.arc.deployment.UnremovableBeanBuildItem.BeanTypeExclusion; import io.quarkus.arc.deployment.ValidationPhaseBuildItem.ValidationErrorBuildItem; import io.quarkus.arc.processor.AlternativePriorities; +import io.quarkus.arc.processor.AnnotationLiteralProcessor; +import io.quarkus.arc.processor.Annotations; import io.quarkus.arc.processor.AnnotationsTransformer; import io.quarkus.arc.processor.BeanConfigurator; import io.quarkus.arc.processor.BeanDefiningAnnotation; import io.quarkus.arc.processor.BeanDeployment; import io.quarkus.arc.processor.BeanDeploymentValidator; +import io.quarkus.arc.processor.BeanGenerator; import io.quarkus.arc.processor.BeanInfo; import io.quarkus.arc.processor.BeanProcessor; import io.quarkus.arc.processor.BeanRegistrar; +import io.quarkus.arc.processor.Beans; +import io.quarkus.arc.processor.BuiltinScope; import io.quarkus.arc.processor.BytecodeTransformer; import io.quarkus.arc.processor.ContextConfigurator; import io.quarkus.arc.processor.ContextRegistrar; import io.quarkus.arc.processor.DotNames; +import io.quarkus.arc.processor.InjectionPointInfo; +import io.quarkus.arc.processor.InjectionPointInfo.TypeAndQualifiers; +import io.quarkus.arc.processor.MethodDescriptors; import io.quarkus.arc.processor.ObserverConfigurator; import io.quarkus.arc.processor.ObserverRegistrar; import io.quarkus.arc.processor.ReflectionRegistration; import io.quarkus.arc.processor.ResourceOutput; import io.quarkus.arc.processor.StereotypeInfo; +import io.quarkus.arc.processor.Transformation; +import io.quarkus.arc.processor.Types; import io.quarkus.arc.runtime.AdditionalBean; import io.quarkus.arc.runtime.ArcRecorder; import io.quarkus.arc.runtime.BeanContainer; @@ -84,10 +98,13 @@ import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveFieldBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveMethodBuildItem; +import io.quarkus.gizmo.MethodDescriptor; +import io.quarkus.gizmo.ResultHandle; import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.QuarkusApplication; import io.quarkus.runtime.annotations.QuarkusMain; import io.quarkus.runtime.test.TestApplicationClassPredicate; +import io.quarkus.runtime.util.HashUtil; /** * This class contains build steps that trigger various phases of the bean processing. @@ -389,13 +406,27 @@ public BeanRegistrationPhaseBuildItem registerBeans(ContextRegistrationPhaseBuil // PHASE 3 - register synthetic observers @BuildStep public ObserverRegistrationPhaseBuildItem registerSyntheticObservers(BeanRegistrationPhaseBuildItem beanRegistrationPhase, - List beanConfigurationRegistry) { + List beanConfigurators, + BuildProducer reflectiveMethods, + BuildProducer reflectiveFields, + BuildProducer unremovableBeans) { - for (BeanConfiguratorBuildItem configurator : beanConfigurationRegistry) { + for (BeanConfiguratorBuildItem configurator : beanConfigurators) { // Just make sure the configurator is processed configurator.getValues().forEach(BeanConfigurator::done); } + // Initialize the type -> bean map + beanRegistrationPhase.getBeanProcessor().getBeanDeployment().initBeanByTypeMap(); + + // Register a synthetic bean for each List with qualifier @All + List listAll = beanRegistrationPhase.getInjectionPoints().stream() + .filter(this::isListAllInjectionPoint).collect(Collectors.toList()); + if (!listAll.isEmpty()) { + registerListInjectionPointsBeans(beanRegistrationPhase, listAll, reflectiveMethods, reflectiveFields, + unremovableBeans); + } + BeanProcessor beanProcessor = beanRegistrationPhase.getBeanProcessor(); ObserverRegistrar.RegistrationContext registrationContext = beanProcessor.registerSyntheticObservers(); @@ -612,6 +643,190 @@ BeanDefiningAnnotationBuildItem quarkusMain() { return new BeanDefiningAnnotationBuildItem(DotName.createSimple(QuarkusMain.class.getName()), DotNames.SINGLETON); } + @BuildStep + AnnotationsTransformerBuildItem transformListAllInjectionPoints() { + return new AnnotationsTransformerBuildItem(new AnnotationsTransformer() { + + @Override + public int getPriority() { + return Integer.MIN_VALUE; + } + + @Override + public boolean appliesTo(Kind kind) { + return kind == Kind.FIELD || kind == Kind.METHOD; + } + + @Override + public void transform(TransformationContext ctx) { + if (Annotations.contains(ctx.getAnnotations(), DotNames.ALL)) { + // For each injection point annotated with @All add a synthetic qualifier + AnnotationTarget target = ctx.getTarget(); + if (target.kind() == Kind.FIELD) { + // Identifier is a hash of "type + field annotations" + String id = HashUtil + .sha1(target.asField().type().toString() + target.asField().annotations().toString()); + ctx.transform().add(DotNames.IDENTIFIED, + AnnotationValue.createStringValue("value", id)).done(); + } else { + MethodInfo method = target.asMethod(); + Set alls = Annotations.getAnnotations(Kind.METHOD_PARAMETER, DotNames.ALL, + ctx.getAnnotations()); + Set paramsAnnotations = Annotations.getAnnotations(Kind.METHOD_PARAMETER, + ctx.getAnnotations()); + List toAdd = new ArrayList<>(); + for (AnnotationInstance annotation : alls) { + short position = annotation.target().asMethodParameter().position(); + Set paramAnnotations = new HashSet<>(); + for (AnnotationInstance paramAnnotation : paramsAnnotations) { + if (paramAnnotation.target().asMethodParameter().position() == position) { + paramAnnotations.add(paramAnnotation); + } + } + // Identifier is a hash of "type + method param annotations" + String id = HashUtil.sha1(method.parameters().get(position) + paramAnnotations.toString()); + toAdd.add( + AnnotationInstance.create(DotNames.IDENTIFIED, + MethodParameterInfo.create(method, + annotation.target().asMethodParameter().position()), + new AnnotationValue[] { AnnotationValue.createStringValue("value", id) })); + } + Transformation transform = ctx.transform(); + toAdd.forEach(transform::add); + transform.done(); + } + } + } + + }); + } + + private void registerListInjectionPointsBeans(BeanRegistrationPhaseBuildItem beanRegistrationPhase, + List injectionPoints, BuildProducer reflectiveMethods, + BuildProducer reflectiveFields, + BuildProducer unremovableBeans) { + BeanDeployment beanDeployment = beanRegistrationPhase.getBeanProcessor().getBeanDeployment(); + AnnotationLiteralProcessor annotationLiterals = beanRegistrationPhase.getBeanProcessor() + .getAnnotationLiteralProcessor(); + ReflectionRegistration reflectionRegistration = new ReflectionRegistration() { + @Override + public void registerMethod(MethodInfo methodInfo) { + reflectiveMethods.produce(new ReflectiveMethodBuildItem(methodInfo)); + } + + @Override + public void registerField(FieldInfo fieldInfo) { + reflectiveFields.produce(new ReflectiveFieldBuildItem(fieldInfo)); + } + + }; + + Set unremovables = new HashSet<>(); + for (InjectionPointInfo injectionPoint : injectionPoints) { + + // The injection point must be registered immediately and NOT inside the creator callback + if (injectionPoint.isField()) { + reflectionRegistration.registerField(injectionPoint.getTarget().asField()); + } else { + reflectionRegistration.registerMethod(injectionPoint.getTarget().asMethod()); + } + + // All qualifiers but @All and @Identified + Set qualifiers = injectionPoint.getRequiredQualifiers().stream() + .filter(a -> !DotNames.ALL.equals(a.name()) && !DotNames.IDENTIFIED.equals(a.name())) + .collect(Collectors.toSet()); + if (qualifiers.isEmpty()) { + // If no other qualifier is used then add @Any + qualifiers.add(AnnotationInstance.create(DotNames.ANY, null, new AnnotationValue[] {})); + } + + Type elementType = injectionPoint.getType().asParameterizedType().arguments().get(0); + + if (!unremovables + .add(new TypeAndQualifiers( + elementType.name().equals(DotNames.INSTANCE_HANDLE) + ? elementType.asParameterizedType().arguments().get(0) + : elementType, + qualifiers))) { + continue; + } + + BeanConfigurator configurator = beanRegistrationPhase.getContext() + .configure(List.class) + .forceApplicationClass() + .scope(BuiltinScope.DEPENDENT.getInfo()) + .addType(injectionPoint.getRequiredType()); + + injectionPoint.getRequiredQualifiers().forEach(configurator::addQualifier); + + if (injectionPoint.getTargetBean().isPresent()) { + // Generate the bean class in the same package as the target bean + configurator.targetPackageName(injectionPoint.getTargetBean().get().getTargetPackageName()); + } + + configurator.creator(mc -> { + ResultHandle injetionPointType = Types.getTypeHandle(mc, injectionPoint.getType()); + + // List or List + ResultHandle requiredType; + MethodDescriptor instancesMethod; + if (elementType.name().equals(DotNames.INSTANCE_HANDLE)) { + requiredType = Types.getTypeHandle(mc, elementType.asParameterizedType().arguments().get(0)); + instancesMethod = MethodDescriptors.INSTANCES_LIST_OF_HANDLES; + } else { + requiredType = Types.getTypeHandle(mc, elementType); + instancesMethod = MethodDescriptors.INSTANCES_LIST_OF; + } + + ResultHandle requiredQualifiers = BeanGenerator.collectQualifiers(null, null, + beanDeployment, mc, annotationLiterals, qualifiers); + ResultHandle injectionPointAnnotations = BeanGenerator.collectInjectionPointAnnotations(null, null, + beanDeployment, + mc, injectionPoint, annotationLiterals, + annotationName -> !annotationName.equals(DotNames.DEPRECATED)); + ResultHandle javaMember = BeanGenerator.getJavaMemberHandle(mc, injectionPoint, + ReflectionRegistration.NOOP); + ResultHandle container = mc + .invokeStaticMethod(MethodDescriptors.ARC_CONTAINER); + ResultHandle targetBean = mc.invokeInterfaceMethod( + MethodDescriptors.ARC_CONTAINER_BEAN, + container, + injectionPoint.getTargetBean().isPresent() + ? mc.load(injectionPoint.getTargetBean().get().getIdentifier()) + : mc.loadNull()); + + ResultHandle ret = mc.invokeStaticMethod(instancesMethod, targetBean, + injetionPointType, requiredType, requiredQualifiers, mc.getMethodParam(0), + injectionPointAnnotations, + javaMember, mc.load(injectionPoint.getPosition())); + mc.returnValue(ret); + }); + configurator.done(); + } + if (!unremovables.isEmpty()) { + // New beans were registered - we need to re-init the type -> bean map + // Also make all beans that match the List<> injection points unremovable + beanDeployment.initBeanByTypeMap(); + // And make all the matching beans unremovable + unremovableBeans.produce(new UnremovableBeanBuildItem(new Predicate() { + @Override + public boolean test(BeanInfo bean) { + for (TypeAndQualifiers tq : unremovables) { + if (Beans.matches(bean, tq)) { + return true; + } + } + return false; + } + })); + } + } + + private boolean isListAllInjectionPoint(InjectionPointInfo injectionPoint) { + return DotNames.LIST.equals(injectionPoint.getRequiredType().name()) + && Annotations.contains(injectionPoint.getRequiredQualifiers(), DotNames.ALL); + } + private abstract static class AbstractCompositeApplicationClassesPredicate implements Predicate { private final IndexView applicationClassesIndex; diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/lookup/ListInjectionTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/lookup/ListInjectionTest.java new file mode 100644 index 00000000000000..daa801422d2e79 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/lookup/ListInjectionTest.java @@ -0,0 +1,191 @@ +package io.quarkus.arc.test.lookup; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.annotation.PreDestroy; +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.Alternative; +import javax.enterprise.inject.Default; +import javax.enterprise.inject.spi.InjectionPoint; +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.All; +import io.quarkus.arc.InstanceHandle; +import io.quarkus.test.QuarkusUnitTest; + +public class ListInjectionTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(Foo.class, ServiceAlpha.class, ServiceBravo.class, ServiceCharlie.class, Service.class, + Counter.class, Converter.class, ConverterAlpha.class, ConverterBravo.class, MyQualifier.class)); + + @Inject + Foo foo; + + @Test + public void testInjection() { + // The list is prefetched eagerly, the container attempts to resolve ambiguities + assertEquals(2, foo.services.size()); + // The list is immutable + assertThatExceptionOfType(UnsupportedOperationException.class) + .isThrownBy(() -> foo.services.add(new ServiceAlpha())); + for (Service service : foo.services) { + Optional ip = service.getInjectionPoint(); + if (ip.isPresent()) { + assertEquals(Foo.class, ip.get().getBean().getBeanClass()); + assertEquals(Service.class, ip.get().getType()); + } + } + // The list is empty if no beans are found + assertTrue(foo.counters.isEmpty()); + + assertEquals(1, foo.convertersDefault.size()); + assertEquals("ok", foo.convertersDefault.get(0).convert("Ok")); + + // Test constructor injection and additional qualifier + assertEquals(1, foo.convertersMyQualifier.size()); + assertEquals("OK", foo.convertersMyQualifier.get(0).convert("OK")); + + // Test List> + assertEquals(1, foo.counterHandles.size()); + InstanceHandle handle = foo.counterHandles.get(0); + assertEquals(CounterAlpha.class, handle.getBean().getBeanClass()); + assertEquals(1, handle.get().count()); + handle.destroy(); + assertTrue(CounterAlpha.DESTROYED.get()); + } + + @Singleton + static class Foo { + + @Inject + @All + List services; + + @MyQualifier + @All + List counters; + + @All + List> counterHandles; + + @Inject + @All + @Default + List convertersDefault; + + final List convertersMyQualifier; + + Foo(@All @MyQualifier List convertersMyQualifier) { + this.convertersMyQualifier = convertersMyQualifier; + } + + } + + interface Service { + + String ping(); + + default Optional getInjectionPoint() { + return Optional.empty(); + } + + } + + interface Counter { + + int count(); + + } + + interface Converter { + + String convert(String val); + + } + + @Singleton + static class ServiceAlpha implements Service { + + public String ping() { + return "alpha"; + } + } + + @Dependent + static class ServiceBravo implements Service { + + @Inject + InjectionPoint injectionPoint; + + public String ping() { + return "bravo"; + } + + @Override + public Optional getInjectionPoint() { + return Optional.of(injectionPoint); + } + + } + + @Dependent + @Alternative // -> not enabled + static class ServiceCharlie implements Service { + + public String ping() { + return "charlie"; + } + } + + @Dependent + static class ConverterAlpha implements Converter { + + @Override + public String convert(String val) { + return val.toLowerCase(); + } + + } + + @MyQualifier + @Singleton + static class ConverterBravo implements Converter { + + @Override + public String convert(String val) { + return val.toUpperCase(); + } + + } + + @Dependent + static class CounterAlpha implements Counter { + + static final AtomicBoolean DESTROYED = new AtomicBoolean(false); + + @Override + public int count() { + return 1; + } + + @PreDestroy + void destroy() { + DESTROYED.set(true); + } + + } + +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/lookup/MyQualifier.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/lookup/MyQualifier.java new file mode 100644 index 00000000000000..2e5b6351d93f4e --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/lookup/MyQualifier.java @@ -0,0 +1,19 @@ +package io.quarkus.arc.test.lookup; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import javax.inject.Qualifier; + +@Qualifier +@Retention(RUNTIME) +@Target({ TYPE, FIELD, METHOD, PARAMETER }) +public @interface MyQualifier { + +} diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AbstractGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AbstractGenerator.java index 3d7126208a1bf8..69fe3445deff85 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AbstractGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AbstractGenerator.java @@ -2,10 +2,8 @@ import io.quarkus.arc.Arc; import java.lang.reflect.Modifier; -import org.jboss.jandex.DotName; import org.jboss.jandex.FieldInfo; import org.jboss.jandex.MethodInfo; -import org.jboss.jandex.Type.Kind; abstract class AbstractGenerator { @@ -83,23 +81,4 @@ protected boolean isPackagePrivate(int mod) { return !(Modifier.isPrivate(mod) || Modifier.isProtected(mod) || Modifier.isPublic(mod)); } - protected String getPackageName(BeanInfo bean) { - DotName providerTypeName; - if (bean.isProducerMethod() || bean.isProducerField()) { - providerTypeName = bean.getDeclaringBean().getProviderType().name(); - } else { - if (bean.getProviderType().kind() == Kind.ARRAY || bean.getProviderType().kind() == Kind.PRIMITIVE) { - providerTypeName = bean.getImplClazz().name(); - } else { - providerTypeName = bean.getProviderType().name(); - } - } - String packageName = DotNames.packageName(providerTypeName); - if (packageName.startsWith("java.")) { - // It is not possible to place a class in a JDK package - packageName = DEFAULT_PACKAGE; - } - return packageName; - } - } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralProcessor.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralProcessor.java index be9a4d4e478419..48038da5e875fc 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralProcessor.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralProcessor.java @@ -87,6 +87,8 @@ public ResultHandle process(BytecodeCreator bytecode, ClassOutput classOutput, C literal.constructorParams.stream().map(m -> m.returnType().name().toString()).toArray()), constructorParams); } else { + Objects.requireNonNull(classOutput); + Objects.requireNonNull(targetPackage); String literalClassName = AnnotationLiteralGenerator.generatedLocalName(targetPackage, DotNames.simpleName(annotationClass), Hashes.sha1(annotationInstance.toString())); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanArchives.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanArchives.java index 6e7299cef1daa8..0db18f62381534 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanArchives.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanArchives.java @@ -1,5 +1,7 @@ package io.quarkus.arc.processor; +import io.quarkus.arc.All; +import io.quarkus.arc.Identified; import io.quarkus.arc.Lock; import io.quarkus.arc.impl.ActivateRequestContextInterceptor; import io.quarkus.arc.impl.InjectableRequestContextController; @@ -67,6 +69,8 @@ private static IndexView buildAdditionalIndex() { index(indexer, Intercepted.class.getName()); index(indexer, Model.class.getName()); index(indexer, Lock.class.getName()); + index(indexer, All.class.getName()); + index(indexer, Identified.class.getName()); // Arc built-in beans index(indexer, ActivateRequestContextInterceptor.class.getName()); index(indexer, InjectableRequestContextController.class.getName()); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfigurator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfigurator.java index be64a8b40bf5bd..bb4fbc9e3a55c4 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfigurator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfigurator.java @@ -63,6 +63,7 @@ public void done() { .defaultBean(defaultBean) .removable(removable) .forceApplicationClass(forceApplicationClass) + .targetPackageName(targetPackageName) .build()); } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfiguratorBase.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfiguratorBase.java index 0a4e3f244fefbd..2a6f52d7dd0d0c 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfiguratorBase.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfiguratorBase.java @@ -41,6 +41,7 @@ public abstract class BeanConfiguratorBase, protected final Map params; protected Type providerType; protected boolean forceApplicationClass; + protected String targetPackageName; protected BeanConfiguratorBase(DotName implClazz) { this.implClazz = implClazz; @@ -65,6 +66,7 @@ public B read(BeanConfiguratorBase base) { qualifiers.clear(); qualifiers.addAll(base.qualifiers); forceApplicationClass = base.forceApplicationClass; + targetPackageName = base.targetPackageName; scope(base.scope); if (base.alternativePriority != null) { alternativePriority(base.alternativePriority); @@ -184,6 +186,11 @@ public B forceApplicationClass() { return self(); } + public B targetPackageName(String name) { + this.targetPackageName = name; + return self(); + } + public B alternativePriority(int priority) { this.alternativePriority = priority; return self(); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java index 4f9ccde761def6..85d46c999b0002 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java @@ -249,7 +249,6 @@ void init(Consumer bytecodeTransformerConsumer, List> additionalUnusedBeanExclusions) { long start = System.nanoTime(); - initBeanByTypeMap(); // Collect dependency resolution errors List errors = new ArrayList<>(); for (BeanInfo bean : beans) { @@ -288,7 +287,10 @@ void init(Consumer bytecodeTransformerConsumer, LOGGER.debugf("Bean deployment initialized in %s ms", TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)); } - private void initBeanByTypeMap() { + /** + * Re-initialize the map that is used to speed-up lookup requests. + */ + public void initBeanByTypeMap() { Map> map = new HashMap<>(); for (BeanInfo bean : beans) { bean.types.stream().map(Type::name).distinct().forEach(rawTypeName -> { @@ -1008,7 +1010,8 @@ private List findBeans(Collection beanDefiningAnnotations, Li BeanInfo declaringBean = beanClassToBean.get(disposerMethod.declaringClass()); if (declaringBean != null) { Injection injection = Injection.forDisposer(disposerMethod, declaringBean.getImplClazz(), this, - injectionPointTransformer); + injectionPointTransformer, declaringBean); + injection.init(declaringBean); disposers.add(new DisposerInfo(declaringBean, disposerMethod, injection)); injectionPoints.addAll(injection.injectionPoints); } @@ -1079,6 +1082,7 @@ private void registerObserverMethods(Collection beanClasses, if (declaringBean != null) { Injection injection = Injection.forObserver(observerMethod, declaringBean.getImplClazz(), this, injectionPointTransformer); + injection.init(declaringBean); ObserverInfo observer = ObserverInfo.create(declaringBean, observerMethod, injection, async, observerTransformers, buildContext, jtaCapabilities); if (observer != null) { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java index 808e54fe682d2a..a4f06f22a87f69 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java @@ -146,7 +146,7 @@ Collection generateSyntheticBean(BeanInfo bean) { String baseName = baseNameBuilder.toString(); ProviderType providerType = new ProviderType(bean.getProviderType()); - String targetPackage = getPackageName(bean); + String targetPackage = bean.getTargetPackageName(); String generatedName = generatedNameFromTarget(targetPackage, baseName, BEAN_SUFFIX); beanToGeneratedName.put(bean, generatedName); if (existingClasses.contains(generatedName)) { @@ -1779,7 +1779,7 @@ protected void implementIsSuppressed(BeanInfo bean, ClassCreator beanCreator) { private String getProxyTypeName(BeanInfo bean, String baseName) { StringBuilder proxyTypeName = new StringBuilder(); - proxyTypeName.append(getPackageName(bean)); + proxyTypeName.append(bean.getTargetPackageName()); if (proxyTypeName.length() > 0) { proxyTypeName.append("."); } @@ -1828,7 +1828,7 @@ private void initializeProxy(BeanInfo bean, String baseName, ClassCreator beanCr proxy.returnValue(proxyInstance); } - static ResultHandle getJavaMemberHandle(MethodCreator constructor, + public static ResultHandle getJavaMemberHandle(MethodCreator constructor, InjectionPointInfo injectionPoint, ReflectionRegistration reflectionRegistration) { ResultHandle javaMemberHandle; if (Kind.FIELD.equals(injectionPoint.getTarget().kind())) { @@ -1869,7 +1869,7 @@ static ResultHandle getJavaMemberHandle(MethodCreator constructor, return javaMemberHandle; } - static ResultHandle collectInjectionPointAnnotations(ClassOutput classOutput, ClassCreator beanCreator, + public static ResultHandle collectInjectionPointAnnotations(ClassOutput classOutput, ClassCreator beanCreator, BeanDeployment beanDeployment, MethodCreator constructor, InjectionPointInfo injectionPoint, AnnotationLiteralProcessor annotationLiterals, Predicate injectionPointAnnotationsPredicate) { ResultHandle annotationsHandle = constructor.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); @@ -1895,7 +1895,7 @@ static ResultHandle collectInjectionPointAnnotations(ClassOutput classOutput, Cl ClassInfo literalClass = getClassByName(beanDeployment.getBeanArchiveIndex(), annotation.name()); annotationHandle = annotationLiterals.process(constructor, classOutput, literalClass, annotation, - Types.getPackageName(beanCreator.getClassName())); + beanCreator != null ? Types.getPackageName(beanCreator.getClassName()) : null); } constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, annotationsHandle, annotationHandle); @@ -1903,16 +1903,22 @@ static ResultHandle collectInjectionPointAnnotations(ClassOutput classOutput, Cl return annotationsHandle; } - static ResultHandle collectInjectionPointQualifiers(ClassOutput classOutput, ClassCreator beanCreator, - BeanDeployment beanDeployment, - MethodCreator constructor, - InjectionPointInfo injectionPoint, AnnotationLiteralProcessor annotationLiterals) { + public static ResultHandle collectInjectionPointQualifiers(ClassOutput classOutput, ClassCreator beanCreator, + BeanDeployment beanDeployment, MethodCreator constructor, InjectionPointInfo injectionPoint, + AnnotationLiteralProcessor annotationLiterals) { + return collectQualifiers(classOutput, beanCreator, beanDeployment, constructor, annotationLiterals, + injectionPoint.hasDefaultedQualifier() ? Collections.emptySet() : injectionPoint.getRequiredQualifiers()); + } + + public static ResultHandle collectQualifiers(ClassOutput classOutput, ClassCreator beanCreator, + BeanDeployment beanDeployment, MethodCreator constructor, AnnotationLiteralProcessor annotationLiterals, + Set requiredQualifiers) { ResultHandle requiredQualifiersHandle; - if (injectionPoint.hasDefaultedQualifier()) { + if (requiredQualifiers.isEmpty()) { requiredQualifiersHandle = constructor.readStaticField(FieldDescriptors.QUALIFIERS_IP_QUALIFIERS); } else { requiredQualifiersHandle = constructor.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); - for (AnnotationInstance qualifierAnnotation : injectionPoint.getRequiredQualifiers()) { + for (AnnotationInstance qualifierAnnotation : requiredQualifiers) { BuiltinQualifier qualifier = BuiltinQualifier.of(qualifierAnnotation); ResultHandle qualifierHandle; if (qualifier != null) { @@ -1921,7 +1927,7 @@ static ResultHandle collectInjectionPointQualifiers(ClassOutput classOutput, Cla // Create annotation literal if needed qualifierHandle = annotationLiterals.process(constructor, classOutput, beanDeployment.getQualifier(qualifierAnnotation.name()), qualifierAnnotation, - Types.getPackageName(beanCreator.getClassName())); + beanCreator != null ? Types.getPackageName(beanCreator.getClassName()) : null); } constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, requiredQualifiersHandle, qualifierHandle); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java index a4a01153047486..f0a62a13725a00 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java @@ -84,15 +84,16 @@ public class BeanInfo implements InjectionTargetInfo { private final boolean forceApplicationClass; + private final String targetPackageName; + BeanInfo(AnnotationTarget target, BeanDeployment beanDeployment, ScopeInfo scope, Set types, - Set qualifiers, - List injections, BeanInfo declaringBean, DisposerInfo disposer, Integer alternativePriority, - List stereotypes, - String name, boolean isDefaultBean) { + Set qualifiers, List injections, BeanInfo declaringBean, DisposerInfo disposer, + Integer alternativePriority, + List stereotypes, String name, boolean isDefaultBean, String targetPackageName) { this(null, null, target, beanDeployment, scope, types, qualifiers, injections, declaringBean, disposer, alternativePriority, stereotypes, name, isDefaultBean, null, null, - Collections.emptyMap(), true, false); + Collections.emptyMap(), true, false, targetPackageName); } BeanInfo(ClassInfo implClazz, Type providerType, AnnotationTarget target, BeanDeployment beanDeployment, ScopeInfo scope, @@ -102,7 +103,7 @@ public class BeanInfo implements InjectionTargetInfo { List stereotypes, String name, boolean isDefaultBean, Consumer creatorConsumer, Consumer destroyerConsumer, - Map params, boolean isRemovable, boolean forceApplicationClass) { + Map params, boolean isRemovable, boolean forceApplicationClass, String targetPackageName) { this.target = Optional.ofNullable(target); if (implClazz == null && target != null) { implClazz = initImplClazz(target, beanDeployment); @@ -142,6 +143,7 @@ public class BeanInfo implements InjectionTargetInfo { this.decoratedMethods = new ConcurrentHashMap<>(); this.lifecycleInterceptors = new ConcurrentHashMap<>(); this.forceApplicationClass = forceApplicationClass; + this.targetPackageName = targetPackageName; } @Override @@ -463,6 +465,29 @@ Map getParams() { return params; } + public String getTargetPackageName() { + if (targetPackageName != null) { + return targetPackageName; + } + DotName providerTypeName; + if (isProducerMethod() || isProducerField()) { + providerTypeName = declaringBean.getProviderType().name(); + } else { + if (providerType.kind() == org.jboss.jandex.Type.Kind.ARRAY + || providerType.kind() == org.jboss.jandex.Type.Kind.PRIMITIVE) { + providerTypeName = implClazz.name(); + } else { + providerTypeName = providerType.name(); + } + } + String packageName = DotNames.packageName(providerTypeName); + if (packageName.startsWith("java.")) { + // It is not possible to place a class in a JDK package + packageName = AbstractGenerator.DEFAULT_PACKAGE; + } + return packageName; + } + void validate(List errors, List validators, Consumer bytecodeTransformerConsumer, Set classesReceivingNoArgsCtor) { Beans.validateBean(this, errors, validators, bytecodeTransformerConsumer, classesReceivingNoArgsCtor); @@ -822,6 +847,8 @@ static class Builder { private boolean forceApplicationClass; + private String targetPackageName; + Builder() { injections = Collections.emptyList(); stereotypes = Collections.emptyList(); @@ -917,10 +944,15 @@ Builder removable(boolean val) { return this; } + Builder targetPackageName(String name) { + this.targetPackageName = name; + return this; + } + BeanInfo build() { return new BeanInfo(implClazz, providerType, target, beanDeployment, scope, types, qualifiers, injections, declaringBean, disposer, alternativePriority, stereotypes, name, isDefaultBean, creatorConsumer, - destroyerConsumer, params, removable, forceApplicationClass); + destroyerConsumer, params, removable, forceApplicationClass, targetPackageName); } public Builder forceApplicationClass(boolean forceApplicationClass) { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java index 803349bf8c509f..2914a12021e8f5 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java @@ -256,6 +256,7 @@ public void accept(BytecodeTransformer transformer) { registerCustomContexts(); registerScopes(); registerBeans(); + beanDeployment.initBeanByTypeMap(); registerSyntheticObservers(); initialize(unsupportedBytecodeTransformer, Collections.emptyList()); ValidationContext validationContext = validate(unsupportedBytecodeTransformer); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanResolver.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanResolver.java index 171b28a69d3b05..770999ff3769ff 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanResolver.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanResolver.java @@ -1,5 +1,6 @@ package io.quarkus.arc.processor; +import java.util.Collections; import java.util.Set; import javax.enterprise.inject.AmbiguousResolutionException; import org.jboss.jandex.AnnotationInstance; @@ -10,6 +11,10 @@ */ public interface BeanResolver { + default Set resolveBeans(Type requiredType, AnnotationInstance... requiredQualifiers) { + return resolveBeans(requiredType, requiredQualifiers.length == 0 ? Collections.emptySet() : Set.of(requiredQualifiers)); + } + /** * Note that this method does not attempt to resolve the ambiguity. * @@ -18,7 +23,7 @@ public interface BeanResolver { * @return the set of beans which have the given required type and qualifiers * @see #resolveAmbiguity(Set) */ - Set resolveBeans(Type requiredType, AnnotationInstance... requiredQualifiers); + Set resolveBeans(Type requiredType, Set requiredQualifiers); /** * Apply the ambiguous dependency resolution rules. diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanResolverImpl.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanResolverImpl.java index 03586f67e3e846..b325bcd43e8ea9 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanResolverImpl.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanResolverImpl.java @@ -37,16 +37,9 @@ class BeanResolverImpl implements BeanResolver { } @Override - public Set resolveBeans(Type requiredType, AnnotationInstance... requiredQualifiers) { + public Set resolveBeans(Type requiredType, Set requiredQualifiers) { Objects.requireNonNull(requiredType, "Required type must not be null"); - Set qualifiers; - if (requiredQualifiers.length == 0) { - qualifiers = Collections.emptySet(); - } else { - qualifiers = new HashSet<>(); - Collections.addAll(qualifiers, requiredQualifiers); - } - TypeAndQualifiers typeAndQualifiers = new TypeAndQualifiers(requiredType, qualifiers); + TypeAndQualifiers typeAndQualifiers = new TypeAndQualifiers(requiredType, requiredQualifiers); // Note that this method must not cache the results beacause it can be used before synthetic components are registered List beans = findMatching(typeAndQualifiers); Set ret; diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java index 8e4bfe0863c6f2..ea13f8444252ba 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java @@ -35,7 +35,7 @@ import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; -final class Beans { +public final class Beans { static final Logger LOGGER = Logger.getLogger(Beans.class); @@ -176,9 +176,13 @@ static BeanInfo build(ClassInfo beanClass, } } - return new BeanInfo(beanClass, beanDeployment, scope, types, qualifiers, - Injection.forBean(beanClass, null, beanDeployment, transformer), null, null, - isAlternative ? alternativePriority : null, stereotypes, name, isDefaultBean); + List injections = Injection.forBean(beanClass, null, beanDeployment, transformer); + BeanInfo bean = new BeanInfo(beanClass, beanDeployment, scope, types, qualifiers, + injections, null, null, isAlternative ? alternativePriority : null, stereotypes, name, isDefaultBean, null); + for (Injection injection : injections) { + injection.init(bean); + } + return bean; } private static void processSuperClass(ClassInfo beanClass, BeanDeployment beanDeployment, @@ -319,9 +323,12 @@ static BeanInfo createProducerMethod(MethodInfo producerMethod, BeanInfo declari } } + List injections = Injection.forBean(producerMethod, declaringBean, beanDeployment, transformer); BeanInfo bean = new BeanInfo(producerMethod, beanDeployment, scope, types, qualifiers, - Injection.forBean(producerMethod, declaringBean, beanDeployment, transformer), declaringBean, - disposer, alternativePriority, stereotypes, name, isDefaultBean); + injections, declaringBean, disposer, alternativePriority, stereotypes, name, isDefaultBean, null); + for (Injection injection : injections) { + injection.init(bean); + } return bean; } @@ -411,7 +418,7 @@ static BeanInfo createProducerField(FieldInfo producerField, BeanInfo declaringB } BeanInfo bean = new BeanInfo(producerField, beanDeployment, scope, types, qualifiers, Collections.emptyList(), - declaringBean, disposer, alternativePriority, stereotypes, name, isDefaultBean); + declaringBean, disposer, alternativePriority, stereotypes, name, isDefaultBean, null); return bean; } @@ -502,7 +509,7 @@ private static String initStereotypeName(List stereotypes, Annot return null; } - static boolean matches(BeanInfo bean, TypeAndQualifiers typeAndQualifiers) { + public static boolean matches(BeanInfo bean, TypeAndQualifiers typeAndQualifiers) { return matches(bean, typeAndQualifiers.type, typeAndQualifiers.qualifiers); } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java index 6c3579973921a0..62a7320b42ba63 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java @@ -84,7 +84,7 @@ Collection generate(BeanInfo bean, String beanClassName, ProviderType providerType = new ProviderType(bean.getProviderType()); ClassInfo providerClass = getClassByName(bean.getDeployment().getBeanArchiveIndex(), providerType.name()); String baseName = getBaseName(bean, beanClassName); - String targetPackage = getPackageName(bean); + String targetPackage = bean.getTargetPackageName(); String generatedName = generatedNameFromTarget(targetPackage, baseName, CLIENT_PROXY_SUFFIX); if (existingClasses.contains(generatedName)) { return Collections.emptyList(); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DecoratorInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DecoratorInfo.java index d799919e2b1006..40abb11d260e81 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DecoratorInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DecoratorInfo.java @@ -21,7 +21,7 @@ public class DecoratorInfo extends BeanInfo implements Comparable Set decoratedTypes, List injections, int priority) { super(target, beanDeployment, BuiltinScope.DEPENDENT.getInfo(), Collections.singleton(Type.create(target.asClass().name(), Kind.CLASS)), new HashSet<>(), injections, - null, null, null, Collections.emptyList(), null, false); + null, null, null, Collections.emptyList(), null, false, null); this.priority = priority; this.delegateInjectionPoint = delegateInjectionPoint; this.decoratedTypes = decoratedTypes; diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DotNames.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DotNames.java index 6d6112e4db772f..ada2989217b1f4 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DotNames.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DotNames.java @@ -1,16 +1,20 @@ package io.quarkus.arc.processor; +import io.quarkus.arc.All; import io.quarkus.arc.AlternativePriority; import io.quarkus.arc.ArcInvocationContext; import io.quarkus.arc.DefaultBean; +import io.quarkus.arc.Identified; import io.quarkus.arc.InjectableBean; import io.quarkus.arc.InjectableInstance; +import io.quarkus.arc.InstanceHandle; import io.quarkus.arc.Unremovable; import io.quarkus.arc.VetoedProducer; import io.quarkus.arc.impl.ComputingCache; import java.io.Serializable; import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; +import java.util.List; import java.util.Optional; import java.util.OptionalDouble; import java.util.OptionalInt; @@ -117,6 +121,10 @@ public final class DotNames { public static final DotName SERIALIZABLE = create(Serializable.class); public static final DotName UNREMOVABLE = create(Unremovable.class); public static final DotName VETOED_PRODUCER = create(VetoedProducer.class); + public static final DotName LIST = create(List.class); + public static final DotName ALL = create(All.class); + public static final DotName IDENTIFIED = create(Identified.class); + public static final DotName INSTANCE_HANDLE = create(InstanceHandle.class); public static final DotName BOOLEAN = create(Boolean.class); public static final DotName BYTE = create(Byte.class); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Injection.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Injection.java index 66de1b57e1bba6..58867caadfaa9b 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Injection.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Injection.java @@ -35,12 +35,6 @@ public class Injection { private static final Logger LOGGER = Logger.getLogger(Injection.class); - /** - * - * @param beanTarget - * @param beanDeployment - * @return the list of injections - */ static List forBean(AnnotationTarget beanTarget, BeanInfo declaringBean, BeanDeployment beanDeployment, InjectionPointModifier transformer) { if (Kind.CLASS.equals(beanTarget.kind())) { @@ -148,7 +142,7 @@ private static boolean hasConstructorInjection(List injections) { } static Injection forDisposer(MethodInfo disposerMethod, ClassInfo beanClass, BeanDeployment beanDeployment, - InjectionPointModifier transformer) { + InjectionPointModifier transformer, BeanInfo declaringBean) { return new Injection(disposerMethod, InjectionPointInfo.fromMethod(disposerMethod, beanClass, beanDeployment, annotations -> annotations.stream().anyMatch(a -> a.name().equals(DotNames.DISPOSES)), transformer)); } @@ -186,6 +180,12 @@ public AnnotationTarget getTarget() { return target; } + public void init(BeanInfo targetBean) { + for (InjectionPointInfo injectionPoint : injectionPoints) { + injectionPoint.setTargetBean(targetBean); + } + } + private static List getAllInjectionPoints(BeanDeployment beanDeployment, ClassInfo beanClass, DotName name, boolean skipConstructors) { List injectAnnotations = new ArrayList<>(); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointInfo.java index fb8193ddc77790..181154f8e4ef05 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointInfo.java @@ -9,6 +9,7 @@ import java.util.List; import java.util.ListIterator; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; @@ -82,6 +83,7 @@ method, position, contains(paramAnnotations, DotNames.TRANSIENT_REFERENCE), private final TypeAndQualifiers typeAndQualifiers; private final AtomicReference resolvedBean; + private final AtomicReference targetBean; private final InjectionPointKind kind; private final boolean hasDefaultedQualifier; private final AnnotationTarget target; @@ -101,6 +103,7 @@ method, position, contains(paramAnnotations, DotNames.TRANSIENT_REFERENCE), ? Collections.singleton(AnnotationInstance.create(DotNames.DEFAULT, null, Collections.emptyList())) : requiredQualifiers); this.resolvedBean = new AtomicReference(null); + this.targetBean = new AtomicReference(null); this.kind = kind; this.hasDefaultedQualifier = requiredQualifiers.isEmpty(); this.target = target; @@ -117,6 +120,14 @@ BeanInfo getResolvedBean() { return resolvedBean.get(); } + public Optional getTargetBean() { + return Optional.ofNullable(targetBean.get()); + } + + public void setTargetBean(BeanInfo bean) { + this.targetBean.set(bean); + } + InjectionPointKind getKind() { return kind; } @@ -273,11 +284,11 @@ enum InjectionPointKind { RESOURCE } - static class TypeAndQualifiers { + public static class TypeAndQualifiers { - final Type type; + public final Type type; - final Set qualifiers; + public final Set qualifiers; public TypeAndQualifiers(Type type, Set qualifiers) { this.type = type; diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorInfo.java index a97c37775ff0cd..b4e5eac7912fa9 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorInfo.java @@ -49,7 +49,7 @@ public class InterceptorInfo extends BeanInfo implements Comparable injections, int priority) { super(target, beanDeployment, BuiltinScope.DEPENDENT.getInfo(), Collections.singleton(Type.create(target.asClass().name(), Kind.CLASS)), new HashSet<>(), injections, - null, null, null, Collections.emptyList(), null, false); + null, null, null, Collections.emptyList(), null, false, null); this.bindings = bindings; this.priority = priority; List aroundInvokes = new ArrayList<>(); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java index 56ba2b55cc1e09..3bffc258f5feff 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java @@ -13,6 +13,7 @@ import io.quarkus.arc.impl.DecoratorDelegateProvider; import io.quarkus.arc.impl.FixedValueSupplier; import io.quarkus.arc.impl.InjectableReferenceProviders; +import io.quarkus.arc.impl.Instances; import io.quarkus.arc.impl.InterceptedMethodMetadata; import io.quarkus.arc.impl.InterceptorInvocation; import io.quarkus.arc.impl.InvocationContexts; @@ -24,7 +25,9 @@ import io.quarkus.gizmo.MethodDescriptor; import java.lang.reflect.Constructor; import java.lang.reflect.Field; +import java.lang.reflect.Member; import java.lang.reflect.Method; +import java.lang.reflect.Type; import java.util.Collections; import java.util.List; import java.util.Map; @@ -273,11 +276,21 @@ public final class MethodDescriptors { public static final MethodDescriptor DECORATOR_DELEGATE_PROVIDER_SET = MethodDescriptor .ofMethod(DecoratorDelegateProvider.class, "set", Object.class, Object.class); + public static final MethodDescriptor DECORATOR_DELEGATE_PROVIDER_UNSET = MethodDescriptor .ofMethod(DecoratorDelegateProvider.class, "unset", void.class); + public static final MethodDescriptor DECORATOR_DELEGATE_PROVIDER_GET = MethodDescriptor .ofMethod(DecoratorDelegateProvider.class, "get", Object.class); + public static final MethodDescriptor INSTANCES_LIST_OF = MethodDescriptor + .ofMethod(Instances.class, "listOf", List.class, InjectableBean.class, Type.class, Type.class, + Set.class, CreationalContextImpl.class, Set.class, Member.class, int.class); + + public static final MethodDescriptor INSTANCES_LIST_OF_HANDLES = MethodDescriptor + .ofMethod(Instances.class, "listOfHandles", List.class, InjectableBean.class, Type.class, Type.class, + Set.class, CreationalContextImpl.class, Set.class, Member.class, int.class); + private MethodDescriptors() { } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java index aa93635b987101..f00797f32579f4 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java @@ -71,11 +71,11 @@ public final class Types { private Types() { } - static ResultHandle getTypeHandle(BytecodeCreator creator, Type type) { + public static ResultHandle getTypeHandle(BytecodeCreator creator, Type type) { return getTypeHandle(creator, type, null); } - static ResultHandle getTypeHandle(BytecodeCreator creator, Type type, ResultHandle tccl) { + public static ResultHandle getTypeHandle(BytecodeCreator creator, Type type, ResultHandle tccl) { return getTypeHandle(creator, type, tccl, null); } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/All.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/All.java new file mode 100644 index 00000000000000..fb1a9f30cee3a4 --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/All.java @@ -0,0 +1,68 @@ +package io.quarkus.arc; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.List; +import javax.enterprise.inject.Any; +import javax.enterprise.inject.Instance; +import javax.enterprise.util.AnnotationLiteral; +import javax.inject.Qualifier; + +/** + * The container provides a synthetic bean for an injection point with the required type {@link List} that also declare this + * qualifier. The injected instance is an immutable list of the contextual references of the disambiguated beans. + * + *
+ * @ApplicationScoped
+ * public class Processor {
+ *
+ *     @Inject
+ *     @All
+ *     List services;
+ * }
+ * 
+ * + * If the injection point declares no other qualifier then {@link Any} is used, i.e. the behavior is equivalent to: + * + *
+ * @ApplicationScoped
+ * public class Processor {
+ *
+ *     @Inject
+ *     @Any
+ *     Instance services;
+ * }
+ * 
+ * + *

+ * Note that the semantics is the same as for the {@link Instance#iterator()}, i.e. the container attempts to resolve + * ambiguities. + * In general, if multiple beans are eligible then the container eliminates all beans that are: + *

    + *
  • not alternatives, except for producer methods and fields of beans that are alternatives,
  • + *
  • default beans.
  • + *
+ */ +@Qualifier +@Retention(RUNTIME) +@Target({ TYPE, FIELD, METHOD, PARAMETER }) +public @interface All { + + /** + * Supports inline instantiation of this qualifier. + */ + public static final class Literal extends AnnotationLiteral implements All { + + public static final Literal INSTANCE = new Literal(); + + private static final long serialVersionUID = 1L; + + } + +} diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Identified.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Identified.java new file mode 100644 index 00000000000000..f75a4c3a5003a5 --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Identified.java @@ -0,0 +1,44 @@ +package io.quarkus.arc; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import javax.enterprise.util.AnnotationLiteral; +import javax.inject.Qualifier; + +/** + * Qualify a bean with a string-based identifier. + */ +@Qualifier +@Retention(RUNTIME) +@Target({ TYPE, FIELD, METHOD, PARAMETER }) +public @interface Identified { + + String value(); + + /** + * Supports inline instantiation of this qualifier. + */ + public static final class Literal extends AnnotationLiteral implements Identified { + + private static final long serialVersionUID = 1L; + + private final String value; + + public Literal(String value) { + this.value = value; + } + + @Override + public String value() { + return value; + } + + } + +} diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceImpl.java index eb5e89490b3f76..758a8da76391ff 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceImpl.java @@ -18,9 +18,7 @@ import java.util.Iterator; import java.util.Objects; import java.util.Set; -import java.util.function.Predicate; import java.util.function.Supplier; -import java.util.stream.Collectors; import javax.enterprise.context.ContextNotActiveException; import javax.enterprise.context.Dependent; import javax.enterprise.inject.AmbiguousResolutionException; @@ -42,8 +40,6 @@ static InstanceImpl of(Type requiredType, Set requiredQualifi Collections.emptySet(), null, -1); } - private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[] {}; - private final CreationalContextImpl creationalContext; private final Set> resolvedBeans; @@ -235,11 +231,7 @@ private Set> beans() { } private Set> resolve() { - return ArcContainerImpl.instance() - .getResolvedBeans(requiredType, requiredQualifiers.toArray(EMPTY_ANNOTATION_ARRAY)) - .stream() - .filter(Predicate.not(InjectableBean::isSuppressed)) - .collect(Collectors.toUnmodifiableSet()); + return Instances.resolveBeans(requiredType, requiredQualifiers); } class InstanceIterator implements Iterator { diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/Instances.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/Instances.java new file mode 100644 index 00000000000000..afb21d9ed1232b --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/Instances.java @@ -0,0 +1,111 @@ +package io.quarkus.arc.impl; + +import io.quarkus.arc.InjectableBean; +import io.quarkus.arc.InstanceHandle; +import io.quarkus.arc.impl.CurrentInjectionPointProvider.InjectionPointImpl; +import java.lang.annotation.Annotation; +import java.lang.reflect.Member; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.spi.InjectionPoint; + +public final class Instances { + + static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[] {}; + + private Instances() { + } + + public static Set> resolveBeans(Type requiredType, Set requiredQualifiers) { + return resolveBeans(requiredType, requiredQualifiers.toArray(EMPTY_ANNOTATION_ARRAY)); + } + + public static Set> resolveBeans(Type requiredType, Annotation... requiredQualifiers) { + return ArcContainerImpl.instance() + .getResolvedBeans(requiredType, requiredQualifiers) + .stream() + .filter(Predicate.not(InjectableBean::isSuppressed)) + .collect(Collectors.toUnmodifiableSet()); + } + + @SuppressWarnings("unchecked") + public static List listOf(InjectableBean targetBean, Type injectionPointType, Type requiredType, + Set requiredQualifiers, + CreationalContextImpl creationalContext, Set annotations, Member javaMember, int position) { + Set> beans = resolveBeans(requiredType, requiredQualifiers); + if (beans.isEmpty()) { + return Collections.emptyList(); + } + List list = new ArrayList<>(beans.size()); + InjectionPoint prev = InjectionPointProvider + .set(new InjectionPointImpl(injectionPointType, requiredType, requiredQualifiers, targetBean, + annotations, javaMember, position)); + try { + for (InjectableBean bean : beans) { + list.add(getBeanInstance((CreationalContextImpl) creationalContext, (InjectableBean) bean)); + } + } finally { + InjectionPointProvider.set(prev); + } + + return List.copyOf(list); + } + + @SuppressWarnings("unchecked") + public static List> listOfHandles(InjectableBean targetBean, Type injectionPointType, + Type requiredType, + Set requiredQualifiers, + CreationalContextImpl creationalContext, Set annotations, Member javaMember, int position) { + Set> beans = resolveBeans(requiredType, requiredQualifiers); + if (beans.isEmpty()) { + return Collections.emptyList(); + } + Supplier supplier = new Supplier() { + @Override + public InjectionPoint get() { + return new InjectionPointImpl(injectionPointType, requiredType, requiredQualifiers, targetBean, + annotations, javaMember, position); + } + }; + List> list = new ArrayList<>(beans.size()); + for (InjectableBean bean : beans) { + list.add(getHandle((CreationalContextImpl) creationalContext, (InjectableBean) bean, supplier)); + } + return List.copyOf(list); + } + + private static T getBeanInstance(CreationalContextImpl parent, InjectableBean bean) { + CreationalContextImpl ctx = parent.child(bean); + T instance = bean.get(ctx); + if (Dependent.class.equals(bean.getScope())) { + CreationalContextImpl.addDependencyToParent(bean, instance, ctx); + } + return instance; + } + + private static InstanceHandle getHandle(CreationalContextImpl parent, InjectableBean bean, + Supplier injectionPointSupplier) { + CreationalContextImpl ctx = parent.child(bean); + return new LazyInstanceHandle<>(bean, ctx, parent, new Supplier() { + + @Override + public T get() { + InjectionPoint prev = InjectionPointProvider + .set(injectionPointSupplier.get()); + try { + return bean.get(ctx); + } finally { + InjectionPointProvider.set(prev); + } + } + }, null); + } + +}