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 de570503f99e5..8e8ebf3f2e1d6 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 @@ -3,6 +3,7 @@ import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT; +import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -949,11 +950,13 @@ public void registerField(FieldInfo fieldInfo) { injectionPoint.getTargetBean().isPresent() ? mc.load(injectionPoint.getTargetBean().get().getIdentifier()) : mc.loadNull()); + boolean isTransient = injectionPoint.isField() + && Modifier.isTransient(injectionPoint.getTarget().asField().flags()); ResultHandle ret = mc.invokeStaticMethod(instancesMethod, targetBean, injectionPointType, requiredType, requiredQualifiers, mc.getMethodParam(0), injectionPointAnnotations, - javaMember, mc.load(injectionPoint.getPosition())); + javaMember, mc.load(injectionPoint.getPosition()), mc.load(isTransient)); mc.returnValue(ret); }); configurator.done(); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationStore.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationStore.java index a43ff39f03f7d..67e4fc4c78e37 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationStore.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationStore.java @@ -111,17 +111,23 @@ private Collection transform(AnnotationTargetKey key) { } private Collection getOriginalAnnotations(AnnotationTarget target) { + Collection annotations; switch (target.kind()) { case CLASS: - return target.asClass().classAnnotations(); + annotations = target.asClass().classAnnotations(); + break; case METHOD: // Note that the returning collection also contains method params annotations - return target.asMethod().annotations(); + annotations = target.asMethod().annotations(); + break; case FIELD: - return target.asField().annotations(); + annotations = target.asField().annotations(); + break; default: throw new IllegalArgumentException("Unsupported annotation target"); } + + return Annotations.onlyRuntimeVisible(annotations); } private List initTransformers(Kind kind, Collection transformers) { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Annotations.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Annotations.java index 2697010b656ed..163df5c5d3a46 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Annotations.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Annotations.java @@ -1,8 +1,10 @@ package io.quarkus.arc.processor; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.function.Function; @@ -106,6 +108,17 @@ public static Set getAnnotations(Kind kind, DotName name, Co return ret; } + /** + * + * @param beanDeployment + * @param method + * @param name + * @return whether given method has a parameter that has an annotation with given name + */ + public static boolean hasParameterAnnotation(BeanDeployment beanDeployment, MethodInfo method, DotName name) { + return contains(getParameterAnnotations(beanDeployment, method), name); + } + /** * * @param beanDeployment @@ -173,4 +186,14 @@ public static AnnotationInstance getParameterAnnotation(MethodInfo method, DotNa return null; } + public static Collection onlyRuntimeVisible(Collection annotations) { + List result = new ArrayList<>(annotations.size()); + for (AnnotationInstance annotation : annotations) { + if (annotation.runtimeVisible()) { + result.add(annotation); + } + } + return result; + } + } 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 42c1177ed4736..98f9262c05b2f 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 @@ -55,17 +55,17 @@ public void done() { String name = this.name; if (name == null) { - name = Beans.initStereotypeName(stereotypes, implClass); + name = Beans.initStereotypeName(stereotypes, implClass, beanDeployment); } Boolean alternative = this.alternative; if (alternative == null) { - alternative = Beans.initStereotypeAlternative(stereotypes); + alternative = Beans.initStereotypeAlternative(stereotypes, beanDeployment); } Integer priority = this.priority; if (priority == null) { - priority = Beans.initStereotypeAlternativePriority(stereotypes); + priority = Beans.initStereotypeAlternativePriority(stereotypes, implClass, beanDeployment); } beanConsumer.accept(new BeanInfo.Builder() 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 fa2730bba6518..b4b67cfa5a8ec 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 @@ -158,9 +158,8 @@ public THIS name(String name) { } /** - * Unlike for the {@link #name(String)} method a new {@link jakarta.inject.Named} qualifier with the specified value is - * added - * to the configured bean. + * Unlike the {@link #name(String)} method, a new {@link jakarta.inject.Named} qualifier with the specified value + * is added to the configured bean. * * @param name * @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 f3bf13d753cd6..d3d087150f52b 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 @@ -4,6 +4,7 @@ import static io.quarkus.arc.processor.IndexClassLookupUtils.getClassByName; import java.lang.annotation.Annotation; +import java.lang.annotation.Retention; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; @@ -602,17 +603,18 @@ Collection extractInterceptorBindings(AnnotationInstance ann private static Collection extractAnnotations(AnnotationInstance annotation, Map singulars, Map repeatables) { + if (!annotation.runtimeVisible()) { + return Collections.emptyList(); + } DotName annotationName = annotation.name(); if (singulars.get(annotationName) != null) { return Collections.singleton(annotation); + } else if (repeatables.get(annotationName) != null) { + // repeatable, we need to extract actual annotations + return Annotations.onlyRuntimeVisible(Arrays.asList(annotation.value().asNestedArray())); } else { - if (repeatables.get(annotationName) != null) { - // repeatable, we need to extract actual annotations - return new ArrayList<>(Arrays.asList(annotation.value().asNestedArray())); - } else { - // neither singular nor repeatable, return empty collection - return Collections.emptyList(); - } + // neither singular nor repeatable, return empty collection + return Collections.emptyList(); } } @@ -695,10 +697,18 @@ private void buildContextPut(String key, Object value) { } } + private boolean isRuntimeAnnotationType(ClassInfo annotationType) { + AnnotationInstance retention = annotationType.declaredAnnotation(Retention.class); + return retention != null && "RUNTIME".equals(retention.value().asEnum()); + } + private Map findQualifiers() { Map qualifiers = new HashMap<>(); for (AnnotationInstance qualifier : beanArchiveImmutableIndex.getAnnotations(DotNames.QUALIFIER)) { ClassInfo qualifierClass = qualifier.target().asClass(); + if (!isRuntimeAnnotationType(qualifierClass)) { + continue; + } if (isExcluded(qualifierClass)) { continue; } @@ -725,6 +735,9 @@ private Map findInterceptorBindings() { // Note: doesn't use AnnotationStore, this will operate on classes without applying annotation transformers for (AnnotationInstance binding : beanArchiveImmutableIndex.getAnnotations(DotNames.INTERCEPTOR_BINDING)) { ClassInfo bindingClass = binding.target().asClass(); + if (!isRuntimeAnnotationType(bindingClass)) { + continue; + } if (isExcluded(bindingClass)) { continue; } @@ -787,6 +800,9 @@ private Map findStereotypes(Map int for (DotName stereotypeName : stereotypeNames) { ClassInfo stereotypeClass = getClassByName(getBeanArchiveIndex(), stereotypeName); if (stereotypeClass != null && !isExcluded(stereotypeClass)) { + if (!isRuntimeAnnotationType(stereotypeClass)) { + continue; + } boolean isAlternative = false; Integer alternativePriority = null; @@ -954,7 +970,8 @@ private List findBeans(Collection beanDefiningAnnotations, Li beanClass); beanClasses.add(beanClass); } - } else if (annotationStore.hasAnnotation(method, DotNames.DISPOSES)) { + } + if (annotationStore.hasAnnotation(method, DotNames.DISPOSES)) { // Disposers are not inherited disposerMethods.add(method); } 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 323dd070705ba..04519201474a4 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 @@ -1929,14 +1929,17 @@ private ResultHandle wrapCurrentInjectionPoint(ClassOutput classOutput, ClassCre ResultHandle annotationsHandle = collectInjectionPointAnnotations(classOutput, beanCreator, bean.getDeployment(), constructor, injectionPoint, annotationLiterals, injectionPointAnnotationsPredicate); ResultHandle javaMemberHandle = getJavaMemberHandle(constructor, injectionPoint, reflectionRegistration); + boolean isTransient = injectionPoint.isField() && Modifier.isTransient(injectionPoint.getTarget().asField().flags()); return constructor.newInstance( MethodDescriptor.ofConstructor(CurrentInjectionPointProvider.class, InjectableBean.class, Supplier.class, java.lang.reflect.Type.class, - Set.class, Set.class, Member.class, int.class), + Set.class, Set.class, Member.class, int.class, boolean.class), constructor.getThis(), constructor.getMethodParam(paramIdx), Types.getTypeHandle(constructor, injectionPoint.getType(), tccl), - requiredQualifiersHandle, annotationsHandle, javaMemberHandle, constructor.load(injectionPoint.getPosition())); + requiredQualifiersHandle, annotationsHandle, javaMemberHandle, + constructor.load(injectionPoint.getPosition()), + constructor.load(isTransient)); } private void initializeProxy(BeanInfo bean, String baseName, ClassCreator beanCreator) { @@ -2024,9 +2027,6 @@ public static ResultHandle collectInjectionPointAnnotations(ClassOutput classOut annotationHandle = constructor .readStaticField(FieldDescriptor.of(InjectLiteral.class, "INSTANCE", InjectLiteral.class)); } else { - if (!annotation.runtimeVisible()) { - continue; - } ClassInfo annotationClass = getClassByName(beanDeployment.getBeanArchiveIndex(), annotation.name()); if (annotationClass == null) { continue; 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 45b71117afc16..23051ce9d52ac 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 @@ -277,15 +277,22 @@ boolean boundsMatch(List bounds, List stricterBounds) { bounds = getUppermostBounds(bounds); stricterBounds = getUppermostBounds(stricterBounds); for (Type bound : bounds) { - for (Type stricterBound : stricterBounds) { - if (!beanDeployment.getAssignabilityCheck().isAssignableFrom(bound, stricterBound)) { - return false; - } + if (!isAssignableFromAtLeastOne(bound, stricterBounds)) { + return false; } } return true; } + boolean isAssignableFromAtLeastOne(Type type1, List types2) { + for (Type type2 : types2) { + if (beanDeployment.getAssignabilityCheck().isAssignableFrom(type1, type2)) { + return true; + } + } + return false; + } + boolean lowerBoundsOfWildcardMatch(Type parameter, WildcardType requiredParameter) { return lowerBoundsOfWildcardMatch(singletonList(parameter), requiredParameter); } 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 ff6515f5462e1..9e26c06a3ead4 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 @@ -3,9 +3,11 @@ import static io.quarkus.arc.processor.IndexClassLookupUtils.getClassByName; import java.lang.reflect.Modifier; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Deque; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -122,7 +124,6 @@ static BeanInfo createProducerMethod(Set beanTypes, MethodInfo producerMet priority = annotation.value().asInt(); continue; } - // This is not supported ATM but should work once we upgrade to Common Annotations 2.1 if ((!isAlternative || priority == null) && annotationName.equals(DotNames.PRIORITY)) { priority = annotation.value().asInt(); continue; @@ -164,10 +165,10 @@ static BeanInfo createProducerMethod(Set beanTypes, MethodInfo producerMet } if (!isAlternative) { - isAlternative = initStereotypeAlternative(stereotypes); + isAlternative = initStereotypeAlternative(stereotypes, beanDeployment); } if (name == null) { - name = initStereotypeName(stereotypes, producerMethod); + name = initStereotypeName(stereotypes, producerMethod, beanDeployment); } if (isAlternative) { @@ -238,7 +239,6 @@ static BeanInfo createProducerField(FieldInfo producerField, BeanInfo declaringB priority = annotation.value().asInt(); continue; } - // This is not supported ATM but should work once we upgrade to Common Annotations 2.1 if ((!isAlternative || priority == null) && annotation.name().equals(DotNames.PRIORITY)) { priority = annotation.value().asInt(); continue; @@ -279,10 +279,10 @@ static BeanInfo createProducerField(FieldInfo producerField, BeanInfo declaringB scope = scopes.get(0); } if (!isAlternative) { - isAlternative = initStereotypeAlternative(stereotypes); + isAlternative = initStereotypeAlternative(stereotypes, beanDeployment); } if (name == null) { - name = initStereotypeName(stereotypes, producerField); + name = initStereotypeName(stereotypes, producerField, beanDeployment); } if (isAlternative) { @@ -321,21 +321,14 @@ static ScopeInfo initStereotypeScope(List stereotypes, Annotatio if (stereotypes.isEmpty()) { return null; } - final Set stereotypeScopes = new HashSet<>(); - for (StereotypeInfo stereotype : stereotypes) { - ScopeInfo defaultScope = stereotype.getDefaultScope(); - if (defaultScope == null) { - List parentStereotypes = new ArrayList<>(stereotype.getParentStereotypes().size()); - for (AnnotationInstance annotation : stereotype.getParentStereotypes()) { - StereotypeInfo parentStereotype = beanDeployment.getStereotype(annotation.name()); - parentStereotypes.add(parentStereotype); - } - defaultScope = initStereotypeScope(parentStereotypes, target, beanDeployment); - } - if (defaultScope != null) { - stereotypeScopes.add(defaultScope); + + Set stereotypeScopes = new HashSet<>(); + for (StereotypeInfo stereotype : stereotypesWithTransitive(stereotypes, beanDeployment)) { + if (stereotype.getDefaultScope() != null) { + stereotypeScopes.add(stereotype.getDefaultScope()); } } + return BeanDeployment.getValidScope(stereotypeScopes, target); } @@ -346,42 +339,58 @@ static ScopeInfo initBeanDefiningAnnotationScope(Set beanDefiningAnno return BeanDeployment.getValidScope(beanDefiningAnnotationScopes, target); } - static boolean initStereotypeAlternative(List stereotypes) { + static boolean initStereotypeAlternative(List stereotypes, BeanDeployment beanDeployment) { if (stereotypes.isEmpty()) { return false; } - for (StereotypeInfo stereotype : stereotypes) { + + for (StereotypeInfo stereotype : stereotypesWithTransitive(stereotypes, beanDeployment)) { if (stereotype.isAlternative()) { return true; } } + return false; } - static Integer initStereotypeAlternativePriority(List stereotypes) { + // called when we know the bean does not declare priority on its own + // therefore, we can just throw when multiple priorities are inherited from stereotypes + static Integer initStereotypeAlternativePriority(List stereotypes, AnnotationTarget target, + BeanDeployment beanDeployment) { if (stereotypes.isEmpty()) { return null; } - for (StereotypeInfo stereotype : stereotypes) { + + Set priorities = new HashSet<>(); + for (StereotypeInfo stereotype : stereotypesWithTransitive(stereotypes, beanDeployment)) { if (stereotype.getAlternativePriority() != null) { - return stereotype.getAlternativePriority(); + priorities.add(stereotype.getAlternativePriority()); } } - return null; + + if (priorities.isEmpty()) { + return null; + } else if (priorities.size() == 1) { + return priorities.iterator().next(); + } else { + throw new DefinitionException("Bean " + target + + " does not declare @Priority and inherits multiple different priorities from stereotypes"); + } } - static String initStereotypeName(List stereotypes, AnnotationTarget target) { + static String initStereotypeName(List stereotypes, AnnotationTarget target, + BeanDeployment beanDeployment) { if (stereotypes.isEmpty()) { return null; } - for (StereotypeInfo stereotype : stereotypes) { + + for (StereotypeInfo stereotype : stereotypesWithTransitive(stereotypes, beanDeployment)) { if (stereotype.isNamed()) { switch (target.kind()) { case CLASS: return getDefaultName(target.asClass()); case FIELD: - return target.asField() - .name(); + return target.asField().name(); case METHOD: return getDefaultName(target.asMethod()); default: @@ -389,9 +398,34 @@ static String initStereotypeName(List stereotypes, AnnotationTar } } } + return null; } + private static List stereotypesWithTransitive(List stereotypes, + BeanDeployment beanDeployment) { + List result = new ArrayList<>(); + Set alreadySeen = new HashSet<>(); // to guard against hypothetical stereotype cycle + Deque workQueue = new ArrayDeque<>(stereotypes); + while (!workQueue.isEmpty()) { + StereotypeInfo stereotype = workQueue.poll(); + result.add(stereotype); + alreadySeen.add(stereotype.getName()); + + for (AnnotationInstance parentStereotype : stereotype.getParentStereotypes()) { + if (alreadySeen.contains(parentStereotype.name())) { + continue; + } + StereotypeInfo parent = beanDeployment.getStereotype(parentStereotype.name()); + if (parent != null) { + workQueue.add(parent); + } + } + } + + return result; + } + /** * Checks if given {@link BeanInfo} has type and qualifiers matching those in provided {@link TypeAndQualifiers}. * Uses standard bean assignability rules; see {@link BeanResolverImpl}. @@ -700,13 +734,22 @@ static void validateBean(BeanInfo bean, List errors, Consumer stereotypes, BeanDeployment deployment) { if (alternativePriority == null) { // No @Priority or @AlernativePriority used - try stereotypes - alternativePriority = initStereotypeAlternativePriority(stereotypes); + alternativePriority = initStereotypeAlternativePriority(stereotypes, target, deployment); } Integer computedPriority = deployment.computeAlternativePriority(target, stereotypes); if (computedPriority != null) { @@ -1150,10 +1193,10 @@ BeanInfo create() { scope = scopes.get(0); } if (!isAlternative) { - isAlternative = initStereotypeAlternative(stereotypes); + isAlternative = initStereotypeAlternative(stereotypes, beanDeployment); } if (name == null) { - name = initStereotypeName(stereotypes, beanClass); + name = initStereotypeName(stereotypes, beanClass, beanDeployment); } if (isAlternative) { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuiltinBean.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuiltinBean.java index 34b5c6c80becd..c871433096f8f 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuiltinBean.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuiltinBean.java @@ -3,6 +3,7 @@ import static io.quarkus.arc.processor.IndexClassLookupUtils.getClassByName; import java.lang.reflect.Member; +import java.lang.reflect.Modifier; import java.util.HashSet; import java.util.Set; import java.util.function.BiPredicate; @@ -192,6 +193,8 @@ private static void generateInstanceBytecode(GeneratorContext ctx) { ctx.constructor, ctx.injectionPoint, ctx.annotationLiterals, ctx.injectionPointAnnotationsPredicate); ResultHandle javaMemberHandle = BeanGenerator.getJavaMemberHandle(ctx.constructor, ctx.injectionPoint, ctx.reflectionRegistration); + boolean isTransient = ctx.injectionPoint.isField() + && Modifier.isTransient(ctx.injectionPoint.getTarget().asField().flags()); ResultHandle beanHandle; switch (ctx.targetInfo.kind()) { case OBSERVER: @@ -207,9 +210,10 @@ private static void generateInstanceBytecode(GeneratorContext ctx) { } ResultHandle instanceProvider = ctx.constructor.newInstance( MethodDescriptor.ofConstructor(InstanceProvider.class, java.lang.reflect.Type.class, Set.class, - InjectableBean.class, Set.class, Member.class, int.class), + InjectableBean.class, Set.class, Member.class, int.class, boolean.class), parameterizedType, qualifiers, beanHandle, annotationsHandle, javaMemberHandle, - ctx.constructor.load(ctx.injectionPoint.getPosition())); + ctx.constructor.load(ctx.injectionPoint.getPosition()), + ctx.constructor.load(isTransient)); ResultHandle instanceProviderSupplier = ctx.constructor.newInstance( MethodDescriptors.FIXED_VALUE_SUPPLIER_CONSTRUCTOR, instanceProvider); ctx.constructor.writeInstanceField( @@ -310,13 +314,10 @@ private static void generateBeanManagerBytecode(GeneratorContext ctx) { private static void generateResourceBytecode(GeneratorContext ctx) { ResultHandle annotations = ctx.constructor.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); - // For a resource field the required qualifiers contain all annotations declared on the field - // (hence we need to check if they are runtime-retained and their classes are available) + // For a resource field the required qualifiers contain all runtime-retained annotations + // declared on the field (hence we need to check if their classes are available) if (!ctx.injectionPoint.getRequiredQualifiers().isEmpty()) { for (AnnotationInstance annotation : ctx.injectionPoint.getRequiredQualifiers()) { - if (!annotation.runtimeVisible()) { - continue; - } ClassInfo annotationClass = getClassByName(ctx.beanDeployment.getBeanArchiveIndex(), annotation.name()); if (annotationClass == null) { continue; 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 c5b6d86ee76db..04b02f3dbb49c 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 @@ -50,6 +50,24 @@ static List forBean(AnnotationTarget beanTarget, BeanInfo declaringBe "Multiple @Inject constructors found on " + beanTarget.asClass().name() + ":\n" + injectConstructors.stream().map(Object::toString).collect(Collectors.joining("\n"))); } + for (AnnotationTarget injectConstructor : injectConstructors) { + Set parameterAnnotations = Annotations.getParameterAnnotations(beanDeployment, + injectConstructor.asMethod()); + for (AnnotationInstance annotation : parameterAnnotations) { + if (DotNames.DISPOSES.equals(annotation.name())) { + throw new DefinitionException( + "Bean constructor must not have a @Disposes parameter: " + injectConstructor); + } + if (DotNames.OBSERVES.equals(annotation.name())) { + throw new DefinitionException( + "Bean constructor must not have an @Observes parameter: " + injectConstructor); + } + if (DotNames.OBSERVES_ASYNC.equals(annotation.name())) { + throw new DefinitionException( + "Bean constructor must not have an @ObservesAsync parameter: " + injectConstructor); + } + } + } Set initializerMethods = injections.stream() .filter(it -> it.isMethod() && !it.isConstructor()) @@ -58,43 +76,58 @@ static List forBean(AnnotationTarget beanTarget, BeanInfo declaringBe .collect(Collectors.toSet()); for (MethodInfo initializerMethod : initializerMethods) { if (beanDeployment.hasAnnotation(initializerMethod, DotNames.PRODUCES)) { - throw new DefinitionException("Initializer method must not be marked @Produces " - + "(alternatively, producer method must not be marked @Inject): " + throw new DefinitionException("Initializer method must not be annotated @Produces " + + "(alternatively, producer method must not be annotated @Inject): " + beanTarget.asClass() + "." + initializerMethod.name()); } - - if (Annotations.contains(Annotations.getParameterAnnotations(beanDeployment, initializerMethod), - DotNames.DISPOSES)) { - throw new DefinitionException("Initializer method must not have a parameter marked @Disposes " - + "(alternatively, disposer method must not be marked @Inject): " + if (Annotations.hasParameterAnnotation(beanDeployment, initializerMethod, DotNames.DISPOSES)) { + throw new DefinitionException("Initializer method must not have a @Disposes parameter " + + "(alternatively, disposer method must not be annotated @Inject): " + beanTarget.asClass() + "." + initializerMethod.name()); } - - if (Annotations.contains(Annotations.getParameterAnnotations(beanDeployment, initializerMethod), - DotNames.OBSERVES)) { - throw new DefinitionException("Initializer method must not have a parameter marked @Observes " - + "(alternatively, observer method must not be marked @Inject): " + if (Annotations.hasParameterAnnotation(beanDeployment, initializerMethod, DotNames.OBSERVES)) { + throw new DefinitionException("Initializer method must not have an @Observes parameter " + + "(alternatively, observer method must not be annotated @Inject): " + beanTarget.asClass() + "." + initializerMethod.name()); } - - if (Annotations.contains(Annotations.getParameterAnnotations(beanDeployment, initializerMethod), - DotNames.OBSERVES_ASYNC)) { - throw new DefinitionException("Initializer method must not have a parameter marked @ObservesAsync " - + "(alternatively, async observer method must not be marked @Inject): " + if (Annotations.hasParameterAnnotation(beanDeployment, initializerMethod, DotNames.OBSERVES_ASYNC)) { + throw new DefinitionException("Initializer method must not have an @ObservesAsync parameter " + + "(alternatively, async observer method must not be annotated @Inject): " + beanTarget.asClass() + "." + initializerMethod.name()); } } return injections; } else if (Kind.METHOD.equals(beanTarget.kind())) { - if (beanTarget.asMethod().parameterTypes().isEmpty()) { + MethodInfo producerMethod = beanTarget.asMethod(); + + if (beanDeployment.hasAnnotation(producerMethod, DotNames.INJECT)) { + throw new DefinitionException("Producer method must not be annotated @Inject " + + "(alternatively, initializer method must not be annotated @Produces): " + + producerMethod); + } + if (Annotations.hasParameterAnnotation(beanDeployment, producerMethod, DotNames.DISPOSES)) { + throw new DefinitionException("Producer method must not have a @Disposes parameter " + + "(alternatively, disposer method must not be annotated @Produces): " + + producerMethod); + } + if (Annotations.hasParameterAnnotation(beanDeployment, producerMethod, DotNames.OBSERVES)) { + throw new DefinitionException("Producer method must not have an @Observes parameter " + + "(alternatively, observer method must not be annotated @Produces): " + + producerMethod); + } + if (Annotations.hasParameterAnnotation(beanDeployment, producerMethod, DotNames.OBSERVES_ASYNC)) { + throw new DefinitionException("Producer method must not have an @ObservesAsync parameter " + + "(alternatively, async observer method must not be annotated @Produces): " + + producerMethod); + } + + if (producerMethod.parameterTypes().isEmpty()) { return Collections.emptyList(); } // All parameters are injection points - return Collections.singletonList( - new Injection(beanTarget.asMethod(), - InjectionPointInfo.fromMethod(beanTarget.asMethod(), declaringBean.getImplClazz(), - beanDeployment, transformer))); + return Collections.singletonList(new Injection(producerMethod, + InjectionPointInfo.fromMethod(producerMethod, declaringBean.getImplClazz(), beanDeployment, transformer))); } throw new IllegalArgumentException("Unsupported annotation target"); } @@ -109,11 +142,8 @@ private static void forClassBean(ClassInfo beanClass, ClassInfo classInfo, BeanD AnnotationTarget injectTarget = injectAnnotation.target(); switch (injectAnnotation.target().kind()) { case FIELD: - injections - .add(new Injection(injectTarget, Collections - .singletonList( - InjectionPointInfo.fromField(injectTarget.asField(), beanClass, beanDeployment, - transformer)))); + injections.add(new Injection(injectTarget, Collections.singletonList( + InjectionPointInfo.fromField(injectTarget.asField(), beanClass, beanDeployment, transformer)))); break; case METHOD: injections.add(new Injection(injectTarget, @@ -152,10 +182,9 @@ private static void forClassBean(ClassInfo beanClass, ClassInfo classInfo, BeanD && resourceAnnotationInstance.target().asField().annotations().stream() .noneMatch(a -> DotNames.INJECT.equals(a.name()))) { // Add special injection for a resource field - injections.add(new Injection(resourceAnnotationInstance.target(), Collections - .singletonList(InjectionPointInfo - .fromResourceField(resourceAnnotationInstance.target().asField(), beanClass, - beanDeployment, transformer)))); + injections.add(new Injection(resourceAnnotationInstance.target(), Collections.singletonList( + InjectionPointInfo.fromResourceField(resourceAnnotationInstance.target().asField(), + beanClass, beanDeployment, transformer)))); } // TODO setter injection } @@ -181,6 +210,34 @@ private static boolean hasConstructorInjection(List injections) { static Injection forDisposer(MethodInfo disposerMethod, ClassInfo beanClass, BeanDeployment beanDeployment, InjectionPointModifier transformer, BeanInfo declaringBean) { + if (beanDeployment.hasAnnotation(disposerMethod, DotNames.INJECT)) { + throw new DefinitionException("Disposer method must not be annotated @Inject " + + "(alternatively, initializer method must not have a @Disposes parameter): " + + disposerMethod); + } + if (beanDeployment.hasAnnotation(disposerMethod, DotNames.PRODUCES)) { + throw new DefinitionException("Disposer method must not be annotated @Produces " + + "(alternatively, producer method must not have a @Disposes parameter): " + + disposerMethod); + } + if (Annotations.hasParameterAnnotation(beanDeployment, disposerMethod, DotNames.OBSERVES)) { + throw new DefinitionException("Disposer method must not have an @Observes parameter " + + "(alternatively, observer method must not have a @Disposes parameter): " + + disposerMethod); + } + if (Annotations.hasParameterAnnotation(beanDeployment, disposerMethod, DotNames.OBSERVES_ASYNC)) { + throw new DefinitionException("Disposer method must not have an @ObservesAsync parameter " + + "(alternatively, async observer method must not have a @Disposes parameter): " + + disposerMethod); + } + if (Annotations.getParameterAnnotations(beanDeployment, disposerMethod) + .stream() + .filter(it -> DotNames.DISPOSES.equals(it.name())) + .count() > 1) { + throw new DefinitionException("Disposer method must not have more than 1 @Disposes parameter: " + + disposerMethod); + } + return new Injection(disposerMethod, InjectionPointInfo.fromMethod(disposerMethod, beanClass, beanDeployment, annotations -> annotations.stream().anyMatch(a -> a.name().equals(DotNames.DISPOSES)), transformer)); } 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 1c7fc38972e45..c40a5e0858bbf 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 @@ -67,7 +67,7 @@ static InjectionPointInfo fromResourceField(FieldInfo field, ClassInfo beanClass InjectionPointModifier transformer) { Type type = resolveType(field.type(), beanClass, field.declaringClass(), beanDeployment); return new InjectionPointInfo(type, - transformer.applyTransformers(type, field, new HashSet<>(field.annotations())), + transformer.applyTransformers(type, field, new HashSet<>(Annotations.onlyRuntimeVisible(field.annotations()))), InjectionPointKind.RESOURCE, field, -1, false, false); } 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 bc5f890b16348..a11a3bd0da27c 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 @@ -264,11 +264,11 @@ public final class MethodDescriptors { 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); + Set.class, CreationalContextImpl.class, Set.class, Member.class, int.class, boolean.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); + Set.class, CreationalContextImpl.class, Set.class, Member.class, int.class, boolean.class); public static final MethodDescriptor COMPONENTS_PROVIDER_UNABLE_TO_LOAD_REMOVED_BEAN_TYPE = MethodDescriptor.ofMethod( ComponentsProvider.class, "unableToLoadRemovedBeanType", diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverGenerator.java index 4700853ff10c8..f627d1c0885e9 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverGenerator.java @@ -567,6 +567,8 @@ protected void createConstructor(ClassOutput classOutput, ClassCreator observerC injectionPointAnnotationsPredicate); ResultHandle javaMemberHandle = BeanGenerator.getJavaMemberHandle(constructor, injectionPoint, reflectionRegistration); + boolean isTransient = injectionPoint.isField() + && Modifier.isTransient(injectionPoint.getTarget().asField().flags()); // Wrap the constructor arg in a Supplier so we can pass it to CurrentInjectionPointProvider c'tor. ResultHandle delegateSupplier = constructor.newInstance( @@ -575,11 +577,12 @@ protected void createConstructor(ClassOutput classOutput, ClassCreator observerC ResultHandle wrapHandle = constructor.newInstance( MethodDescriptor.ofConstructor(CurrentInjectionPointProvider.class, InjectableBean.class, Supplier.class, java.lang.reflect.Type.class, - Set.class, Set.class, Member.class, int.class), + Set.class, Set.class, Member.class, int.class, boolean.class), constructor.loadNull(), delegateSupplier, Types.getTypeHandle(constructor, injectionPoint.getType()), requiredQualifiersHandle, annotationsHandle, javaMemberHandle, - constructor.load(injectionPoint.getPosition())); + constructor.load(injectionPoint.getPosition()), + constructor.load(isTransient)); ResultHandle wrapSupplierHandle = constructor.newInstance( MethodDescriptors.FIXED_VALUE_SUPPLIER_CONSTRUCTOR, wrapHandle); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverInfo.java index 009188098f290..ec23ce565663d 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverInfo.java @@ -43,9 +43,10 @@ public class ObserverInfo implements InjectionTargetInfo { static ObserverInfo create(BeanInfo declaringBean, MethodInfo observerMethod, Injection injection, boolean isAsync, List transformers, BuildContext buildContext, boolean jtaCapabilities) { - MethodParameterInfo eventParameter = initEventParam(observerMethod, declaringBean.getDeployment()); + BeanDeployment beanDeployment = declaringBean.getDeployment(); + MethodParameterInfo eventParameter = initEventParam(observerMethod, beanDeployment); AnnotationInstance priorityAnnotation = find( - getParameterAnnotations(declaringBean.getDeployment(), observerMethod, eventParameter.position()), + getParameterAnnotations(beanDeployment, observerMethod, eventParameter.position()), DotNames.PRIORITY); Integer priority; if (priorityAnnotation != null) { @@ -57,19 +58,35 @@ static ObserverInfo create(BeanInfo declaringBean, MethodInfo observerMethod, In Type observedType = observerMethod.parameterType(eventParameter.position()); if (Types.containsTypeVariable(observedType)) { Map resolvedTypeVariables = Types - .resolvedTypeVariables(declaringBean.getImplClazz(), declaringBean.getDeployment()) + .resolvedTypeVariables(declaringBean.getImplClazz(), beanDeployment) .getOrDefault(observerMethod.declaringClass(), Collections.emptyMap()); observedType = Types.resolveTypeParam(observedType, resolvedTypeVariables, - declaringBean.getDeployment().getBeanArchiveIndex()); + beanDeployment.getBeanArchiveIndex()); } - return create(null, declaringBean.getDeployment(), declaringBean.getTarget().get().asClass().name(), declaringBean, + Reception reception = initReception(isAsync, beanDeployment, observerMethod); + if (reception == Reception.IF_EXISTS && BuiltinScope.DEPENDENT.is(declaringBean.getScope())) { + throw new DefinitionException("@Dependent bean must not have a conditional observer method: " + + observerMethod); + } + + if (beanDeployment.hasAnnotation(observerMethod, DotNames.INJECT)) { + throw new DefinitionException("Observer method must not be annotated @Inject: " + observerMethod); + } + if (beanDeployment.hasAnnotation(observerMethod, DotNames.PRODUCES)) { + throw new DefinitionException("Observer method must not be annotated @Produces: " + observerMethod); + } + if (Annotations.hasParameterAnnotation(beanDeployment, observerMethod, DotNames.DISPOSES)) { + throw new DefinitionException("Observer method must not have a @Disposes parameter: " + observerMethod); + } + + return create(null, beanDeployment, declaringBean.getTarget().get().asClass().name(), declaringBean, observerMethod, injection, eventParameter, observedType, - initQualifiers(declaringBean.getDeployment(), observerMethod, eventParameter), - initReception(isAsync, declaringBean.getDeployment(), observerMethod), - initTransactionPhase(isAsync, declaringBean.getDeployment(), observerMethod), isAsync, priority, transformers, + initQualifiers(beanDeployment, observerMethod, eventParameter), + reception, + initTransactionPhase(isAsync, beanDeployment, observerMethod), isAsync, priority, transformers, buildContext, jtaCapabilities, null, Collections.emptyMap()); } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/QualifierRegistrar.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/QualifierRegistrar.java index f9301c3d8b01a..b2e273d61dd6b 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/QualifierRegistrar.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/QualifierRegistrar.java @@ -14,9 +14,9 @@ public interface QualifierRegistrar extends BuildExtension { /** * Returns a map of additional qualifers where the key represents the annotation type and the value is an optional set of - * non-binding members. Here, "non-binding" is meant in the sense of {@code jakarta.enterprise.util.Nonbinding}. I.e. - * members - * named in the set will be ignored when the CDI container is selecting a bean instance for a particular injection point. + * non-binding members. Here, "non-binding" is meant in the sense of {@code jakarta.enterprise.util.Nonbinding}. + * I.e. members named in the set will be ignored when the CDI container is selecting a bean instance for a particular + * injection point. */ Map> getAdditionalQualifiers(); } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AlternativePriority.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AlternativePriority.java index 48be16ec02521..5fd701beb4c1b 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AlternativePriority.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AlternativePriority.java @@ -10,10 +10,10 @@ /** * If a bean is annotated with this annotation, it is considered an enabled alternative with given priority. * Effectively, this is a shortcut for {@code Alternative} plus {@code Priority} annotations. + *

+ * This annotation can be used not only on bean classes, but also method and field producers. * - * This annotation can be used not only on bean classes, but also method and field producers (unlike pure {@code Priority}). - * - * @deprecated Use {@link Alternative} and {@link io.quarkus.arc.Priority}/{@link jakarta.annotation.Priority} instead + * @deprecated Use {@link Alternative} and {@link jakarta.annotation.Priority} instead */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD, ElementType.TYPE, ElementType.FIELD }) diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceHandle.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceHandle.java index c03ba183b3dfc..63bce6bf5fd68 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceHandle.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceHandle.java @@ -41,9 +41,8 @@ default T orElse(T other) { /** * Destroy the instance as defined by - * {@link jakarta.enterprise.context.spi.Contextual#destroy(Object, jakarta.enterprise.context.spi.CreationalContext)}. If - * this - * is a CDI contextual instance it is also removed from the underlying context. + * {@link jakarta.enterprise.context.spi.Contextual#destroy(Object, jakarta.enterprise.context.spi.CreationalContext)}. + * If this is a CDI contextual instance, it is also removed from the underlying context. * * @see AlterableContext#destroy(jakarta.enterprise.context.spi.Contextual) */ diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Priority.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Priority.java index b07211ebb4127..f90be54b65afa 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Priority.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Priority.java @@ -1,19 +1,25 @@ package io.quarkus.arc; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - import java.lang.annotation.Retention; -import java.lang.annotation.Target; +import java.lang.annotation.RetentionPolicy; /** - * This annotation has the same semantics as {@link jakarta.annotation.Priority} except that the {@link Target} meta-annotation - * is - * not present. The main motivation is to support method and field declarations, i.e. this annotation can be used for producer - * methods and fields. Note that this problem is fixed in Common Annotations 2.1. + * This annotation has the same semantics as {@link jakarta.annotation.Priority}. + *

+ * Prior to Common Annotations 2.1, the {@code jakarta.annotation.Priority} annotation + * was meta-annotated {@code @Target({TYPE, PARAMETER})} and so was only usable on class + * declarations and method parameters. This annotation was introduced to allow annotating + * producer methods and fields. + *

+ * Since Common Annotations 2.1, the {@code jakarta.annotation.Priority} is no longer + * meta-annotated {@code @Target}, so these two annotations are equivalent. *

* A priority specified by {@link AlternativePriority} and {@link jakarta.annotation.Priority} takes precedence. + * + * @deprecated use {@link jakarta.annotation.Priority}; this annotation will be removed at some time after Quarkus 3.6 */ -@Retention(RUNTIME) +@Retention(RetentionPolicy.RUNTIME) +@Deprecated public @interface Priority { int value(); diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Unremovable.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Unremovable.java index 510435c10c3a6..2ac04494fb65a 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Unremovable.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Unremovable.java @@ -18,8 +18,7 @@ *

  • does not declare an observer,
  • *
  • does not declare any producer which is eligible for injection to any injection point,
  • *
  • is not directly eligible for injection into any `jakarta.enterprise.inject.Instance` or `jakarta.inject.Provider` - * injection - * point
  • + * injection point * */ @Retention(RetentionPolicy.RUNTIME) diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BeanManagerBean.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BeanManagerBean.java index 15d44803fd392..e2d91f8d66d97 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BeanManagerBean.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BeanManagerBean.java @@ -4,11 +4,12 @@ import java.util.Set; import jakarta.enterprise.context.spi.CreationalContext; +import jakarta.enterprise.inject.spi.BeanContainer; import jakarta.enterprise.inject.spi.BeanManager; public class BeanManagerBean extends BuiltInBean { - private static final Set BM_TYPES = Set.of(Object.class, BeanManager.class); + private static final Set BM_TYPES = Set.of(Object.class, BeanContainer.class, BeanManager.class); @Override public Set getTypes() { diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BeanManagerImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BeanManagerImpl.java index e85b713fc55a3..8a6829dea54c7 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BeanManagerImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BeanManagerImpl.java @@ -4,10 +4,10 @@ import java.lang.reflect.Type; import java.util.Arrays; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; import java.util.Set; -import java.util.stream.Collectors; import jakarta.el.ELResolver; import jakarta.el.ExpressionFactory; @@ -125,8 +125,8 @@ public Set> resolveObserverMethods(T event, Annota if (Types.containsTypeVariable(eventType)) { throw new IllegalArgumentException("The runtime type of the event object contains a type variable: " + eventType); } - Set eventQualifiers = Arrays.asList(qualifiers).stream().collect(Collectors.toSet()); - return ArcContainerImpl.instance().resolveObservers(eventType, eventQualifiers).stream().collect(Collectors.toSet()); + Set eventQualifiers = new HashSet<>(Arrays.asList(qualifiers)); + return new LinkedHashSet<>(ArcContainerImpl.instance().resolveObservers(eventType, eventQualifiers)); } @Override diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/CurrentInjectionPointProvider.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/CurrentInjectionPointProvider.java index 3ee2809f5504a..70bb4258cb18c 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/CurrentInjectionPointProvider.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/CurrentInjectionPointProvider.java @@ -34,7 +34,7 @@ public class CurrentInjectionPointProvider implements InjectableReferenceProvider { static final InjectionPoint EMPTY = new InjectionPointImpl(Object.class, Object.class, Collections.emptySet(), null, null, - null, -1); + null, -1, false); static final Supplier EMPTY_SUPPLIER = new Supplier() { @@ -49,10 +49,11 @@ public InjectionPoint get() { private final InjectionPoint injectionPoint; public CurrentInjectionPointProvider(InjectableBean bean, Supplier> delegateSupplier, - Type requiredType, Set qualifiers, Set annotations, Member javaMember, int position) { + Type requiredType, Set qualifiers, Set annotations, Member javaMember, int position, + boolean isTransient) { this.delegateSupplier = delegateSupplier; this.injectionPoint = new InjectionPointImpl(requiredType, requiredType, qualifiers, bean, annotations, javaMember, - position); + position, isTransient); } @Override @@ -70,17 +71,16 @@ InjectableReferenceProvider getDelegate() { } public static class InjectionPointImpl implements InjectionPoint { - private final Type requiredType; private final Set qualifiers; private final InjectableBean bean; private final Annotated annotated; private final Member member; + private final boolean isTransient; public InjectionPointImpl(Type injectionPointType, Type requiredType, Set qualifiers, - InjectableBean bean, - Set annotations, - Member javaMember, int position) { + InjectableBean bean, Set annotations, Member javaMember, + int position, boolean isTransient) { this.requiredType = requiredType; this.qualifiers = qualifiers; this.bean = bean; @@ -93,6 +93,7 @@ public InjectionPointImpl(Type injectionPointType, Type requiredType, Set get(CreationalContext> creationalContext) { // Obtain current IP to get the required type and qualifiers InjectionPoint ip = InjectionPointProvider.get(); InstanceImpl> instance = new InstanceImpl>((InjectableBean) ip.getBean(), ip.getType(), - ip.getQualifiers(), (CreationalContextImpl) creationalContext, Collections.EMPTY_SET, ip.getMember(), 0); + ip.getQualifiers(), (CreationalContextImpl) creationalContext, Collections.EMPTY_SET, ip.getMember(), + 0, ip.isTransient()); CreationalContextImpl.addDependencyToParent((InjectableBean>) ip.getBean(), instance, creationalContext); return instance; } 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 8e9d7d3f9df2d..c879fac8ea857 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 @@ -43,7 +43,7 @@ public class InstanceImpl implements InjectableInstance { static InstanceImpl of(Type requiredType, Set requiredQualifiers) { return new InstanceImpl<>(null, null, requiredType, requiredQualifiers, new CreationalContextImpl<>(null), - Collections.emptySet(), null, -1); + Collections.emptySet(), null, -1, false); } private final CreationalContextImpl creationalContext; @@ -58,21 +58,25 @@ static InstanceImpl of(Type requiredType, Set requiredQualifi private final Set annotations; private final Member javaMember; private final int position; + private final boolean isTransient; private final LazyValue cachedGetResult; InstanceImpl(InjectableBean targetBean, Type type, Set qualifiers, - CreationalContextImpl creationalContext, Set annotations, Member javaMember, int position) { - this(targetBean, type, getRequiredType(type), qualifiers, creationalContext, annotations, javaMember, position); + CreationalContextImpl creationalContext, Set annotations, Member javaMember, int position, + boolean isTransient) { + this(targetBean, type, getRequiredType(type), qualifiers, creationalContext, annotations, javaMember, position, + isTransient); } private InstanceImpl(InstanceImpl parent, Type requiredType, Set requiredQualifiers) { this(parent.targetBean, parent.injectionPointType, requiredType, requiredQualifiers, parent.creationalContext, - parent.annotations, parent.javaMember, parent.position); + parent.annotations, parent.javaMember, parent.position, parent.isTransient); } InstanceImpl(InjectableBean targetBean, Type injectionPointType, Type requiredType, Set requiredQualifiers, - CreationalContextImpl creationalContext, Set annotations, Member javaMember, int position) { + CreationalContextImpl creationalContext, Set annotations, Member javaMember, int position, + boolean isTransient) { this.injectionPointType = injectionPointType; this.requiredType = requiredType; this.requiredQualifiers = requiredQualifiers != null ? requiredQualifiers : Collections.emptySet(); @@ -87,6 +91,7 @@ private InstanceImpl(InstanceImpl parent, Type requiredType, Set this.annotations = annotations; this.javaMember = javaMember; this.position = position; + this.isTransient = isTransient; this.cachedGetResult = isGetCached(annotations) ? new LazyValue<>(this::getInternal) : null; } @@ -174,7 +179,7 @@ private InstanceHandle getHandle(InjectableBean bean) { public H get() { InjectionPoint prev = InjectionPointProvider .set(new InjectionPointImpl(injectionPointType, requiredType, requiredQualifiers, targetBean, - annotations, javaMember, position)); + annotations, javaMember, position, isTransient)); try { return bean.get(context); } finally { @@ -223,7 +228,7 @@ private T getBeanInstance(InjectableBean bean) { CreationalContextImpl ctx = creationalContext.child(bean); InjectionPoint prev = InjectionPointProvider .set(new InjectionPointImpl(injectionPointType, requiredType, requiredQualifiers, targetBean, annotations, - javaMember, position)); + javaMember, position, isTransient)); T instance; try { instance = bean.get(ctx); diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceProvider.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceProvider.java index 013472e61ead4..3dc821963f6bb 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceProvider.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceProvider.java @@ -23,15 +23,17 @@ public class InstanceProvider implements InjectableReferenceProvider annotations; private final Member javaMember; private final int position; + private final boolean isTransient; public InstanceProvider(Type type, Set qualifiers, InjectableBean targetBean, Set annotations, - Member javaMember, int position) { + Member javaMember, int position, boolean isTransient) { this.requiredType = type; this.qualifiers = qualifiers; this.targetBean = targetBean; this.annotations = annotations; this.javaMember = javaMember; this.position = position; + this.isTransient = isTransient; } @SuppressWarnings({ "unchecked", "rawtypes" }) @@ -39,7 +41,7 @@ public InstanceProvider(Type type, Set qualifiers, InjectableBean public Instance get(CreationalContext> creationalContext) { InstanceImpl instance = new InstanceImpl(targetBean, requiredType, qualifiers, CreationalContextImpl.unwrap(creationalContext), - annotations, javaMember, position); + annotations, javaMember, position, isTransient); CreationalContextImpl.addDependencyToParent(InstanceBean.INSTANCE, instance, (CreationalContext) creationalContext); return instance; 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 index afff1078bd8fa..240a068b0bc40 100644 --- 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 @@ -60,7 +60,8 @@ private static List> resolveAllBeans(Type requiredType, Set List listOf(InjectableBean targetBean, Type injectionPointType, Type requiredType, Set requiredQualifiers, - CreationalContextImpl creationalContext, Set annotations, Member javaMember, int position) { + CreationalContextImpl creationalContext, Set annotations, Member javaMember, int position, + boolean isTransient) { List> beans = resolveAllBeans(requiredType, requiredQualifiers); if (beans.isEmpty()) { return Collections.emptyList(); @@ -68,7 +69,7 @@ public static List listOf(InjectableBean targetBean, Type injectionPoi List list = new ArrayList<>(beans.size()); InjectionPoint prev = InjectionPointProvider .set(new InjectionPointImpl(injectionPointType, requiredType, requiredQualifiers, targetBean, - annotations, javaMember, position)); + annotations, javaMember, position, isTransient)); try { for (InjectableBean bean : beans) { list.add(getBeanInstance((CreationalContextImpl) creationalContext, (InjectableBean) bean)); @@ -81,14 +82,14 @@ public static List listOf(InjectableBean targetBean, Type injectionPoi } public static List> listOfHandles(InjectableBean targetBean, Type injectionPointType, - Type requiredType, - Set requiredQualifiers, - CreationalContextImpl creationalContext, Set annotations, Member javaMember, int position) { + Type requiredType, Set requiredQualifiers, + CreationalContextImpl creationalContext, Set annotations, Member javaMember, int position, + boolean isTransient) { Supplier supplier = new Supplier() { @Override public InjectionPoint get() { return new InjectionPointImpl(injectionPointType, requiredType, requiredQualifiers, targetBean, - annotations, javaMember, position); + annotations, javaMember, position, isTransient); } }; return listOfHandles(supplier, requiredType, requiredQualifiers, creationalContext); diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/types/MultipleBoundsTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/types/MultipleBoundsTest.java new file mode 100644 index 0000000000000..1ab52fff59b67 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/types/MultipleBoundsTest.java @@ -0,0 +1,47 @@ +package io.quarkus.arc.test.bean.types; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.util.TypeLiteral; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; + +public class MultipleBoundsTest { + @RegisterExtension + ArcTestContainer container = new ArcTestContainer(BazImpl.class, Consumer.class); + + @Test + public void multipleBounds() { + Consumer consumer = Arc.container().instance(new TypeLiteral>() { + }).get(); + assertTrue(consumer.baz instanceof BazImpl); + } + + interface Foo { + } + + interface Bar { + } + + interface Baz { + } + + static class FooImpl implements Foo { + } + + @Dependent + static class BazImpl implements Baz { + } + + @Dependent + static class Consumer { + @Inject + Baz baz; + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerTest.java index 388f1de936bab..10ee32f83921e 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerTest.java @@ -9,6 +9,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -38,6 +39,7 @@ import jakarta.enterprise.inject.Default; import jakarta.enterprise.inject.spi.Annotated; import jakarta.enterprise.inject.spi.Bean; +import jakarta.enterprise.inject.spi.BeanContainer; import jakarta.enterprise.inject.spi.BeanManager; import jakarta.enterprise.inject.spi.InjectionPoint; import jakarta.enterprise.inject.spi.InterceptionType; @@ -77,6 +79,14 @@ public Map> getAdditionalQualifiers() { }) .build(); + @Test + public void testBeanManagerBeanTypes() { + BeanManager bm = Arc.container().beanManager(); + + assertSame(bm, Arc.container().instance(BeanContainer.class).get()); + assertSame(bm, Arc.container().instance(BeanManager.class).get()); + } + @Test public void testGetBeans() { BeanManager beanManager = Arc.container().instance(Legacy.class).get().getBeanManager(); diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/constructornoinject/DisposesParamConstructorTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/constructornoinject/DisposesParamConstructorTest.java new file mode 100644 index 0000000000000..bf0b792b40f21 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/constructornoinject/DisposesParamConstructorTest.java @@ -0,0 +1,38 @@ +package io.quarkus.arc.test.injection.constructornoinject; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Disposes; +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class DisposesParamConstructorTest { + + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(MyBean.class) + .shouldFail() + .build(); + + @Test + public void testInjection() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertTrue(error instanceof DefinitionException); + assertTrue(error.getMessage().contains("Bean constructor must not have a @Disposes parameter")); + } + + @Dependent + static class MyBean { + @Inject + public MyBean(@Disposes String ignored) { + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/constructornoinject/MultiInjectConstructorFailureTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/constructornoinject/MultiInjectConstructorFailureTest.java index 597d3dcd50d62..cc58d74b0de76 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/constructornoinject/MultiInjectConstructorFailureTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/constructornoinject/MultiInjectConstructorFailureTest.java @@ -26,6 +26,7 @@ public void testInjection() { Throwable error = container.getFailure(); assertNotNull(error); assertTrue(error instanceof DefinitionException); + assertTrue(error.getMessage().contains("Multiple @Inject constructors found")); } @Dependent diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/constructornoinject/ObservesAsyncParamConstructorTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/constructornoinject/ObservesAsyncParamConstructorTest.java new file mode 100644 index 0000000000000..9d6486389a28f --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/constructornoinject/ObservesAsyncParamConstructorTest.java @@ -0,0 +1,38 @@ +package io.quarkus.arc.test.injection.constructornoinject; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.event.ObservesAsync; +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class ObservesAsyncParamConstructorTest { + + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(MyBean.class) + .shouldFail() + .build(); + + @Test + public void testInjection() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertTrue(error instanceof DefinitionException); + assertTrue(error.getMessage().contains("Bean constructor must not have an @ObservesAsync parameter")); + } + + @Dependent + static class MyBean { + @Inject + public MyBean(@ObservesAsync String ignored) { + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/constructornoinject/ObservesParamConstructorTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/constructornoinject/ObservesParamConstructorTest.java new file mode 100644 index 0000000000000..f1d8a2b15f910 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/constructornoinject/ObservesParamConstructorTest.java @@ -0,0 +1,38 @@ +package io.quarkus.arc.test.injection.constructornoinject; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class ObservesParamConstructorTest { + + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(MyBean.class) + .shouldFail() + .build(); + + @Test + public void testInjection() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertTrue(error instanceof DefinitionException); + assertTrue(error.getMessage().contains("Bean constructor must not have an @Observes parameter")); + } + + @Dependent + static class MyBean { + @Inject + public MyBean(@Observes String ignored) { + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/metadata/InjectionPointMetadataTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/metadata/InjectionPointMetadataTest.java index 9e72f84e71a2b..88e897f18dd6c 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/metadata/InjectionPointMetadataTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/metadata/InjectionPointMetadataTest.java @@ -65,6 +65,12 @@ public void testInjectionPointMetadata() { assertFalse(annotatedField.isAnnotationPresent(Deprecated.class)); assertTrue(annotatedField.getAnnotation(Singleton.class) == null); assertTrue(annotatedField.getAnnotations(Singleton.class).isEmpty()); + assertFalse(injectionPoint.isTransient()); + + // Transient field + InjectionPoint transientInjectionPoint = controller.transientControlled.injectionPoint; + assertNotNull(transientInjectionPoint); + assertTrue(transientInjectionPoint.isTransient()); // Method InjectionPoint methodInjectionPoint = controller.controlledMethod.injectionPoint; @@ -76,6 +82,7 @@ public void testInjectionPointMetadata() { assertEquals(0, methodParam.getPosition()); assertEquals(Controller.class, methodParam.getDeclaringCallable().getJavaMember().getDeclaringClass()); assertEquals("setControlled", methodParam.getDeclaringCallable().getJavaMember().getName()); + assertFalse(methodInjectionPoint.isTransient()); // Constructor InjectionPoint ctorInjectionPoint = controller.controlledCtor.injectionPoint; @@ -91,6 +98,7 @@ public void testInjectionPointMetadata() { assertEquals(1, ctorParam.getAnnotations().size()); assertTrue(ctorParam.getDeclaringCallable() instanceof AnnotatedConstructor); assertEquals(Controller.class, ctorParam.getDeclaringCallable().getJavaMember().getDeclaringClass()); + assertFalse(ctorInjectionPoint.isTransient()); // Instance InjectionPoint instanceInjectionPoint = controller.instanceControlled.get().injectionPoint; @@ -112,6 +120,7 @@ public void testInjectionPointMetadata() { assertTrue(annotatedField.getAnnotation(Singleton.class) == null); assertTrue(annotatedField.getAnnotations(Singleton.class).isEmpty()); assertEquals(1, annotatedField.getAnnotations().size()); + assertFalse(instanceInjectionPoint.isTransient()); } @SuppressWarnings({ "unchecked", "serial" }) @@ -136,6 +145,7 @@ public void testObserverInjectionPointMetadata() { assertTrue(annotatedParam.isAnnotationPresent(FooAnnotation.class)); assertTrue(annotatedParam.getAnnotation(Singleton.class) == null); assertTrue(annotatedParam.getAnnotations(Singleton.class).isEmpty()); + assertFalse(injectionPoint.isTransient()); } @Singleton @@ -153,6 +163,9 @@ static class Controller { @Inject Instance instanceControlled; + @Inject + transient Controlled transientControlled; + @Inject public Controller(BeanManager beanManager, @Singleton Controlled controlled) { this.controlledCtor = controlled; diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/ifexists/DependentReceptionIfExistsTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/ifexists/DependentReceptionIfExistsTest.java new file mode 100644 index 0000000000000..4532c0fa259c6 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/ifexists/DependentReceptionIfExistsTest.java @@ -0,0 +1,36 @@ +package io.quarkus.arc.test.observers.ifexists; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.event.Reception; +import jakarta.enterprise.inject.spi.DefinitionException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class DependentReceptionIfExistsTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(DependentObserver.class) + .shouldFail() + .build(); + + @Test + public void testFailure() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertTrue(error instanceof DefinitionException); + assertTrue(error.getMessage().contains("@Dependent bean must not have a conditional observer method")); + } + + @Dependent + static class DependentObserver { + void observeString(@Observes(notifyObserver = Reception.IF_EXISTS) String value) { + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/illegal/AsyncObserverDisposesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/illegal/AsyncObserverDisposesTest.java new file mode 100644 index 0000000000000..93aecb9e65e29 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/illegal/AsyncObserverDisposesTest.java @@ -0,0 +1,37 @@ +package io.quarkus.arc.test.observers.illegal; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.event.ObservesAsync; +import jakarta.enterprise.inject.Disposes; +import jakarta.enterprise.inject.spi.DefinitionException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class AsyncObserverDisposesTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(Observer.class) + .shouldFail() + .build(); + + @Test + public void trigger() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertInstanceOf(DefinitionException.class, error); + assertTrue(error.getMessage().contains("Disposer method must not have an @ObservesAsync parameter")); + } + + @Dependent + static class Observer { + void observe(@ObservesAsync String ignored, @Disposes String ignored2) { + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/illegal/AsyncObserverInjectTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/illegal/AsyncObserverInjectTest.java new file mode 100644 index 0000000000000..f86194fc159ef --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/illegal/AsyncObserverInjectTest.java @@ -0,0 +1,38 @@ +package io.quarkus.arc.test.observers.illegal; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.event.ObservesAsync; +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class AsyncObserverInjectTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(Observer.class) + .shouldFail() + .build(); + + @Test + public void trigger() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertInstanceOf(DefinitionException.class, error); + assertTrue(error.getMessage().contains("Initializer method must not have an @ObservesAsync parameter")); + } + + @Dependent + static class Observer { + @Inject + void observe(@ObservesAsync String ignored) { + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/illegal/AsyncObserverProducesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/illegal/AsyncObserverProducesTest.java new file mode 100644 index 0000000000000..8d6cba1bd20f0 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/illegal/AsyncObserverProducesTest.java @@ -0,0 +1,39 @@ +package io.quarkus.arc.test.observers.illegal; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.event.ObservesAsync; +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.spi.DefinitionException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class AsyncObserverProducesTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(Observer.class) + .shouldFail() + .build(); + + @Test + public void trigger() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertInstanceOf(DefinitionException.class, error); + assertTrue(error.getMessage().contains("Producer method must not have an @ObservesAsync parameter")); + } + + @Dependent + static class Observer { + @Produces + String observe(@ObservesAsync String ignored) { + return null; + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/illegal/ObserverDisposesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/illegal/ObserverDisposesTest.java new file mode 100644 index 0000000000000..1de7bdd0f6067 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/illegal/ObserverDisposesTest.java @@ -0,0 +1,37 @@ +package io.quarkus.arc.test.observers.illegal; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.Disposes; +import jakarta.enterprise.inject.spi.DefinitionException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class ObserverDisposesTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(Observer.class) + .shouldFail() + .build(); + + @Test + public void trigger() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertInstanceOf(DefinitionException.class, error); + assertTrue(error.getMessage().contains("Disposer method must not have an @Observes parameter")); + } + + @Dependent + static class Observer { + void observe(@Observes String ignored, @Disposes String ignored2) { + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/illegal/ObserverInjectTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/illegal/ObserverInjectTest.java new file mode 100644 index 0000000000000..9909199d98160 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/illegal/ObserverInjectTest.java @@ -0,0 +1,38 @@ +package io.quarkus.arc.test.observers.illegal; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class ObserverInjectTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(Observer.class) + .shouldFail() + .build(); + + @Test + public void trigger() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertInstanceOf(DefinitionException.class, error); + assertTrue(error.getMessage().contains("Initializer method must not have an @Observes parameter")); + } + + @Dependent + static class Observer { + @Inject + void observe(@Observes String ignored) { + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/illegal/ObserverProducesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/illegal/ObserverProducesTest.java new file mode 100644 index 0000000000000..9fb3210a2dfe4 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/illegal/ObserverProducesTest.java @@ -0,0 +1,39 @@ +package io.quarkus.arc.test.observers.illegal; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.spi.DefinitionException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class ObserverProducesTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(Observer.class) + .shouldFail() + .build(); + + @Test + public void trigger() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertInstanceOf(DefinitionException.class, error); + assertTrue(error.getMessage().contains("Producer method must not have an @Observes parameter")); + } + + @Dependent + static class Observer { + @Produces + String observe(@Observes String ignored) { + return null; + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/disposer/illegal/DisposerInjectTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/disposer/illegal/DisposerInjectTest.java new file mode 100644 index 0000000000000..ab0b04d1858b1 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/disposer/illegal/DisposerInjectTest.java @@ -0,0 +1,45 @@ +package io.quarkus.arc.test.producer.disposer.illegal; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Disposes; +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class DisposerInjectTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(ProducerDisposer.class) + .shouldFail() + .build(); + + @Test + public void trigger() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertInstanceOf(DefinitionException.class, error); + assertTrue(error.getMessage().contains("Initializer method must not have a @Disposes parameter")); + } + + @Dependent + static class ProducerDisposer { + @Produces + @Dependent + String produce() { + return ""; + } + + @Inject + void dispose(@Disposes String ignored) { + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/disposer/illegal/DisposerProducesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/disposer/illegal/DisposerProducesTest.java new file mode 100644 index 0000000000000..21588dda8bf91 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/disposer/illegal/DisposerProducesTest.java @@ -0,0 +1,44 @@ +package io.quarkus.arc.test.producer.disposer.illegal; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Disposes; +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.spi.DefinitionException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class DisposerProducesTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(ProducerDisposer.class) + .shouldFail() + .build(); + + @Test + public void trigger() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertInstanceOf(DefinitionException.class, error); + assertTrue(error.getMessage().contains("Disposer method must not be annotated @Produces")); + } + + @Dependent + static class ProducerDisposer { + @Produces + @Dependent + String produce() { + return ""; + } + + @Produces + void dispose(@Disposes String ignored) { + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/disposer/illegal/DoubleDisposerTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/disposer/illegal/DoubleDisposerTest.java new file mode 100644 index 0000000000000..74c6a327fcb7a --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/disposer/illegal/DoubleDisposerTest.java @@ -0,0 +1,49 @@ +package io.quarkus.arc.test.producer.disposer.illegal; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Disposes; +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.spi.DefinitionException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class DoubleDisposerTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(ProducerDisposer.class) + .shouldFail() + .build(); + + @Test + public void trigger() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertInstanceOf(DefinitionException.class, error); + assertTrue(error.getMessage().contains("Disposer method must not have more than 1 @Disposes parameter")); + } + + @Dependent + static class ProducerDisposer { + @Produces + @Dependent + String produceString() { + return ""; + } + + @Produces + @Dependent + Integer produceInteger() { + return 0; + } + + void dispose(@Disposes String ignored, @Disposes Integer ignored2) { + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/illegal/NormalScopedArrayProducerFieldTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/illegal/NormalScopedArrayProducerFieldTest.java new file mode 100644 index 0000000000000..0d4a15fbc1407 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/illegal/NormalScopedArrayProducerFieldTest.java @@ -0,0 +1,41 @@ +package io.quarkus.arc.test.producer.illegal; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.spi.DeploymentException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class NormalScopedArrayProducerFieldTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(Producer.class) + .shouldFail() + .build(); + + @Test + public void trigger() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertInstanceOf(DeploymentException.class, error); + assertTrue(error.getMessage().contains("Producer field for a normal scoped bean must not have an array type")); + } + + static class MyPojo { + } + + @Dependent + static class Producer { + @Produces + @ApplicationScoped + MyPojo[] produce = new MyPojo[0]; + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/illegal/NormalScopedArrayProducerMethodTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/illegal/NormalScopedArrayProducerMethodTest.java new file mode 100644 index 0000000000000..738552a0fdd46 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/illegal/NormalScopedArrayProducerMethodTest.java @@ -0,0 +1,43 @@ +package io.quarkus.arc.test.producer.illegal; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.spi.DeploymentException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class NormalScopedArrayProducerMethodTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(Producer.class) + .shouldFail() + .build(); + + @Test + public void trigger() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertInstanceOf(DeploymentException.class, error); + assertTrue(error.getMessage().contains("Producer method for a normal scoped bean must not have an array type")); + } + + static class MyPojo { + } + + @Dependent + static class Producer { + @Produces + @ApplicationScoped + MyPojo[] produce() { + return new MyPojo[0]; + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/illegal/NormalScopedPrimitiveProducerFieldTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/illegal/NormalScopedPrimitiveProducerFieldTest.java new file mode 100644 index 0000000000000..1e417c4073ccc --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/illegal/NormalScopedPrimitiveProducerFieldTest.java @@ -0,0 +1,38 @@ +package io.quarkus.arc.test.producer.illegal; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.spi.DeploymentException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class NormalScopedPrimitiveProducerFieldTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(Producer.class) + .shouldFail() + .build(); + + @Test + public void trigger() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertInstanceOf(DeploymentException.class, error); + assertTrue(error.getMessage().contains("Producer field for a normal scoped bean must not have a primitive type")); + } + + @Dependent + static class Producer { + @Produces + @ApplicationScoped + int produce = 0; + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/illegal/NormalScopedPrimitiveProducerMethodTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/illegal/NormalScopedPrimitiveProducerMethodTest.java new file mode 100644 index 0000000000000..85f6b4f6354e1 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/illegal/NormalScopedPrimitiveProducerMethodTest.java @@ -0,0 +1,43 @@ +package io.quarkus.arc.test.producer.illegal; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.spi.DeploymentException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class NormalScopedPrimitiveProducerMethodTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(Producer.class) + .shouldFail() + .build(); + + @Test + public void trigger() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertInstanceOf(DeploymentException.class, error); + assertTrue(error.getMessage().contains("Producer method for a normal scoped bean must not have a primitive type")); + } + + static class MyPojo { + } + + @Dependent + static class Producer { + @Produces + @ApplicationScoped + int produce() { + return 0; + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/CyclicStereotypesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/CyclicStereotypesTest.java new file mode 100644 index 0000000000000..46804df912fa6 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/CyclicStereotypesTest.java @@ -0,0 +1,73 @@ +package io.quarkus.arc.test.stereotypes; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import jakarta.annotation.Priority; +import jakarta.enterprise.context.RequestScoped; +import jakarta.enterprise.inject.Alternative; +import jakarta.enterprise.inject.Stereotype; +import jakarta.inject.Named; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.InjectableBean; +import io.quarkus.arc.test.ArcTestContainer; + +public class CyclicStereotypesTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(Stereotype1.class, Stereotype2.class, Stereotype3.class, + MyBean.class); + + @Test + public void test() { + InjectableBean bean = Arc.container().instance(MyBean.class).getBean(); + assertEquals(RequestScoped.class, bean.getScope()); + assertEquals("myBean", bean.getName()); + assertTrue(bean.isAlternative()); + assertEquals(123, bean.getPriority()); + } + + // stereotype transitivity: + // 1 --> 2 + // 2 --> 3 + // 3 --> 2 + + @Stereotype2 + @Stereotype + @Target({ TYPE, METHOD, FIELD }) + @Retention(RUNTIME) + @interface Stereotype1 { + } + + @RequestScoped + @Named + @Stereotype3 + @Stereotype + @Target({ TYPE, METHOD, FIELD }) + @Retention(RUNTIME) + @interface Stereotype2 { + } + + @Alternative + @Priority(123) + @Stereotype2 + @Stereotype + @Target({ TYPE, METHOD, FIELD }) + @Retention(RUNTIME) + @interface Stereotype3 { + } + + @Stereotype1 + static class MyBean { + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/DeeplyTransitiveStereotypesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/DeeplyTransitiveStereotypesTest.java new file mode 100644 index 0000000000000..85d6c8ea20f81 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/DeeplyTransitiveStereotypesTest.java @@ -0,0 +1,109 @@ +package io.quarkus.arc.test.stereotypes; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import jakarta.annotation.Priority; +import jakarta.enterprise.context.RequestScoped; +import jakarta.enterprise.inject.Alternative; +import jakarta.enterprise.inject.Stereotype; +import jakarta.inject.Named; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.InjectableBean; +import io.quarkus.arc.test.ArcTestContainer; + +public class DeeplyTransitiveStereotypesTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer( + Stereotype1.class, + Stereotype2.class, + Stereotype3.class, + Stereotype4.class, + Stereotype5.class, + Stereotype6.class, + Stereotype7.class, + MyBean.class); + + @Test + public void test() { + InjectableBean bean = Arc.container().instance(MyBean.class).getBean(); + assertEquals(RequestScoped.class, bean.getScope()); + assertEquals("myBean", bean.getName()); + assertTrue(bean.isAlternative()); + assertEquals(123, bean.getPriority()); + } + + // stereotype transitivity: + // 1 --> 2 + // 2 --> 3 + // 3 --> 4, 5 + // 4 --> 6 + // 5 --> 7 + + @Stereotype2 + @Stereotype + @Target({ TYPE, METHOD, FIELD }) + @Retention(RUNTIME) + @interface Stereotype1 { + } + + @Stereotype3 + @Stereotype + @Target({ TYPE, METHOD, FIELD }) + @Retention(RUNTIME) + @interface Stereotype2 { + } + + @Stereotype4 + @Stereotype5 + @Stereotype + @Target({ TYPE, METHOD, FIELD }) + @Retention(RUNTIME) + @interface Stereotype3 { + } + + @RequestScoped + @Stereotype6 + @Stereotype + @Target({ TYPE, METHOD, FIELD }) + @Retention(RUNTIME) + @interface Stereotype4 { + } + + @Named + @Stereotype7 + @Stereotype + @Target({ TYPE, METHOD, FIELD }) + @Retention(RUNTIME) + @interface Stereotype5 { + } + + @Alternative + @Stereotype + @Target({ TYPE, METHOD, FIELD }) + @Retention(RUNTIME) + @interface Stereotype6 { + } + + @Priority(123) + @Stereotype + @Target({ TYPE, METHOD, FIELD }) + @Retention(RUNTIME) + @interface Stereotype7 { + } + + @Stereotype1 + static class MyBean { + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/InconsistentPriorityStereotypesOverriddenOnBeanTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/InconsistentPriorityStereotypesOverriddenOnBeanTest.java new file mode 100644 index 0000000000000..7a87a38dde6d7 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/InconsistentPriorityStereotypesOverriddenOnBeanTest.java @@ -0,0 +1,68 @@ +package io.quarkus.arc.test.stereotypes; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import jakarta.annotation.Priority; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Alternative; +import jakarta.enterprise.inject.Stereotype; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.InjectableBean; +import io.quarkus.arc.test.ArcTestContainer; + +public class InconsistentPriorityStereotypesOverriddenOnBeanTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(Stereotype1.class, Stereotype2.class, Stereotype3.class, + MyBean.class); + + @Test + public void test() { + InjectableBean bean = Arc.container().instance(MyBean.class).getBean(); + assertTrue(bean.isAlternative()); + assertEquals(789, bean.getPriority()); + } + + // stereotype transitivity: + // 1 --> 2, 3 + + @Alternative + @Stereotype2 + @Stereotype3 + @Stereotype + @Target({ TYPE, METHOD, FIELD }) + @Retention(RUNTIME) + @interface Stereotype1 { + } + + @Priority(123) + @Stereotype + @Target({ TYPE, METHOD, FIELD }) + @Retention(RUNTIME) + @interface Stereotype2 { + } + + @Priority(456) + @Stereotype + @Target({ TYPE, METHOD, FIELD }) + @Retention(RUNTIME) + @interface Stereotype3 { + } + + @Dependent + @Stereotype1 + @Priority(789) + static class MyBean { + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/InconsistentPriorityStereotypesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/InconsistentPriorityStereotypesTest.java new file mode 100644 index 0000000000000..e30a8aadcde27 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/InconsistentPriorityStereotypesTest.java @@ -0,0 +1,70 @@ +package io.quarkus.arc.test.stereotypes; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import jakarta.annotation.Priority; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Alternative; +import jakarta.enterprise.inject.Stereotype; +import jakarta.enterprise.inject.spi.DefinitionException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class InconsistentPriorityStereotypesTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(Stereotype1.class, Stereotype2.class, Stereotype3.class, MyBean.class) + .shouldFail() + .build(); + + @Test + public void trigger() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertInstanceOf(DefinitionException.class, error); + assertTrue(error.getMessage().contains("inherits multiple different priorities from stereotypes")); + } + + // stereotype transitivity: + // 1 --> 2, 3 + + @Alternative + @Stereotype2 + @Stereotype3 + @Stereotype + @Target({ TYPE, METHOD, FIELD }) + @Retention(RUNTIME) + @interface Stereotype1 { + } + + @Priority(123) + @Stereotype + @Target({ TYPE, METHOD, FIELD }) + @Retention(RUNTIME) + @interface Stereotype2 { + } + + @Priority(456) + @Stereotype + @Target({ TYPE, METHOD, FIELD }) + @Retention(RUNTIME) + @interface Stereotype3 { + } + + @Dependent + @Stereotype1 + static class MyBean { + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/TransitiveStereotypeTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/TransitiveStereotypeTest.java index 2732f9b804f2d..f62d9cd32dc88 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/TransitiveStereotypeTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/TransitiveStereotypeTest.java @@ -7,6 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -14,16 +15,20 @@ import java.util.UUID; import jakarta.annotation.PostConstruct; +import jakarta.annotation.Priority; import jakarta.enterprise.context.RequestScoped; +import jakarta.enterprise.inject.Alternative; import jakarta.enterprise.inject.Stereotype; import jakarta.enterprise.inject.spi.Bean; import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.inject.Named; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import io.quarkus.arc.Arc; import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.InjectableBean; import io.quarkus.arc.test.ArcTestContainer; public class TransitiveStereotypeTest { @@ -46,7 +51,7 @@ public void test() { Set> metadata = bm.getBeans(MyBean.class); assertEquals(1, metadata.size()); - assertEquals(RequestScoped.class, metadata.iterator().next().getScope()); + assertMetadata((InjectableBean) metadata.iterator().next()); container.requestContext().deactivate(); } @@ -60,13 +65,23 @@ public void test() { Set> metadata = bm.getBeans(MyBean.class); assertEquals(1, metadata.size()); - assertEquals(RequestScoped.class, metadata.iterator().next().getScope()); + assertMetadata((InjectableBean) metadata.iterator().next()); container.requestContext().deactivate(); } } + private void assertMetadata(InjectableBean bean) { + assertEquals(RequestScoped.class, bean.getScope()); + assertEquals("myBean", bean.getName()); + assertTrue(bean.isAlternative()); + assertEquals(123, bean.getPriority()); + } + @RequestScoped + @Named + @Alternative + @Priority(123) @Stereotype @Target({ TYPE, METHOD, FIELD }) @Retention(RUNTIME)