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 0dcd87b2927c7..f5fb1364f1109 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 @@ -153,6 +153,9 @@ public class BeanDeployment { this.excludeTypes = builder.excludeTypes != null ? new ArrayList<>(builder.excludeTypes) : Collections.emptyList(); + findScopeAnnotations(DotNames.SCOPE, beanDefiningAnnotations); + findScopeAnnotations(DotNames.NORMAL_SCOPE, beanDefiningAnnotations); + qualifierNonbindingMembers = new HashMap<>(); qualifiers = findQualifiers(); for (QualifierRegistrar registrar : builder.qualifierRegistrars) { @@ -721,10 +724,33 @@ private void buildContextPut(String key, Object value) { } private boolean isRuntimeAnnotationType(ClassInfo annotationType) { + if (!annotationType.isAnnotation()) { + return false; + } AnnotationInstance retention = annotationType.declaredAnnotation(Retention.class); return retention != null && "RUNTIME".equals(retention.value().asEnum()); } + // this method finds and registers custom scope annotations prior to registering custom contexts; + // context registration will later overwrite the corresponding entries in the map, which leaves + // custom scope annotations without a context, and that makes no sense _except_ for the CDI TCK + private void findScopeAnnotations(DotName scopeMetaAnnotation, + Map beanDefiningAnnotations) { + for (AnnotationInstance scope : beanArchiveImmutableIndex.getAnnotations(scopeMetaAnnotation)) { + ClassInfo scopeClass = scope.target().asClass(); + if (!isRuntimeAnnotationType(scopeClass)) { + continue; + } + if (isExcluded(scopeClass)) { + continue; + } + DotName scopeName = scopeClass.name(); + if (BuiltinScope.from(scopeName) == null && !beanDefiningAnnotations.containsKey(scopeName)) { + beanDefiningAnnotations.put(scopeName, new BeanDefiningAnnotation(scopeName)); + } + } + } + private Map findQualifiers() { Map qualifiers = new HashMap<>(); for (AnnotationInstance qualifier : beanArchiveImmutableIndex.getAnnotations(DotNames.QUALIFIER)) { @@ -870,7 +896,7 @@ private Map findStereotypes(Map int return stereotypes; } - private static ScopeInfo getScope(DotName scopeAnnotationName, + private ScopeInfo getScope(DotName scopeAnnotationName, Map> customContexts) { BuiltinScope builtin = BuiltinScope.from(scopeAnnotationName); if (builtin != null) { @@ -881,6 +907,19 @@ private static ScopeInfo getScope(DotName scopeAnnotationName, return customScope; } } + if (beanDefiningAnnotations.containsKey(scopeAnnotationName)) { + // custom scope annotations without a registered context make no sense, + // but the CDI TCK uses them + ClassInfo clazz = beanArchiveImmutableIndex.getClassByName(scopeAnnotationName); + if (clazz != null) { + boolean inherited = clazz.hasDeclaredAnnotation(DotNames.INHERITED); + if (clazz.hasDeclaredAnnotation(DotNames.SCOPE)) { + return new ScopeInfo(scopeAnnotationName, false, inherited); + } else if (clazz.hasDeclaredAnnotation(DotNames.NORMAL_SCOPE)) { + return new ScopeInfo(scopeAnnotationName, true, inherited); + } + } + } return null; } @@ -1197,7 +1236,7 @@ private List findBeans(Collection beanDefiningAnnotations, Li Type disposedParamType = unusedDisposer.getDisposedParameterType(); boolean matchingProducerBeanExists = false; scan: for (BeanInfo bean : beans) { - if (bean.isProducerMethod() || bean.isProducerField()) { + if (bean.isProducer()) { if (!bean.getDeclaringBean().equals(unusedDisposer.getDeclaringBean())) { continue; } 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 7b0ace32b5150..15af009794e8a 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 @@ -42,6 +42,7 @@ import org.jboss.jandex.FieldInfo; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.Type; +import org.jboss.logging.Logger; import io.quarkus.arc.InjectableBean; import io.quarkus.arc.InjectableDecorator; @@ -54,12 +55,14 @@ import io.quarkus.arc.impl.InitializedInterceptor; import io.quarkus.arc.impl.SyntheticCreationalContextImpl; import io.quarkus.arc.impl.SyntheticCreationalContextImpl.TypeAndQualifiers; +import io.quarkus.arc.impl.UncaughtExceptions; import io.quarkus.arc.processor.BeanInfo.InterceptionInfo; import io.quarkus.arc.processor.BeanProcessor.PrivateMembersCollector; import io.quarkus.arc.processor.BuiltinBean.GeneratorContext; import io.quarkus.arc.processor.ResourceOutput.Resource; import io.quarkus.arc.processor.ResourceOutput.Resource.SpecialType; import io.quarkus.gizmo.AssignableResultHandle; +import io.quarkus.gizmo.BranchResult; import io.quarkus.gizmo.BytecodeCreator; import io.quarkus.gizmo.CatchBlockCreator; import io.quarkus.gizmo.ClassCreator; @@ -325,6 +328,8 @@ Collection generateSyntheticBean(BeanInfo bean) { implementHashCode(bean, beanCreator); implementToString(beanCreator); + implementGetInjectionPoints(bean, beanCreator); + beanCreator.close(); return classOutput.getResources(); } @@ -412,6 +417,8 @@ Collection generateClassBean(BeanInfo bean, ClassInfo beanClass) { implementHashCode(bean, beanCreator); implementToString(beanCreator); + implementGetInjectionPoints(bean, beanCreator); + beanCreator.close(); return classOutput.getResources(); } @@ -499,6 +506,8 @@ Collection generateProducerMethodBean(BeanInfo bean, MethodInfo produc implementHashCode(bean, beanCreator); implementToString(beanCreator); + implementGetInjectionPoints(bean, beanCreator); + beanCreator.close(); return classOutput.getResources(); } @@ -580,6 +589,8 @@ Collection generateProducerFieldBean(BeanInfo bean, FieldInfo producer implementHashCode(bean, beanCreator); implementToString(beanCreator); + implementGetInjectionPoints(bean, beanCreator); + beanCreator.close(); return classOutput.getResources(); } @@ -608,7 +619,7 @@ protected void createProviderFields(ClassCreator beanCreator, BeanInfo bean, Map interceptorToProviderSupplier, Map decoratorToProviderSupplier) { // Declaring bean provider - if (bean.isProducerMethod() || bean.isProducerField()) { + if (bean.isProducer()) { beanCreator.getFieldCreator(FIELD_NAME_DECLARING_PROVIDER_SUPPLIER, Supplier.class) .setModifiers(ACC_PRIVATE | ACC_FINAL); } @@ -647,7 +658,7 @@ protected MethodCreator initConstructor(ClassOutput classOutput, ClassCreator be // First collect all param types List parameterTypes = new ArrayList<>(); - if (bean.isProducerMethod() || bean.isProducerField()) { + if (bean.isProducer()) { parameterTypes.add(Supplier.class.getName()); } for (InjectionPointInfo injectionPoint : bean.getAllInjectionPoints()) { @@ -680,7 +691,7 @@ protected MethodCreator initConstructor(ClassOutput classOutput, ClassCreator be // Declaring bean provider int paramIdx = 0; - if (bean.isProducerMethod() || bean.isProducerField()) { + if (bean.isProducer()) { constructor.writeInstanceField( FieldDescriptor.of(beanCreator.getClassName(), FIELD_NAME_DECLARING_PROVIDER_SUPPLIER, Supplier.class.getName()), @@ -716,7 +727,7 @@ protected MethodCreator initConstructor(ClassOutput classOutput, ClassCreator be } else { // Not a built-in bean if (injectionPoint.isCurrentInjectionPointWrapperNeeded()) { - ResultHandle wrapHandle = wrapCurrentInjectionPoint(classOutput, beanCreator, bean, constructor, + ResultHandle wrapHandle = wrapCurrentInjectionPoint(bean, constructor, injectionPoint, paramIdx++, tccl, reflectionRegistration); ResultHandle wrapSupplierHandle = constructor.newInstance( MethodDescriptors.FIXED_VALUE_SUPPLIER_CONSTRUCTOR, wrapHandle); @@ -810,25 +821,25 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, Provide Map injectionPointToProviderField, boolean isApplicationClass, String baseName, String targetPackage) { - MethodCreator destroy = beanCreator - .getMethodCreator("destroy", void.class, providerType.descriptorName(), CreationalContext.class) - .setModifiers(ACC_PUBLIC); + MethodCreator doDestroy = beanCreator + .getMethodCreator("doDestroy", void.class, providerType.descriptorName(), CreationalContext.class) + .setModifiers(ACC_PRIVATE); if (bean.isClassBean()) { if (!bean.isInterceptor()) { // in case someone calls `Bean.destroy()` directly (i.e., they use the low-level CDI API), // they may pass us a client proxy - ResultHandle instance = destroy.invokeStaticInterfaceMethod(MethodDescriptors.CLIENT_PROXY_UNWRAP, - destroy.getMethodParam(0)); + ResultHandle instance = doDestroy.invokeStaticInterfaceMethod(MethodDescriptors.CLIENT_PROXY_UNWRAP, + doDestroy.getMethodParam(0)); // if there's no `@PreDestroy` interceptor, we'll generate code to invoke `@PreDestroy` callbacks - // directly into the `destroy` method: + // directly into the `doDestroy` method: // - // public void destroy(MyBean var1, CreationalContext var2) { + // private void doDestroy(MyBean var1, CreationalContext var2) { // var1.myPreDestroyCallback(); // var2.release(); // } - BytecodeCreator preDestroyBytecode = destroy; + BytecodeCreator preDestroyBytecode = doDestroy; // PreDestroy interceptors if (!bean.getLifecycleInterceptors(InterceptionType.PRE_DESTROY).isEmpty()) { @@ -836,16 +847,16 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, Provide // callbacks into a `Runnable` that we pass into the interceptor chain to be called // by the last `proceed()` call: // - // public void destroy(MyBean var1, CreationalContext var2) { + // private void doDestroy(MyBean var1, CreationalContext var2) { // // this is a `Runnable` that calls `MyBean.myPreDestroyCallback()` // MyBean_Bean$$function$$2 var3 = new MyBean_Bean$$function$$2(var1); // ((MyBean_Subclass)var1).arc$destroy((Runnable)var3); // var2.release(); // } - FunctionCreator preDestroyForwarder = destroy.createFunction(Runnable.class); + FunctionCreator preDestroyForwarder = doDestroy.createFunction(Runnable.class); preDestroyBytecode = preDestroyForwarder.getBytecode(); - destroy.invokeVirtualMethod( + doDestroy.invokeVirtualMethod( MethodDescriptor.ofMethod(SubclassGenerator.generatedName(bean.getProviderType().name(), baseName), SubclassGenerator.DESTROY_METHOD_NAME, void.class, Runnable.class), instance, preDestroyForwarder.getInstance()); @@ -874,15 +885,15 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, Provide preDestroyBytecode.invokeVirtualMethod(MethodDescriptor.of(callback), instance); } } - if (preDestroyBytecode != destroy) { + if (preDestroyBytecode != doDestroy) { // only if we're generating a `Runnable`, see above preDestroyBytecode.returnVoid(); } } // ctx.release() - destroy.invokeInterfaceMethod(MethodDescriptors.CREATIONAL_CTX_RELEASE, destroy.getMethodParam(1)); - destroy.returnValue(null); + doDestroy.invokeInterfaceMethod(MethodDescriptors.CREATIONAL_CTX_RELEASE, doDestroy.getMethodParam(1)); + doDestroy.returnValue(null); } else if (bean.getDisposer() != null) { // Invoke the disposer method @@ -890,26 +901,26 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, Provide MethodInfo disposerMethod = bean.getDisposer().getDisposerMethod(); boolean isStatic = Modifier.isStatic(disposerMethod.flags()); - ResultHandle declaringProviderSupplierHandle = destroy.readInstanceField( + ResultHandle declaringProviderSupplierHandle = doDestroy.readInstanceField( FieldDescriptor.of(beanCreator.getClassName(), FIELD_NAME_DECLARING_PROVIDER_SUPPLIER, Supplier.class.getName()), - destroy.getThis()); - ResultHandle declaringProviderHandle = destroy.invokeInterfaceMethod( + doDestroy.getThis()); + ResultHandle declaringProviderHandle = doDestroy.invokeInterfaceMethod( MethodDescriptors.SUPPLIER_GET, declaringProviderSupplierHandle); - ResultHandle ctxHandle = destroy.newInstance( - MethodDescriptor.ofConstructor(CreationalContextImpl.class, Contextual.class), destroy.loadNull()); + ResultHandle ctxHandle = doDestroy.newInstance( + MethodDescriptor.ofConstructor(CreationalContextImpl.class, Contextual.class), doDestroy.loadNull()); ResultHandle declaringProviderInstanceHandle; if (isStatic) { // for static disposers, we don't need to resolve this handle // the `null` will only be used for reflective invocation in case the disposer is private, which is OK - declaringProviderInstanceHandle = destroy.loadNull(); + declaringProviderInstanceHandle = doDestroy.loadNull(); } else { - declaringProviderInstanceHandle = destroy.invokeInterfaceMethod( + declaringProviderInstanceHandle = doDestroy.invokeInterfaceMethod( MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, declaringProviderHandle, ctxHandle); if (bean.getDeclaringBean().getScope().isNormal()) { // We need to unwrap the client proxy - declaringProviderInstanceHandle = destroy.invokeInterfaceMethod( + declaringProviderInstanceHandle = doDestroy.invokeInterfaceMethod( MethodDescriptors.CLIENT_PROXY_GET_CONTEXTUAL_INSTANCE, declaringProviderInstanceHandle); } @@ -920,21 +931,23 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, Provide Iterator injectionPointsIterator = bean.getDisposer().getInjection().injectionPoints.iterator(); for (int i = 0; i < disposerMethod.parametersCount(); i++) { if (i == disposedParamPosition) { - referenceHandles[i] = destroy.getMethodParam(0); + referenceHandles[i] = doDestroy.getMethodParam(0); } else { InjectionPointInfo injectionPoint = injectionPointsIterator.next(); - ResultHandle childCtxHandle = destroy.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD_CONTEXTUAL, + ResultHandle childCtxHandle = doDestroy.invokeStaticMethod( + MethodDescriptors.CREATIONAL_CTX_CHILD_CONTEXTUAL, declaringProviderHandle, ctxHandle); - ResultHandle providerSupplierHandle = destroy + ResultHandle providerSupplierHandle = doDestroy .readInstanceField(FieldDescriptor.of(beanCreator.getClassName(), injectionPointToProviderField.get(injectionPoint), - Supplier.class.getName()), destroy.getThis()); - ResultHandle providerHandle = destroy.invokeInterfaceMethod(MethodDescriptors.SUPPLIER_GET, + Supplier.class.getName()), doDestroy.getThis()); + ResultHandle providerHandle = doDestroy.invokeInterfaceMethod(MethodDescriptors.SUPPLIER_GET, providerSupplierHandle); - AssignableResultHandle referenceHandle = destroy.createVariable(Object.class); - destroy.assign(referenceHandle, destroy.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, - providerHandle, childCtxHandle)); - checkPrimitiveInjection(destroy, injectionPoint, referenceHandle); + AssignableResultHandle referenceHandle = doDestroy.createVariable(Object.class); + doDestroy.assign(referenceHandle, + doDestroy.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, + providerHandle, childCtxHandle)); + checkPrimitiveInjection(doDestroy, injectionPoint, referenceHandle); referenceHandles[i] = referenceHandle; } } @@ -942,40 +955,68 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, Provide if (Modifier.isPrivate(disposerMethod.flags())) { privateMembers.add(isApplicationClass, String.format("Disposer %s#%s", disposerMethod.declaringClass().name(), disposerMethod.name())); - ResultHandle paramTypesArray = destroy.newArray(Class.class, destroy.load(referenceHandles.length)); - ResultHandle argsArray = destroy.newArray(Object.class, destroy.load(referenceHandles.length)); + ResultHandle paramTypesArray = doDestroy.newArray(Class.class, doDestroy.load(referenceHandles.length)); + ResultHandle argsArray = doDestroy.newArray(Object.class, doDestroy.load(referenceHandles.length)); for (int i = 0; i < referenceHandles.length; i++) { - destroy.writeArrayValue(paramTypesArray, i, - destroy.loadClass(disposerMethod.parameterType(i).name().toString())); - destroy.writeArrayValue(argsArray, i, referenceHandles[i]); + doDestroy.writeArrayValue(paramTypesArray, i, + doDestroy.loadClass(disposerMethod.parameterType(i).name().toString())); + doDestroy.writeArrayValue(argsArray, i, referenceHandles[i]); } reflectionRegistration.registerMethod(disposerMethod); - destroy.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, - destroy.loadClass(disposerMethod.declaringClass().name().toString()), - destroy.load(disposerMethod.name()), paramTypesArray, declaringProviderInstanceHandle, argsArray); + doDestroy.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, + doDestroy.loadClass(disposerMethod.declaringClass().name().toString()), + doDestroy.load(disposerMethod.name()), paramTypesArray, declaringProviderInstanceHandle, argsArray); } else if (isStatic) { - destroy.invokeStaticMethod(MethodDescriptor.of(disposerMethod), referenceHandles); + doDestroy.invokeStaticMethod(MethodDescriptor.of(disposerMethod), referenceHandles); } else { - destroy.invokeVirtualMethod(MethodDescriptor.of(disposerMethod), declaringProviderInstanceHandle, + doDestroy.invokeVirtualMethod(MethodDescriptor.of(disposerMethod), declaringProviderInstanceHandle, referenceHandles); } // Destroy @Dependent instances injected into method parameters of a disposer method - destroy.invokeInterfaceMethod(MethodDescriptors.CREATIONAL_CTX_RELEASE, ctxHandle); + doDestroy.invokeInterfaceMethod(MethodDescriptors.CREATIONAL_CTX_RELEASE, ctxHandle); // If the declaring bean is @Dependent and the disposer is not static, we must destroy the instance afterwards if (BuiltinScope.DEPENDENT.is(bean.getDisposer().getDeclaringBean().getScope()) && !isStatic) { - destroy.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_BEAN_DESTROY, declaringProviderHandle, + doDestroy.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_BEAN_DESTROY, declaringProviderHandle, declaringProviderInstanceHandle, ctxHandle); } // ctx.release() - destroy.invokeInterfaceMethod(MethodDescriptors.CREATIONAL_CTX_RELEASE, destroy.getMethodParam(1)); - destroy.returnValue(null); + doDestroy.invokeInterfaceMethod(MethodDescriptors.CREATIONAL_CTX_RELEASE, doDestroy.getMethodParam(1)); + doDestroy.returnValue(null); } else if (bean.isSynthetic()) { - bean.getDestroyerConsumer().accept(destroy); + bean.getDestroyerConsumer().accept(doDestroy); } + MethodCreator destroy = beanCreator + .getMethodCreator("destroy", void.class, providerType.descriptorName(), CreationalContext.class) + .setModifiers(ACC_PUBLIC); + + TryBlock tryBlock = destroy.tryBlock(); + tryBlock.invokeSpecialMethod(doDestroy.getMethodDescriptor(), tryBlock.getThis(), + tryBlock.getMethodParam(0), tryBlock.getMethodParam(1)); + + CatchBlockCreator catchBlock = tryBlock.addCatch(Throwable.class); + ResultHandle error = catchBlock.load("Error occurred while destroying instance of " + bean); + ResultHandle logger = catchBlock.readStaticField(FieldDescriptor.of(UncaughtExceptions.class, "LOGGER", Logger.class)); + ResultHandle isDebugEnabled = catchBlock + .invokeVirtualMethod(MethodDescriptor.ofMethod(Logger.class, "isDebugEnabled", boolean.class), logger); + BranchResult branch = catchBlock.ifFalse(isDebugEnabled); + branch.falseBranch().invokeVirtualMethod( + MethodDescriptor.ofMethod(Logger.class, "error", void.class, Object.class, Throwable.class), + logger, error, catchBlock.getCaughtException()); + ResultHandle fullError = Gizmo.newStringBuilder(branch.trueBranch()) + .append(error) + .append(": ") + .append(catchBlock.getCaughtException()) + .callToString(); + branch.trueBranch().invokeVirtualMethod( + MethodDescriptor.ofMethod(Logger.class, "error", void.class, Object.class), + logger, fullError); + + destroy.returnVoid(); + // Bridge method needed MethodCreator bridgeDestroy = beanCreator.getMethodCreator("destroy", void.class, Object.class, CreationalContext.class) .setModifiers(ACC_PUBLIC | ACC_BRIDGE); @@ -2102,6 +2143,43 @@ protected void implementIsSuppressed(BeanInfo bean, ClassCreator beanCreator) { isSuppressed.returnValue(isSuppressed.load(false)); } + private void implementGetInjectionPoints(BeanInfo bean, ClassCreator beanCreator) { + // this is practically never used at runtime, but it makes the `Bean` classes bigger; + // let's only implement `getInjectionPoints()` in strict mode, to be able to pass the TCK + if (!bean.getDeployment().strictCompatibility) { + return; + } + + List injectionPoints = bean.getAllInjectionPoints(); + if (injectionPoints.isEmpty()) { + // inherit the default implementation from `InjectableBean` + return; + } + + MethodCreator getInjectionPoints = beanCreator.getMethodCreator("getInjectionPoints", Set.class); + + ResultHandle tccl = getInjectionPoints.invokeVirtualMethod(MethodDescriptors.THREAD_GET_TCCL, + getInjectionPoints.invokeStaticMethod(MethodDescriptors.THREAD_CURRENT_THREAD)); + + ResultHandle result = getInjectionPoints.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); + for (InjectionPointInfo injectionPoint : injectionPoints) { + ResultHandle type = Types.getTypeHandle(getInjectionPoints, injectionPoint.getType(), tccl); + ResultHandle qualifiers = collectInjectionPointQualifiers(bean.getDeployment(), getInjectionPoints, + injectionPoint, annotationLiterals); + ResultHandle annotations = collectInjectionPointAnnotations(bean.getDeployment(), getInjectionPoints, + injectionPoint, annotationLiterals, injectionPointAnnotationsPredicate); + ResultHandle member = getJavaMemberHandle(getInjectionPoints, injectionPoint, reflectionRegistration); + + ResultHandle ip = getInjectionPoints.newInstance(MethodDescriptors.INJECTION_POINT_IMPL_CONSTRUCTOR, + type, type, qualifiers, getInjectionPoints.getThis(), annotations, member, + getInjectionPoints.load(injectionPoint.getPosition()), + getInjectionPoints.load(injectionPoint.isTransient())); + getInjectionPoints.invokeInterfaceMethod(MethodDescriptors.SET_ADD, result, ip); + + } + getInjectionPoints.returnValue(result); + } + private String getProxyTypeName(BeanInfo bean, String baseName) { StringBuilder proxyTypeName = new StringBuilder(); proxyTypeName.append(bean.getClientProxyPackageName()); @@ -2113,15 +2191,14 @@ private String getProxyTypeName(BeanInfo bean, String baseName) { return proxyTypeName.toString(); } - private ResultHandle wrapCurrentInjectionPoint(ClassOutput classOutput, ClassCreator beanCreator, BeanInfo bean, + private ResultHandle wrapCurrentInjectionPoint(BeanInfo bean, MethodCreator constructor, InjectionPointInfo injectionPoint, int paramIdx, ResultHandle tccl, ReflectionRegistration reflectionRegistration) { - ResultHandle requiredQualifiersHandle = collectInjectionPointQualifiers(classOutput, beanCreator, bean.getDeployment(), + ResultHandle requiredQualifiersHandle = collectInjectionPointQualifiers(bean.getDeployment(), constructor, injectionPoint, annotationLiterals); - ResultHandle annotationsHandle = collectInjectionPointAnnotations(classOutput, beanCreator, bean.getDeployment(), + ResultHandle annotationsHandle = collectInjectionPointAnnotations(bean.getDeployment(), constructor, injectionPoint, annotationLiterals, injectionPointAnnotationsPredicate); ResultHandle javaMemberHandle = getJavaMemberHandle(constructor, injectionPoint, reflectionRegistration); - boolean isTransient = injectionPoint.isField() && Modifier.isTransient(injectionPoint.getTarget().asField().flags()); // TODO empty IP for synthetic injections @@ -2133,7 +2210,7 @@ private ResultHandle wrapCurrentInjectionPoint(ClassOutput classOutput, ClassCre Types.getTypeHandle(constructor, injectionPoint.getType(), tccl), requiredQualifiersHandle, annotationsHandle, javaMemberHandle, constructor.load(injectionPoint.getPosition()), - constructor.load(isTransient)); + constructor.load(injectionPoint.isTransient())); } private void initializeProxy(BeanInfo bean, String baseName, ClassCreator beanCreator) { @@ -2158,16 +2235,16 @@ private void initializeProxy(BeanInfo bean, String baseName, ClassCreator beanCr proxy.returnValue(proxyInstance); } - public static ResultHandle getJavaMemberHandle(MethodCreator constructor, - InjectionPointInfo injectionPoint, ReflectionRegistration reflectionRegistration) { + public static ResultHandle getJavaMemberHandle(MethodCreator bytecode, InjectionPointInfo injectionPoint, + ReflectionRegistration reflectionRegistration) { ResultHandle javaMemberHandle; if (injectionPoint.isSynthetic()) { - javaMemberHandle = constructor.loadNull(); + javaMemberHandle = bytecode.loadNull(); } else if (injectionPoint.isField()) { FieldInfo field = injectionPoint.getTarget().asField(); - javaMemberHandle = constructor.invokeStaticMethod(MethodDescriptors.REFLECTIONS_FIND_FIELD, - constructor.loadClass(field.declaringClass().name().toString()), - constructor.load(field.name())); + javaMemberHandle = bytecode.invokeStaticMethod(MethodDescriptors.REFLECTIONS_FIND_FIELD, + bytecode.loadClass(field.declaringClass().name().toString()), + bytecode.load(field.name())); reflectionRegistration.registerField(field); } else { MethodInfo method = injectionPoint.getTarget().asMethod(); @@ -2175,39 +2252,39 @@ public static ResultHandle getJavaMemberHandle(MethodCreator constructor, if (method.name().equals(Methods.INIT)) { // Reflections.findConstructor(org.foo.SimpleBean.class,java.lang.String.class) ResultHandle[] paramsHandles = new ResultHandle[2]; - paramsHandles[0] = constructor.loadClass(method.declaringClass().name().toString()); - ResultHandle paramsArray = constructor.newArray(Class.class, constructor.load(method.parametersCount())); + paramsHandles[0] = bytecode.loadClass(method.declaringClass().name().toString()); + ResultHandle paramsArray = bytecode.newArray(Class.class, bytecode.load(method.parametersCount())); for (ListIterator iterator = method.parameterTypes().listIterator(); iterator.hasNext();) { - constructor.writeArrayValue(paramsArray, iterator.nextIndex(), - constructor.loadClass(iterator.next().name().toString())); + bytecode.writeArrayValue(paramsArray, iterator.nextIndex(), + bytecode.loadClass(iterator.next().name().toString())); } paramsHandles[1] = paramsArray; - javaMemberHandle = constructor.invokeStaticMethod(MethodDescriptors.REFLECTIONS_FIND_CONSTRUCTOR, + javaMemberHandle = bytecode.invokeStaticMethod(MethodDescriptors.REFLECTIONS_FIND_CONSTRUCTOR, paramsHandles); } else { // Reflections.findMethod(org.foo.SimpleBean.class,"foo",java.lang.String.class) ResultHandle[] paramsHandles = new ResultHandle[3]; - paramsHandles[0] = constructor.loadClass(method.declaringClass().name().toString()); - paramsHandles[1] = constructor.load(method.name()); - ResultHandle paramsArray = constructor.newArray(Class.class, constructor.load(method.parametersCount())); + paramsHandles[0] = bytecode.loadClass(method.declaringClass().name().toString()); + paramsHandles[1] = bytecode.load(method.name()); + ResultHandle paramsArray = bytecode.newArray(Class.class, bytecode.load(method.parametersCount())); for (ListIterator iterator = method.parameterTypes().listIterator(); iterator.hasNext();) { - constructor.writeArrayValue(paramsArray, iterator.nextIndex(), - constructor.loadClass(iterator.next().name().toString())); + bytecode.writeArrayValue(paramsArray, iterator.nextIndex(), + bytecode.loadClass(iterator.next().name().toString())); } paramsHandles[2] = paramsArray; - javaMemberHandle = constructor.invokeStaticMethod(MethodDescriptors.REFLECTIONS_FIND_METHOD, paramsHandles); + javaMemberHandle = bytecode.invokeStaticMethod(MethodDescriptors.REFLECTIONS_FIND_METHOD, paramsHandles); } } return javaMemberHandle; } - public static ResultHandle collectInjectionPointAnnotations(ClassOutput classOutput, ClassCreator beanCreator, - BeanDeployment beanDeployment, MethodCreator constructor, InjectionPointInfo injectionPoint, - AnnotationLiteralProcessor annotationLiterals, Predicate injectionPointAnnotationsPredicate) { + public static ResultHandle collectInjectionPointAnnotations(BeanDeployment beanDeployment, MethodCreator bytecode, + InjectionPointInfo injectionPoint, AnnotationLiteralProcessor annotationLiterals, + Predicate injectionPointAnnotationsPredicate) { if (injectionPoint.isSynthetic()) { - return constructor.invokeStaticMethod(MethodDescriptors.COLLECTIONS_EMPTY_SET); + return bytecode.invokeStaticMethod(MethodDescriptors.COLLECTIONS_EMPTY_SET); } - ResultHandle annotationsHandle = constructor.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); + ResultHandle annotationsHandle = bytecode.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); Collection annotations; if (Kind.FIELD.equals(injectionPoint.getTarget().kind())) { FieldInfo field = injectionPoint.getTarget().asField(); @@ -2223,51 +2300,49 @@ public static ResultHandle collectInjectionPointAnnotations(ClassOutput classOut } ResultHandle annotationHandle; if (DotNames.INJECT.equals(annotation.name())) { - annotationHandle = constructor + annotationHandle = bytecode .readStaticField(FieldDescriptor.of(InjectLiteral.class, "INSTANCE", InjectLiteral.class)); } else { ClassInfo annotationClass = getClassByName(beanDeployment.getBeanArchiveIndex(), annotation.name()); if (annotationClass == null) { continue; } - annotationHandle = annotationLiterals.create(constructor, annotationClass, annotation); + annotationHandle = annotationLiterals.create(bytecode, annotationClass, annotation); } - constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, annotationsHandle, + bytecode.invokeInterfaceMethod(MethodDescriptors.SET_ADD, annotationsHandle, annotationHandle); } return annotationsHandle; } - public static ResultHandle collectInjectionPointQualifiers(ClassOutput classOutput, ClassCreator beanCreator, - BeanDeployment beanDeployment, MethodCreator constructor, InjectionPointInfo injectionPoint, - AnnotationLiteralProcessor annotationLiterals) { - return collectQualifiers(classOutput, beanCreator, beanDeployment, constructor, annotationLiterals, + public static ResultHandle collectInjectionPointQualifiers(BeanDeployment beanDeployment, MethodCreator bytecode, + InjectionPointInfo injectionPoint, AnnotationLiteralProcessor annotationLiterals) { + return collectQualifiers(beanDeployment, bytecode, annotationLiterals, injectionPoint.hasDefaultedQualifier() ? Collections.emptySet() : injectionPoint.getRequiredQualifiers()); } - public static ResultHandle collectQualifiers(ClassOutput classOutput, ClassCreator beanCreator, - BeanDeployment beanDeployment, MethodCreator constructor, AnnotationLiteralProcessor annotationLiterals, - Set requiredQualifiers) { - ResultHandle requiredQualifiersHandle; - if (requiredQualifiers.isEmpty()) { - requiredQualifiersHandle = constructor.readStaticField(FieldDescriptors.QUALIFIERS_IP_QUALIFIERS); + public static ResultHandle collectQualifiers(BeanDeployment beanDeployment, MethodCreator bytecode, + AnnotationLiteralProcessor annotationLiterals, Set qualifiers) { + ResultHandle qualifiersHandle; + if (qualifiers.isEmpty()) { + qualifiersHandle = bytecode.readStaticField(FieldDescriptors.QUALIFIERS_IP_QUALIFIERS); } else { - requiredQualifiersHandle = constructor.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); - for (AnnotationInstance qualifierAnnotation : requiredQualifiers) { + qualifiersHandle = bytecode.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); + for (AnnotationInstance qualifierAnnotation : qualifiers) { BuiltinQualifier qualifier = BuiltinQualifier.of(qualifierAnnotation); ResultHandle qualifierHandle; if (qualifier != null) { - qualifierHandle = qualifier.getLiteralInstance(constructor); + qualifierHandle = qualifier.getLiteralInstance(bytecode); } else { // Create annotation literal if needed - qualifierHandle = annotationLiterals.create(constructor, + qualifierHandle = annotationLiterals.create(bytecode, beanDeployment.getQualifier(qualifierAnnotation.name()), qualifierAnnotation); } - constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, requiredQualifiersHandle, + bytecode.invokeInterfaceMethod(MethodDescriptors.SET_ADD, qualifiersHandle, qualifierHandle); } } - return requiredQualifiersHandle; + return qualifiersHandle; } static void destroyTransientReferences(BytecodeCreator bytecode, Iterable transientReferences) { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java index fcf2b7e5b754d..160e9cb46cba8 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java @@ -2,6 +2,7 @@ import static io.quarkus.arc.processor.IndexClassLookupUtils.getClassByName; +import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -190,6 +191,20 @@ public boolean isProducerField() { return target.isPresent() && Kind.FIELD.equals(target.get().kind()); } + public boolean isProducer() { + return isProducerMethod() || isProducerField(); + } + + public boolean isStaticProducer() { + if (isProducerField()) { + return Modifier.isStatic(target.get().asField().flags()); + } else if (isProducerMethod()) { + return Modifier.isStatic(target.get().asMethod().flags()); + } else { + return false; + } + } + public boolean isSynthetic() { return !target.isPresent(); } @@ -532,7 +547,7 @@ public String getTargetPackageName() { return targetPackageName; } DotName providerTypeName; - if (isProducerMethod() || isProducerField()) { + if (isProducer()) { providerTypeName = declaringBean.getProviderType().name(); } else { if (providerType.kind() == org.jboss.jandex.Type.Kind.ARRAY @@ -551,7 +566,7 @@ public String getTargetPackageName() { } public String getClientProxyPackageName() { - if (isProducerField() || isProducerMethod()) { + if (isProducer()) { AnnotationTarget target = getTarget().get(); DotName typeName = target.kind() == Kind.FIELD ? target.asField().type().name() : target.asMethod().returnType().name(); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanStream.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanStream.java index a3c155faee4d4..369fc220f89b3 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanStream.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanStream.java @@ -218,7 +218,7 @@ public Optional findByIdentifier(String id) { * @return the new stream of producer beans */ public BeanStream producers() { - stream = stream.filter(bean -> bean.isProducerField() || bean.isProducerMethod()); + stream = stream.filter(BeanInfo::isProducer); return this; } 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 5e35c2d14530d..f6200b874084f 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 @@ -64,9 +64,11 @@ private static ScopeInfo inheritScope(ClassInfo beanClass, BeanDeployment beanDe } for (AnnotationInstance annotation : beanDeployment.getAnnotationStore().getAnnotations(classFromIndex)) { ScopeInfo scopeAnnotation = beanDeployment.getScope(annotation.name()); - if (scopeAnnotation != null && scopeAnnotation.declaresInherited()) { - // found some scope, return - return scopeAnnotation; + if (scopeAnnotation != null) { + // found some scope, return it if it's inherited + // if it isn't inherited, it still prevents the bean class + // from inheriting another scope from a further superclass + return scopeAnnotation.declaresInherited() ? scopeAnnotation : null; } } superClassName = classFromIndex.superName(); @@ -787,7 +789,7 @@ static void validateBean(BeanInfo bean, List errors, Consumer generate(String name, BeanDeployment beanDeployment, Map Foo, Baz - // Foo -> Baz - // Interceptor -> Baz - Map> beanToInjections = initBeanToInjections(beanDeployment); + Map> dependencyMap = initBeanDependencyMap(beanDeployment); // Break bean processing into multiple addBeans() methods // Map> ResultHandle beanIdToBeanHandle = getComponents.newInstance(MethodDescriptor.ofConstructor(HashMap.class)); - processBeans(componentsProvider, getComponents, beanIdToBeanHandle, beanToInjections, beanToGeneratedName, + processBeans(componentsProvider, getComponents, beanIdToBeanHandle, dependencyMap, beanToGeneratedName, beanDeployment); // Break observers processing into multiple addObservers() methods @@ -189,55 +185,52 @@ Collection generate(String name, BeanDeployment beanDeployment, Map> beanToInjections, + Map> dependencyMap, Map beanToGeneratedName, BeanDeployment beanDeployment) { Set processed = new HashSet<>(); BeanAdder beanAdder = new BeanAdder(componentsProvider, getComponents, processed, beanIdToBeanHandle, beanToGeneratedName); - // - iterate over beanToInjections entries and process beans for which all dependencies are already present in the map + // - iterate over dependencyMap entries and process beans for which all dependencies were already processed // - when a bean is processed the map entry is removed - // - if we're stuck and the map is not empty ISE is thrown - boolean stuck = false; + // - if we're stuck and the map is not empty, we found a circular dependency (and throw an ISE) Predicate isNotDependencyPredicate = new Predicate() { @Override public boolean test(BeanInfo b) { - return !isDependency(b, beanToInjections); + return !isDependency(b, dependencyMap); } }; Predicate isNormalScopedOrNotDependencyPredicate = new Predicate() { @Override public boolean test(BeanInfo b) { - return b.getScope().isNormal() || !isDependency(b, beanToInjections); + return b.getScope().isNormal() || !isDependency(b, dependencyMap); } }; Predicate isNotProducerOrNormalScopedOrNotDependencyPredicate = new Predicate() { @Override public boolean test(BeanInfo b) { // Try to process non-producer beans first, including declaring beans of producers - if (b.isProducerField() || b.isProducerMethod()) { + if (b.isProducer()) { return false; } - return b.getScope().isNormal() || !isDependency(b, beanToInjections); + return b.getScope().isNormal() || !isDependency(b, dependencyMap); } }; - while (!beanToInjections.isEmpty()) { + boolean stuck = false; + while (!dependencyMap.isEmpty()) { if (stuck) { - throw circularDependenciesNotSupportedException(beanToInjections); + throw circularDependenciesNotSupportedException(dependencyMap); } stuck = true; // First try to process beans that are not dependencies - stuck = addBeans(beanAdder, beanToInjections, processed, beanIdToBeanHandle, - beanToGeneratedName, isNotDependencyPredicate); + stuck = addBeans(beanAdder, dependencyMap, processed, isNotDependencyPredicate); if (stuck) { // It seems we're stuck but we can try to process normal scoped beans that can prevent a circular dependency - stuck = addBeans(beanAdder, beanToInjections, processed, beanIdToBeanHandle, - beanToGeneratedName, isNotProducerOrNormalScopedOrNotDependencyPredicate); + stuck = addBeans(beanAdder, dependencyMap, processed, isNotProducerOrNormalScopedOrNotDependencyPredicate); if (stuck) { - stuck = addBeans(beanAdder, beanToInjections, processed, - beanIdToBeanHandle, beanToGeneratedName, isNormalScopedOrNotDependencyPredicate); + stuck = addBeans(beanAdder, dependencyMap, processed, isNormalScopedOrNotDependencyPredicate); } } } @@ -298,38 +291,73 @@ private void processRemovedBeans(ClassCreator componentsProvider, BytecodeCreato } } - private Map> initBeanToInjections(BeanDeployment beanDeployment) { - Function> computeNewArrayFun = new Function>() { - + /** + * Returns a dependency map for bean instantiation. Say the following beans exist: + * + *
+     * class Foo {
+     *     @Inject
+     *     Bar bar;
+     *
+     *     @Inject
+     *     Baz baz;
+     * }
+     *
+     * class Bar {
+     *     @Inject
+     *     Baz baz;
+     * }
+     *
+     * class Baz {
+     * }
+     * 
+ * + * To create an instance of {@code Foo}, instances of {@code Bar} and {@code Baz} must already exist. + * Further, to create an instance of {@code Bar}, an instance of {@code Baz} must already exist. + * The returned map contains this information in the reverse form: + * + *
+     * Foo -> []
+     * Bar -> [Foo]
+     * Baz -> [Foo, Bar]
+     * 
+ * + * The key in this map is a bean and the value is a list of beans that depend on the key. In other words, + * the key is a dependency and the value is a list of its dependants. + */ + private Map> initBeanDependencyMap(BeanDeployment beanDeployment) { + Function> newArrayList = new Function>() { @Override public List apply(BeanInfo b) { return new ArrayList<>(); } }; + Map> beanToInjections = new HashMap<>(); for (BeanInfo bean : beanDeployment.getBeans()) { - if (bean.isProducerMethod() || bean.isProducerField()) { - beanToInjections.computeIfAbsent(bean.getDeclaringBean(), computeNewArrayFun).add(bean); + if (bean.isProducer() && !bean.isStaticProducer()) { + // `static` producer doesn't depend on its declaring bean + beanToInjections.computeIfAbsent(bean.getDeclaringBean(), newArrayList).add(bean); } for (Injection injection : bean.getInjections()) { for (InjectionPointInfo injectionPoint : injection.injectionPoints) { if (!BuiltinBean.resolvesTo(injectionPoint)) { - beanToInjections.computeIfAbsent(injectionPoint.getResolvedBean(), computeNewArrayFun).add(bean); + beanToInjections.computeIfAbsent(injectionPoint.getResolvedBean(), newArrayList).add(bean); } } } if (bean.getDisposer() != null) { for (InjectionPointInfo injectionPoint : bean.getDisposer().getInjection().injectionPoints) { if (!BuiltinBean.resolvesTo(injectionPoint)) { - beanToInjections.computeIfAbsent(injectionPoint.getResolvedBean(), computeNewArrayFun).add(bean); + beanToInjections.computeIfAbsent(injectionPoint.getResolvedBean(), newArrayList).add(bean); } } } for (InterceptorInfo interceptor : bean.getBoundInterceptors()) { - beanToInjections.computeIfAbsent(interceptor, computeNewArrayFun).add(bean); + beanToInjections.computeIfAbsent(interceptor, newArrayList).add(bean); } for (DecoratorInfo decorator : bean.getBoundDecorators()) { - beanToInjections.computeIfAbsent(decorator, computeNewArrayFun).add(bean); + beanToInjections.computeIfAbsent(decorator, newArrayList).add(bean); } } // Also process interceptor and decorator injection points @@ -337,7 +365,7 @@ public List apply(BeanInfo b) { for (Injection injection : interceptor.getInjections()) { for (InjectionPointInfo injectionPoint : injection.injectionPoints) { if (!BuiltinBean.resolvesTo(injectionPoint)) { - beanToInjections.computeIfAbsent(injectionPoint.getResolvedBean(), computeNewArrayFun) + beanToInjections.computeIfAbsent(injectionPoint.getResolvedBean(), newArrayList) .add(interceptor); } } @@ -347,7 +375,7 @@ public List apply(BeanInfo b) { for (Injection injection : decorator.getInjections()) { for (InjectionPointInfo injectionPoint : injection.injectionPoints) { if (!injectionPoint.isDelegate() && !BuiltinBean.resolvesTo(injectionPoint)) { - beanToInjections.computeIfAbsent(injectionPoint.getResolvedBean(), computeNewArrayFun) + beanToInjections.computeIfAbsent(injectionPoint.getResolvedBean(), newArrayList) .add(decorator); } } @@ -357,14 +385,11 @@ public List apply(BeanInfo b) { return beanToInjections; } - private boolean addBeans(BeanAdder beanAdder, - Map> beanToInjections, Set processed, - ResultHandle beanIdToBeanHandle, Map beanToGeneratedName, Predicate filter) { + private boolean addBeans(BeanAdder beanAdder, Map> beanToInjections, + Set processed, Predicate filter) { boolean stuck = true; - for (Iterator>> iterator = beanToInjections.entrySet().iterator(); iterator - .hasNext();) { - Entry> entry = iterator.next(); - BeanInfo bean = entry.getKey(); + for (Iterator iterator = beanToInjections.keySet().iterator(); iterator.hasNext();) { + BeanInfo bean = iterator.next(); if (filter.test(bean)) { iterator.remove(); beanAdder.addComponent(bean); @@ -375,10 +400,9 @@ private boolean addBeans(BeanAdder beanAdder, return stuck; } - private boolean isDependency(BeanInfo bean, Map> beanToInjections) { - for (Iterator>> iterator = beanToInjections.entrySet().iterator(); iterator.hasNext();) { - Entry> entry = iterator.next(); - if (entry.getValue().contains(bean)) { + private boolean isDependency(BeanInfo bean, Map> dependencyMap) { + for (List dependants : dependencyMap.values()) { + if (dependants.contains(bean)) { return true; } } @@ -654,14 +678,16 @@ void addComponentInternal(BeanInfo bean) { List params = new ArrayList<>(); List paramTypes = new ArrayList<>(); - if (bean.isProducerMethod() || bean.isProducerField()) { - if (!processedBeans.contains(bean.getDeclaringBean())) { - throw new IllegalStateException( - "Declaring bean of a producer bean is not available - most probably an unsupported circular dependency use case \n - declaring bean: " - + bean.getDeclaringBean() + "\n - producer bean: " + bean); + if (bean.isProducer()) { + if (processedBeans.contains(bean.getDeclaringBean())) { + params.add(addMethod.invokeInterfaceMethod(MethodDescriptors.MAP_GET, + beanIdToBeanHandle, addMethod.load(bean.getDeclaringBean().getIdentifier()))); + } else { + // Declaring bean was not processed yet - use MapValueSupplier + params.add(addMethod.newInstance( + MethodDescriptors.MAP_VALUE_SUPPLIER_CONSTRUCTOR, + beanIdToBeanHandle, addMethod.load(bean.getDeclaringBean().getIdentifier()))); } - params.add(addMethod.invokeInterfaceMethod(MethodDescriptors.MAP_GET, - beanIdToBeanHandle, addMethod.load(bean.getDeclaringBean().getIdentifier()))); paramTypes.add(Type.getDescriptor(Supplier.class)); } for (InjectionPointInfo injectionPoint : injectionPoints) { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DotNames.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DotNames.java index f70e5577ff3ad..556ec8c7b71f9 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DotNames.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DotNames.java @@ -16,6 +16,7 @@ import jakarta.decorator.Delegate; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.Initialized; +import jakarta.enterprise.context.NormalScope; import jakarta.enterprise.context.control.ActivateRequestContext; import jakarta.enterprise.event.Event; import jakarta.enterprise.event.Observes; @@ -44,6 +45,7 @@ import jakarta.inject.Named; import jakarta.inject.Provider; import jakarta.inject.Qualifier; +import jakarta.inject.Scope; import jakarta.inject.Singleton; import jakarta.interceptor.AroundConstruct; import jakarta.interceptor.AroundInvoke; @@ -104,6 +106,8 @@ public final class DotNames { public static final DotName EVENT_METADATA = create(EventMetadata.class); public static final DotName ALTERNATIVE = create(Alternative.class); public static final DotName DEFAULT_BEAN = create(DefaultBean.class); + public static final DotName SCOPE = create(Scope.class); + public static final DotName NORMAL_SCOPE = create(NormalScope.class); public static final DotName SINGLETON = create(Singleton.class); public static final DotName APPLICATION_SCOPED = create(ApplicationScoped.class); public static final DotName STEREOTYPE = create(Stereotype.class); 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 67997481ce130..7bc44dbce45c8 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 @@ -2,6 +2,7 @@ import static io.quarkus.arc.processor.Annotations.contains; +import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -244,6 +245,10 @@ public boolean isParam() { return target != null && target.kind() == Kind.METHOD; } + public boolean isTransient() { + return isField() && Modifier.isTransient(target.asField().flags()); + } + /** * * @return true if this injection point represents a method parameter annotated with {@code TransientReference} that 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 45407549f2781..2ab3b90a7bc87 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 @@ -552,19 +552,15 @@ protected void createConstructor(ClassOutput classOutput, ClassCreator observerC annotationLiterals, observer, reflectionRegistration, injectionPointAnnotationsPredicate)); } else { if (injectionPoint.isCurrentInjectionPointWrapperNeeded()) { - ResultHandle requiredQualifiersHandle = BeanGenerator.collectInjectionPointQualifiers(classOutput, - observerCreator, + ResultHandle requiredQualifiersHandle = BeanGenerator.collectInjectionPointQualifiers( observer.getDeclaringBean().getDeployment(), constructor, injectionPoint, annotationLiterals); - ResultHandle annotationsHandle = BeanGenerator.collectInjectionPointAnnotations(classOutput, - observerCreator, + ResultHandle annotationsHandle = BeanGenerator.collectInjectionPointAnnotations( observer.getDeclaringBean().getDeployment(), constructor, injectionPoint, annotationLiterals, 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( @@ -578,7 +574,7 @@ protected void createConstructor(ClassOutput classOutput, ClassCreator observerC Types.getTypeHandle(constructor, injectionPoint.getType()), requiredQualifiersHandle, annotationsHandle, javaMemberHandle, constructor.load(injectionPoint.getPosition()), - constructor.load(isTransient)); + constructor.load(injectionPoint.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/UnusedBeans.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/UnusedBeans.java index 83a6e115b8587..f4c261df3be14 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/UnusedBeans.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/UnusedBeans.java @@ -26,7 +26,7 @@ static Set findRemovableBeans(Collection beans, Collection unusedProducers = new HashSet<>(); Set unusedButDeclaresProducer = new HashSet<>(); - List producers = beans.stream().filter(b -> b.isProducerMethod() || b.isProducerField()) + List producers = beans.stream().filter(BeanInfo::isProducer) .collect(Collectors.toList()); List instanceInjectionPoints = injectionPoints.stream() .filter(InjectionPointInfo::isProgrammaticLookup) @@ -81,7 +81,7 @@ static Set findRemovableBeans(Collection beans, Collection> getStereotypes() { return Collections.emptySet(); } + /** + * By default, this method always returns an empty set, because obtaining the set + * of injection points of a bean at application runtime is rarely useful. + *

+ * In the {@linkplain ArcContainer#strictCompatibility() strict mode}, this method + * works as described by the CDI specification. Feedback on usefulness of this + * method is welcome! + */ @Override default Set getInjectionPoints() { return Collections.emptySet(); diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInstanceHandle.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInstanceHandle.java index 8776486456e15..55cc81bed93f8 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInstanceHandle.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInstanceHandle.java @@ -79,16 +79,7 @@ protected void destroyInternal() { if (parentCreationalContext != null) { parentCreationalContext.release(); } else { - try { - bean.destroy(instanceInternal(), creationalContext); - } catch (Throwable t) { - String msg = "Error occurred while destroying instance of bean [%s]"; - if (LOGGER.isDebugEnabled()) { - LOGGER.errorf(t, msg, bean.getClass().getName()); - } else { - LOGGER.errorf(msg + ": %s", bean.getClass().getName(), t); - } - } + bean.destroy(instanceInternal(), creationalContext); } } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java index 2310027778dc5..4d1afc751b964 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java @@ -155,7 +155,7 @@ public ArcContainerImpl(CurrentContextFactory currentContextFactory, boolean str } resourceProviders.trimToSize(); - instance = InstanceImpl.of(Object.class, Collections.emptySet()); + instance = InstanceImpl.forGlobalEntrypoint(Object.class, Collections.emptySet()); this.beans = List.copyOf(beans); this.interceptors = List.copyOf(interceptors); 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 aa47c4be52cbe..4e6aeb0bfa9ed 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 @@ -60,8 +60,15 @@ public Object getReference(Bean bean, Type beanType, CreationalContext ctx + "; its bean types are: " + bean.getTypes()); } if (bean instanceof InjectableBean && ctx instanceof CreationalContextImpl) { - return ArcContainerImpl.beanInstanceHandle((InjectableBean) bean, (CreationalContextImpl) ctx, true, null, true) - .get(); + // there's no actual injection point or an `Instance` object, + // the "current" injection point must be `null` + InjectionPoint prev = InjectionPointProvider.set(null); + try { + return ArcContainerImpl.beanInstanceHandle((InjectableBean) bean, (CreationalContextImpl) ctx, + false, null, true).get(); + } finally { + InjectionPointProvider.set(prev); + } } throw new IllegalArgumentException( "Arguments must be instances of " + InjectableBean.class + " and " + CreationalContextImpl.class + ": \nbean: " @@ -81,7 +88,8 @@ public Object getInjectableReference(InjectionPoint ij, CreationalContext ctx InjectableBean bean = (InjectableBean) resolve(beans); InjectionPoint prev = InjectionPointProvider.set(ij); try { - return ArcContainerImpl.beanInstanceHandle(bean, (CreationalContextImpl) ctx, false, null, true).get(); + return ArcContainerImpl.beanInstanceHandle(bean, (CreationalContextImpl) ctx, + false, null, true).get(); } finally { InjectionPointProvider.set(prev); } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceBean.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceBean.java index caf276abea47a..0fc33ce7e6c96 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceBean.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceBean.java @@ -31,7 +31,7 @@ public Class getBeanClass() { public Instance 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(), + InstanceImpl> instance = InstanceImpl.forInjection((InjectableBean) ip.getBean(), ip.getType(), ip.getQualifiers(), (CreationalContextImpl) creationalContext, Collections.EMPTY_SET, ip.getMember(), 0, ip.isTransient()); CreationalContextImpl.addDependencyToParent((InjectableBean>) ip.getBean(), instance, creationalContext); 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 659aa9f1a80fa..4c84a064e46d4 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 @@ -41,8 +41,8 @@ public class InstanceImpl implements InjectableInstance { public static Instance forSynthesis(CreationalContextImpl creationalContext, boolean allowInjectionPointLookup) { - InstanceImpl result = new InstanceImpl<>(null, null, Object.class, Collections.emptySet(), - creationalContext, Collections.emptySet(), null, -1, false, false); + InstanceImpl result = new InstanceImpl<>(creationalContext, Object.class, Collections.emptySet(), + null, null, Collections.emptySet(), null, -1, false, false); if (allowInjectionPointLookup) { return result; } @@ -119,15 +119,21 @@ public T get() { return new Guard<>(result); } - static InstanceImpl of(Type requiredType, Set requiredQualifiers) { - return new InstanceImpl<>(null, null, requiredType, requiredQualifiers, - new CreationalContextImpl<>(null), - Collections.emptySet(), null, -1, false, true); + static InstanceImpl forGlobalEntrypoint(Type requiredType, Set requiredQualifiers) { + return new InstanceImpl<>(new CreationalContextImpl<>(null), requiredType, requiredQualifiers, + null, null, Collections.emptySet(), null, -1, false, true); + } + + static InstanceImpl forInjection(InjectableBean targetBean, Type type, Set qualifiers, + CreationalContextImpl creationalContext, Set annotations, Member javaMember, int position, + boolean isTransient) { + return new InstanceImpl<>(creationalContext, getRequiredType(type), qualifiers, + type, targetBean, annotations, javaMember, position, isTransient, true); } private static InstanceImpl child(InstanceImpl parent, Type requiredType, Set requiredQualifiers) { - return new InstanceImpl<>(parent.targetBean, parent.injectionPointType, requiredType, requiredQualifiers, - parent.creationalContext, parent.annotations, parent.javaMember, parent.position, parent.isTransient, + return new InstanceImpl<>(parent.creationalContext, requiredType, requiredQualifiers, parent.injectionPointType, + parent.targetBean, parent.annotations, parent.javaMember, parent.position, parent.isTransient, parent.resetCurrentInjectionPoint); } @@ -149,32 +155,28 @@ private static InstanceImpl child(InstanceImpl parent, Type requiredTy private final LazyValue cachedGetResult; - InstanceImpl(InjectableBean targetBean, Type type, Set qualifiers, - CreationalContextImpl creationalContext, Set annotations, Member javaMember, int position, - boolean isTransient) { - this(targetBean, type, getRequiredType(type), qualifiers, creationalContext, annotations, javaMember, position, - isTransient, true); - } - - private InstanceImpl(InjectableBean targetBean, Type injectionPointType, Type requiredType, - Set requiredQualifiers, CreationalContextImpl creationalContext, Set annotations, - Member javaMember, int position, boolean isTransient, boolean resetCurrentInjectionPoint) { - this.injectionPointType = injectionPointType; + private InstanceImpl(CreationalContextImpl creationalContext, Type requiredType, Set requiredQualifiers, + Type injectionPointType, InjectableBean targetBean, Set annotations, Member javaMember, + int position, boolean isTransient, boolean resetCurrentInjectionPoint) { + this.creationalContext = creationalContext; this.requiredType = requiredType; this.requiredQualifiers = requiredQualifiers != null ? requiredQualifiers : Collections.emptySet(); - this.creationalContext = creationalContext; + if (this.requiredQualifiers.isEmpty() && Object.class.equals(requiredType)) { // Do not prefetch the beans for Instance with no qualifiers this.resolvedBeans = null; } else { this.resolvedBeans = resolve(); } + + this.injectionPointType = injectionPointType; this.targetBean = targetBean; this.annotations = annotations; this.javaMember = javaMember; this.position = position; this.isTransient = isTransient; this.resetCurrentInjectionPoint = resetCurrentInjectionPoint; + this.cachedGetResult = isGetCached(annotations) ? new LazyValue<>(this::getInternal) : null; } 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 3dc821963f6bb..cb5b2e0496b71 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 @@ -39,7 +39,7 @@ public InstanceProvider(Type type, Set qualifiers, InjectableBean @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public Instance get(CreationalContext> creationalContext) { - InstanceImpl instance = new InstanceImpl(targetBean, requiredType, qualifiers, + InstanceImpl instance = InstanceImpl.forInjection(targetBean, requiredType, qualifiers, CreationalContextImpl.unwrap(creationalContext), annotations, javaMember, position, isTransient); CreationalContextImpl.addDependencyToParent(InstanceBean.INSTANCE, instance, diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/UncaughtExceptions.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/UncaughtExceptions.java new file mode 100644 index 0000000000000..10720cbdd7bd5 --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/UncaughtExceptions.java @@ -0,0 +1,7 @@ +package io.quarkus.arc.impl; + +import org.jboss.logging.Logger; + +public class UncaughtExceptions { + public static final Logger LOGGER = Logger.getLogger(UncaughtExceptions.class.getName()); +} diff --git a/independent-projects/arc/tcks/arquillian/src/main/java/io/quarkus/arc/arquillian/Deployer.java b/independent-projects/arc/tcks/arquillian/src/main/java/io/quarkus/arc/arquillian/Deployer.java index f24fb09893d61..6f02538e4e7df 100644 --- a/independent-projects/arc/tcks/arquillian/src/main/java/io/quarkus/arc/arquillian/Deployer.java +++ b/independent-projects/arc/tcks/arquillian/src/main/java/io/quarkus/arc/arquillian/Deployer.java @@ -3,7 +3,6 @@ import java.io.Closeable; import java.io.IOException; import java.io.InputStream; -import java.lang.annotation.Annotation; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -19,10 +18,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import jakarta.enterprise.inject.Stereotype; -import jakarta.inject.Qualifier; -import jakarta.interceptor.InterceptorBinding; - import org.jboss.arquillian.container.spi.client.container.DeploymentException; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; @@ -44,6 +39,7 @@ import io.quarkus.arc.processor.BeanArchives; import io.quarkus.arc.processor.BeanDefiningAnnotation; import io.quarkus.arc.processor.BeanProcessor; +import io.quarkus.arc.processor.DotNames; import io.quarkus.arc.processor.bcextensions.ExtensionsEntryPoint; final class Deployer { @@ -133,8 +129,8 @@ private void generate() throws IOException, ExecutionException, InterruptedExcep .setTransformUnproxyableClasses(false) .setRemoveUnusedBeans(false) .setBuildCompatibleExtensions(buildCompatibleExtensions) - .setAdditionalBeanDefiningAnnotations(Set.of(new BeanDefiningAnnotation( - DotName.createSimple(ExtraBean.class.getName()), null))) + .setAdditionalBeanDefiningAnnotations(Set.of( + new BeanDefiningAnnotation(DotName.createSimple(ExtraBean.class)))) .addAnnotationTransformer(new AnnotationsTransformer() { @Override public boolean appliesTo(AnnotationTarget.Kind kind) { @@ -231,12 +227,12 @@ private IndexView buildImmutableBeanArchiveIndex(Index applicationIndex, Set> metaAnnotations = Set.of(Qualifier.class, InterceptorBinding.class, Stereotype.class); - for (Class metaAnnotation : metaAnnotations) { - DotName metaAnnotationName = DotName.createSimple(metaAnnotation.getName()); - for (AnnotationInstance annotation : applicationIndex.getAnnotations(metaAnnotationName)) { + Set metaAnnotations = Set.of(DotNames.SCOPE, DotNames.NORMAL_SCOPE, DotNames.QUALIFIER, + DotNames.INTERCEPTOR_BINDING, DotNames.STEREOTYPE); + for (DotName metaAnnotation : metaAnnotations) { + for (AnnotationInstance annotation : applicationIndex.getAnnotations(metaAnnotation)) { if (annotation.target().kind().equals(AnnotationTarget.Kind.CLASS)) { String annotationClass = annotation.target().asClass().name().toString(); String classFile = annotationClass.replace('.', '/') + ".class"; diff --git a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml index f7630e55ef81d..844b4b621e59a 100644 --- a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml +++ b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml @@ -22,30 +22,13 @@ - - - - - - - - - - - + - - - - - - - - + @@ -62,19 +45,9 @@ - - - - - - - - - - - + @@ -84,34 +57,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -124,6 +69,7 @@ + @@ -148,35 +94,12 @@ - - - - - - - - - - - - - - - - - - - - - - - @@ -185,6 +108,7 @@ + @@ -192,6 +116,7 @@ + diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/destroy/BeanPreDestroyErrorTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/destroy/BeanPreDestroyErrorTest.java index a5e920e92e433..91da1069892c6 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/destroy/BeanPreDestroyErrorTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/destroy/BeanPreDestroyErrorTest.java @@ -1,15 +1,21 @@ package io.quarkus.arc.test.bean.destroy; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.math.BigDecimal; import jakarta.annotation.PreDestroy; import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.context.spi.CreationalContext; import jakarta.enterprise.inject.Disposes; import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.spi.Bean; +import jakarta.enterprise.inject.spi.BeanManager; import jakarta.inject.Singleton; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -22,20 +28,56 @@ public class BeanPreDestroyErrorTest { @RegisterExtension ArcTestContainer container = new ArcTestContainer(DestroyBean.class, DestroyProducerBean.class); + @BeforeEach + public void setup() { + DestroyBean.destroyed = false; + DestroyProducerBean.destroyed = false; + } + @Test public void testErrorSwallowed() { // Test that an exception is not rethrown InstanceHandle beanInstance = Arc.container().instance(DestroyBean.class); assertEquals(42, beanInstance.get().ping()); + assertFalse(DestroyBean.destroyed); beanInstance.destroy(); + assertTrue(DestroyBean.destroyed); InstanceHandle bigInstance = Arc.container().instance(BigDecimal.class); assertEquals(BigDecimal.ONE, bigInstance.get()); + assertFalse(DestroyProducerBean.destroyed); bigInstance.destroy(); + assertTrue(DestroyProducerBean.destroyed); + } + + @Test + public void testErrorSwallowedWithLowLevelApi() { + BeanManager bm = Arc.container().beanManager(); + + { + Bean bean = (Bean) bm.resolve(bm.getBeans(DestroyBean.class)); + CreationalContext ctx = bm.createCreationalContext(bean); + DestroyBean instance = bean.create(ctx); + assertEquals(42, instance.ping()); + assertFalse(DestroyBean.destroyed); + bean.destroy(instance, ctx); + assertTrue(DestroyBean.destroyed); + } + + { + Bean bean = (Bean) bm.resolve(bm.getBeans(BigDecimal.class)); + CreationalContext ctx = bm.createCreationalContext(bean); + BigDecimal instance = bean.create(ctx); + assertEquals(BigDecimal.ONE, instance); + assertFalse(DestroyProducerBean.destroyed); + bean.destroy(instance, ctx); + assertTrue(DestroyProducerBean.destroyed); + } } @Singleton static class DestroyBean { + static boolean destroyed; int ping() { return 42; @@ -43,6 +85,7 @@ int ping() { @PreDestroy void destroy() { + destroyed = true; throw new IllegalStateException(); } @@ -50,6 +93,7 @@ void destroy() { @Dependent static class DestroyProducerBean { + static boolean destroyed; @Produces BigDecimal produce() { @@ -57,6 +101,7 @@ BigDecimal produce() { } void dispose(@Disposes BigDecimal val) { + destroyed = true; throw new IllegalStateException(); } diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/injectionpoints/BeanInjectionPointsTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/injectionpoints/BeanInjectionPointsTest.java new file mode 100644 index 0000000000000..c9cb7f6e43ed1 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/injectionpoints/BeanInjectionPointsTest.java @@ -0,0 +1,219 @@ +package io.quarkus.arc.test.bean.injectionpoints; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.util.Set; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.event.ObservesAsync; +import jakarta.enterprise.inject.Default; +import jakarta.enterprise.inject.Disposes; +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.spi.Annotated; +import jakarta.enterprise.inject.spi.AnnotatedField; +import jakarta.enterprise.inject.spi.AnnotatedParameter; +import jakarta.enterprise.inject.spi.InjectionPoint; +import jakarta.enterprise.util.AnnotationLiteral; +import jakarta.inject.Inject; +import jakarta.inject.Qualifier; +import jakarta.inject.Singleton; + +import org.jboss.jandex.ClassType; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.BeanCreator; +import io.quarkus.arc.InjectableBean; +import io.quarkus.arc.SyntheticCreationalContext; +import io.quarkus.arc.processor.BeanRegistrar; +import io.quarkus.arc.test.ArcTestContainer; + +public class BeanInjectionPointsTest { + @RegisterExtension + private ArcTestContainer container = new ArcTestContainer.Builder() + .beanClasses(MyBean.class, MyDependency.class, MyQualifier1.class, MyQualifier2.class) + .strictCompatibility(true) // we don't support `Bean.getInjectionPoints()` by default + .beanRegistrars(new BeanRegistrar() { + @Override + public void register(RegistrationContext context) { + context.configure(MySyntheticBean.class) + .types(MySyntheticBean.class) + .addInjectionPoint(ClassType.create(MyDependency.class)) + .creator(MySyntheticBeanCreator.class) + .done(); + } + }) + .build(); + + @Test + public void classBean() { + InjectableBean bean = Arc.container().instance(MyBean.class).getBean(); + Set injectionPoints = bean.getInjectionPoints(); + assertEquals(3, injectionPoints.size()); + for (InjectionPoint injectionPoint : injectionPoints) { + Member member = injectionPoint.getMember(); + Annotated annotated = injectionPoint.getAnnotated(); + if (member instanceof Field) { + assertEquals(MyDependency.class, injectionPoint.getType()); + assertEquals(Set.of(Default.Literal.INSTANCE), injectionPoint.getQualifiers()); + assertEquals(bean, injectionPoint.getBean()); + assertEquals("fieldInjection", member.getName()); + assertInstanceOf(AnnotatedField.class, annotated); + verifyExtraAnnotation(annotated, "a"); + } else if (member instanceof Constructor) { + assertEquals("io.quarkus.arc.test.bean.injectionpoints.BeanInjectionPointsTest$MyBean", member.getName()); + assertEquals(MyDependency.class, injectionPoint.getType()); + assertEquals(Set.of(MyQualifier1.Literal.INSTANCE), injectionPoint.getQualifiers()); + assertEquals(bean, injectionPoint.getBean()); + assertInstanceOf(AnnotatedParameter.class, annotated); + assertEquals(0, ((AnnotatedParameter) annotated).getPosition()); + verifyExtraAnnotation(annotated, "b"); + } else if (member instanceof Method) { + assertEquals("init", member.getName()); + assertEquals(MyDependency.class, injectionPoint.getType()); + assertEquals(Set.of(MyQualifier1.Literal.INSTANCE, MyQualifier2.Literal.INSTANCE), + injectionPoint.getQualifiers()); + assertEquals(bean, injectionPoint.getBean()); + assertInstanceOf(AnnotatedParameter.class, annotated); + assertEquals(0, ((AnnotatedParameter) annotated).getPosition()); + verifyExtraAnnotation(annotated, "c"); + } else { + fail("Unknown injection point: " + injectionPoint); + } + } + } + + @Test + public void producerMethodBean() { + assertTrue(Arc.container().instance(MyDependency.class).getBean().getInjectionPoints().isEmpty()); + + InjectableBean bean = Arc.container().instance(String.class).getBean(); + Set injectionPoints = bean.getInjectionPoints(); + assertEquals(1, injectionPoints.size()); + InjectionPoint injectionPoint = injectionPoints.iterator().next(); + assertEquals(MyDependency.class, injectionPoint.getType()); + assertEquals(Set.of(MyQualifier2.Literal.INSTANCE), injectionPoint.getQualifiers()); + assertEquals(bean, injectionPoint.getBean()); + assertEquals("produce", injectionPoint.getMember().getName()); + assertInstanceOf(AnnotatedParameter.class, injectionPoint.getAnnotated()); + assertEquals(0, ((AnnotatedParameter) injectionPoint.getAnnotated()).getPosition()); + verifyExtraAnnotation(injectionPoint.getAnnotated(), "f"); + } + + @Test + public void syntheticBean() { + InjectableBean bean = Arc.container().instance(MySyntheticBean.class).getBean(); + Set injectionPoints = bean.getInjectionPoints(); + assertEquals(1, injectionPoints.size()); + InjectionPoint injectionPoint = injectionPoints.iterator().next(); + assertEquals(MyDependency.class, injectionPoint.getType()); + assertEquals(Set.of(Default.Literal.INSTANCE), injectionPoint.getQualifiers()); + assertEquals(bean, injectionPoint.getBean()); + assertNull(injectionPoint.getMember()); + assertNull(injectionPoint.getAnnotated()); + } + + private void verifyExtraAnnotation(Annotated annotated, String expectedValue) { + assertTrue(annotated.isAnnotationPresent(ExtraAnnotation.class)); + + assertNotNull(annotated.getAnnotation(ExtraAnnotation.class)); + assertEquals(expectedValue, annotated.getAnnotation(ExtraAnnotation.class).value()); + + assertNotNull(annotated.getAnnotations(ExtraAnnotation.class)); + assertEquals(1, annotated.getAnnotations(ExtraAnnotation.class).size()); + assertEquals(expectedValue, annotated.getAnnotations(ExtraAnnotation.class).iterator().next().value()); + + Set all = annotated.getAnnotations(); + assertNotNull(all); + assertTrue(all.size() >= 1); + boolean found = false; + for (Annotation annotation : all) { + if (annotation instanceof ExtraAnnotation) { + found = true; + assertEquals(expectedValue, ((ExtraAnnotation) annotation).value()); + } + } + assertTrue(found); + } + + @Singleton + static class MyBean { + @Inject + @ExtraAnnotation("a") + MyDependency fieldInjection; + + @Inject + MyBean(@MyQualifier1 @ExtraAnnotation("b") MyDependency constructorInjection) { + } + + @Inject + void init(@MyQualifier1 @MyQualifier2 @ExtraAnnotation("c") MyDependency initializerMethodInjection) { + } + + void observe(@Observes String ignored, @ExtraAnnotation("d") MyDependency observerMethodInjection) { + } + + void observeAsync(@ObservesAsync String ignored, @ExtraAnnotation("e") MyDependency asyncObserverMethodInjection) { + } + + @Produces + String produce(@MyQualifier2 @ExtraAnnotation("f") MyDependency producerMethodInjection) { + return ""; + } + + void dispose(@Disposes String ignored, @ExtraAnnotation("g") MyDependency disposerMethodInjection) { + } + } + + @Dependent + @Default + @MyQualifier1 + @MyQualifier2 + static class MyDependency { + } + + static class MySyntheticBean { + } + + static class MySyntheticBeanCreator implements BeanCreator { + @Override + public MySyntheticBean create(SyntheticCreationalContext context) { + return new MySyntheticBean(); + } + } + + @Qualifier + @Retention(RetentionPolicy.RUNTIME) + @interface MyQualifier1 { + final class Literal extends AnnotationLiteral implements MyQualifier1 { + static final Literal INSTANCE = new Literal(); + } + } + + @Qualifier + @Retention(RetentionPolicy.RUNTIME) + @interface MyQualifier2 { + final class Literal extends AnnotationLiteral implements MyQualifier2 { + static final Literal INSTANCE = new Literal(); + } + } + + @Retention(RetentionPolicy.RUNTIME) + @interface ExtraAnnotation { + String value(); + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/scope/CustomScopeWithoutContextTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/scope/CustomScopeWithoutContextTest.java new file mode 100644 index 0000000000000..ba3b43a866701 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/scope/CustomScopeWithoutContextTest.java @@ -0,0 +1,58 @@ +package io.quarkus.arc.test.bean.scope; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.enterprise.context.NormalScope; +import jakarta.enterprise.inject.spi.Bean; +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.inject.Scope; + +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 CustomScopeWithoutContextTest { + @RegisterExtension + ArcTestContainer container = new ArcTestContainer(MyScope.class, MyNormalScope.class, MyBean.class, MyNormalBean.class); + + @Test + public void pseudoScope() { + BeanManager bm = Arc.container().beanManager(); + Bean bean = (Bean) bm.resolve(bm.getBeans(MyBean.class)); + assertEquals(MyScope.class, bean.getScope()); + } + + @Test + public void normalScope() { + BeanManager bm = Arc.container().beanManager(); + Bean bean = (Bean) bm.resolve(bm.getBeans(MyNormalBean.class)); + assertEquals(MyNormalScope.class, bean.getScope()); + } + + @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) + @Retention(RetentionPolicy.RUNTIME) + @Scope + @interface MyScope { + } + + @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) + @Retention(RetentionPolicy.RUNTIME) + @NormalScope + @interface MyNormalScope { + } + + @MyScope + static class MyBean { + } + + @MyNormalScope + static class MyNormalBean { + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/scope/ScopeInheritanceTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/scope/ScopeInheritanceTest.java new file mode 100644 index 0000000000000..94c05b8f04658 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/scope/ScopeInheritanceTest.java @@ -0,0 +1,76 @@ +package io.quarkus.arc.test.bean.scope; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.context.NormalScope; +import jakarta.enterprise.context.RequestScoped; +import jakarta.enterprise.inject.Stereotype; +import jakarta.enterprise.inject.spi.Bean; +import jakarta.enterprise.inject.spi.BeanManager; + +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 ScopeInheritanceTest { + @RegisterExtension + ArcTestContainer container = new ArcTestContainer(MyScope.class, MyStereotype.class, MyBean1.class, MyBean2.class); + + @Test + public void nonInheritedScopeOnDirectSuperclass() { + BeanManager bm = Arc.container().beanManager(); + Bean bean = (Bean) bm.resolve(bm.getBeans(MyBean1.class)); + assertEquals(Dependent.class, bean.getScope()); + } + + @Test + public void inheritedScopeOnDirectSuperclass() { + BeanManager bm = Arc.container().beanManager(); + Bean bean = (Bean) bm.resolve(bm.getBeans(MyBean2.class)); + assertEquals(RequestScoped.class, bean.getScope()); + } + + @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) + @Retention(RetentionPolicy.RUNTIME) + @NormalScope + @interface MyScope { + } + + // just a bean defining annotation + @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) + @Retention(RetentionPolicy.RUNTIME) + @Stereotype + @interface MyStereotype { + } + + @MyStereotype + static class MyBean1 extends DirectSuperclassWithNonInheritedScope { + } + + @MyStereotype + static class MyBean2 extends DirectSuperclassWithInheritedScope { + } + + // `MyScope` is not `@Inherited`, so `MyBean1` will not inherit it, but it will + // also prevent inheriting `@ApplicationScoped` from `IndirectSuperclass` + @MyScope + static class DirectSuperclassWithNonInheritedScope extends IndirectSuperclass { + } + + @RequestScoped + static class DirectSuperclassWithInheritedScope extends IndirectSuperclass { + } + + @ApplicationScoped + static class IndirectSuperclass { + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/cdi/bcextensions/SyntheticBeanInjectionPointTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/cdi/bcextensions/SyntheticBeanInjectionPointTest.java index c164b66c0ddbb..b9b7057d50329 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/cdi/bcextensions/SyntheticBeanInjectionPointTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/cdi/bcextensions/SyntheticBeanInjectionPointTest.java @@ -1,5 +1,6 @@ package io.quarkus.arc.test.cdi.bcextensions; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.fail; @@ -13,6 +14,7 @@ import jakarta.enterprise.inject.build.compatible.spi.SyntheticBeanDisposer; import jakarta.enterprise.inject.build.compatible.spi.SyntheticComponents; import jakarta.enterprise.inject.spi.InjectionPoint; +import jakarta.inject.Inject; import jakarta.inject.Singleton; import org.junit.jupiter.api.Test; @@ -25,11 +27,12 @@ public class SyntheticBeanInjectionPointTest { @RegisterExtension public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(MyBean.class) .buildCompatibleExtensions(new MyExtension()) .build(); @Test - public void test() { + public void testLookupDependentSynthBean() { InstanceHandle handle = Arc.container().select(MyDependentBean.class).getHandle(); try { handle.get(); @@ -37,13 +40,37 @@ public void test() { fail(); } assertNotNull(MyDependentBeanCreator.lookedUp); + assertNull(MyDependentBeanCreator.lookedUp.getMember()); try { handle.destroy(); } catch (Exception ignored) { } assertNull(MyDependentBeanDisposer.lookedUp); + } + + @Test + public void testInjectDependentSynthBean() { + InstanceHandle handle = Arc.container().select(MyBean.class).getHandle(); + try { + handle.get(); + } catch (Exception e) { + fail(); + } + assertNotNull(MyDependentBeanCreator.lookedUp); + assertNotNull(MyDependentBeanCreator.lookedUp.getMember()); + assertEquals(MyBean.class, MyDependentBeanCreator.lookedUp.getMember().getDeclaringClass()); + assertEquals("dependency", MyDependentBeanCreator.lookedUp.getMember().getName()); + try { + handle.destroy(); + } catch (Exception ignored) { + } + assertNull(MyDependentBeanDisposer.lookedUp); + } + + @Test + public void testLookupSingletonSynthBean() { try { Arc.container().select(MySingletonBean.class).get(); } catch (Exception ignored) { @@ -51,6 +78,12 @@ public void test() { assertNull(MySingletonBeanCreator.lookedUp); } + @Dependent + public static class MyBean { + @Inject + MyDependentBean dependency; + } + public static class MyExtension implements BuildCompatibleExtension { @Synthesis public void synthesise(SyntheticComponents syn) { diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/circular/CircularProducerNormalScopeConstructorInjectionTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/circular/CircularProducerNormalScopeConstructorInjectionTest.java new file mode 100644 index 0000000000000..a08489642c2b2 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/circular/CircularProducerNormalScopeConstructorInjectionTest.java @@ -0,0 +1,82 @@ +package io.quarkus.arc.test.circular; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Inject; +import jakarta.inject.Qualifier; + +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 CircularProducerNormalScopeConstructorInjectionTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class, MyQualifier.class); + + @Test + public void test() { + MyBean bean = Arc.container().instance(MyBean.class).get(); + assertEquals("foobarquux", bean.get()); + } + + static class MyValue { + private final String value; + + // for client proxy + MyValue() { + this.value = null; + } + + MyValue(String value) { + this.value = value; + } + + String get() { + return value; + } + } + + @Dependent + static class MyBean { + @Produces + @ApplicationScoped + MyValue producerMethod() { + return new MyValue("foobar"); + } + + @Produces + @ApplicationScoped + @MyQualifier + MyValue producerField = new MyValue("quux"); + + MyValue foobar; + + MyValue quux; + + @Inject + MyBean(MyValue foobar, @MyQualifier MyValue quux) { + this.foobar = foobar; + this.quux = quux; + } + + String get() { + return foobar.get() + quux.get(); + } + } + + @Qualifier + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER }) + @interface MyQualifier { + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/circular/CircularProducerNormalScopeFieldInjectionTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/circular/CircularProducerNormalScopeFieldInjectionTest.java new file mode 100644 index 0000000000000..c3f51095b14b1 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/circular/CircularProducerNormalScopeFieldInjectionTest.java @@ -0,0 +1,79 @@ +package io.quarkus.arc.test.circular; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Inject; +import jakarta.inject.Qualifier; + +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 CircularProducerNormalScopeFieldInjectionTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class, MyQualifier.class); + + @Test + public void test() { + MyBean bean = Arc.container().instance(MyBean.class).get(); + assertEquals("foobarquux", bean.get()); + } + + static class MyValue { + private final String value; + + // for client proxy + MyValue() { + this.value = null; + } + + MyValue(String value) { + this.value = value; + } + + String get() { + return value; + } + } + + @Dependent + static class MyBean { + @Produces + @ApplicationScoped + MyValue producerMethod() { + return new MyValue("foobar"); + } + + @Produces + @ApplicationScoped + @MyQualifier + MyValue producerField = new MyValue("quux"); + + @Inject + MyValue foobar; + + @Inject + @MyQualifier + MyValue quux; + + String get() { + return foobar.get() + quux.get(); + } + } + + @Qualifier + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER }) + @interface MyQualifier { + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/circular/CircularProducerNormalScopeSetterInjectionTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/circular/CircularProducerNormalScopeSetterInjectionTest.java new file mode 100644 index 0000000000000..3def33c3742ac --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/circular/CircularProducerNormalScopeSetterInjectionTest.java @@ -0,0 +1,82 @@ +package io.quarkus.arc.test.circular; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Inject; +import jakarta.inject.Qualifier; + +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 CircularProducerNormalScopeSetterInjectionTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class, MyQualifier.class); + + @Test + public void test() { + MyBean bean = Arc.container().instance(MyBean.class).get(); + assertEquals("foobarquux", bean.get()); + } + + static class MyValue { + private final String value; + + // for client proxy + MyValue() { + this.value = null; + } + + MyValue(String value) { + this.value = value; + } + + String get() { + return value; + } + } + + @Dependent + static class MyBean { + @Produces + @ApplicationScoped + MyValue producerMethod() { + return new MyValue("foobar"); + } + + @Produces + @ApplicationScoped + @MyQualifier + MyValue producerField = new MyValue("quux"); + + MyValue foobar; + + MyValue quux; + + @Inject + void set(MyValue foobar, @MyQualifier MyValue quux) { + this.foobar = foobar; + this.quux = quux; + } + + String get() { + return foobar.get() + quux.get(); + } + } + + @Qualifier + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER }) + @interface MyQualifier { + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/circular/CircularProducerTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/circular/CircularProducerTest.java new file mode 100644 index 0000000000000..8d3f6378498de --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/circular/CircularProducerTest.java @@ -0,0 +1,63 @@ +package io.quarkus.arc.test.circular; + +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.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Inject; +import jakarta.inject.Qualifier; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class CircularProducerTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer.Builder() + .beanClasses(MyBean.class, MyQualifier.class) + .shouldFail() + .build(); + + @Test + public void trigger() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertInstanceOf(IllegalStateException.class, error); + assertTrue(error.getMessage().contains("Circular dependencies not supported")); + } + + @Dependent + static class MyBean { + @Produces + @Dependent + String producerMethod() { + return "foobar"; + } + + @Produces + @Dependent + @MyQualifier + String producerField = "quux"; + + final String value; + + @Inject + MyBean(String foobar, @MyQualifier String quux) { + this.value = foobar + quux; + } + } + + @Qualifier + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER }) + @interface MyQualifier { + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/circular/CircularStaticProducerTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/circular/CircularStaticProducerTest.java new file mode 100644 index 0000000000000..93f51d112d058 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/circular/CircularStaticProducerTest.java @@ -0,0 +1,57 @@ +package io.quarkus.arc.test.circular; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Inject; +import jakarta.inject.Qualifier; + +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 CircularStaticProducerTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class, MyQualifier.class); + + @Test + public void test() { + MyBean bean = Arc.container().instance(MyBean.class).get(); + assertEquals("foobarquux", bean.value); + } + + @Dependent + static class MyBean { + @Produces + @Dependent + static String producerMethod() { + return "foobar"; + } + + @Produces + @Dependent + @MyQualifier + static String producerField = "quux"; + + final String value; + + @Inject + MyBean(String foobar, @MyQualifier String quux) { + this.value = foobar + quux; + } + } + + @Qualifier + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER }) + @interface MyQualifier { + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injectionpoint/BeanWithInjectionPointMetadata.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injectionpoint/BeanWithInjectionPointMetadata.java new file mode 100644 index 0000000000000..8b15d9e6848ea --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injectionpoint/BeanWithInjectionPointMetadata.java @@ -0,0 +1,48 @@ +package io.quarkus.arc.test.injectionpoint; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.function.Consumer; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.spi.InjectionPoint; +import jakarta.inject.Inject; + +@Dependent +public class BeanWithInjectionPointMetadata { + @Inject + InjectionPoint field; + + InjectionPoint constructor; + + InjectionPoint initializer; + + @Inject + public BeanWithInjectionPointMetadata(InjectionPoint ip) { + this.constructor = ip; + } + + @Inject + void init(InjectionPoint ip) { + this.initializer = ip; + } + + public void assertPresent(Consumer asserter) { + assertNotNull(field); + assertNotNull(constructor); + assertNotNull(initializer); + + if (asserter != null) { + asserter.accept(field); + asserter.accept(constructor); + asserter.accept(initializer); + } + } + + public void assertAbsent() { + assertNull(field); + assertNull(constructor); + assertNull(initializer); + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injectionpoint/DummyInjectionPoint.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injectionpoint/DummyInjectionPoint.java new file mode 100644 index 0000000000000..93238a4ff0436 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injectionpoint/DummyInjectionPoint.java @@ -0,0 +1,55 @@ +package io.quarkus.arc.test.injectionpoint; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Member; +import java.lang.reflect.Type; +import java.util.Set; + +import jakarta.enterprise.inject.spi.Annotated; +import jakarta.enterprise.inject.spi.Bean; +import jakarta.enterprise.inject.spi.InjectionPoint; + +class DummyInjectionPoint implements InjectionPoint { + private final Type type; + private final Set qualifiers; + + DummyInjectionPoint(Type type, Annotation... qualifiers) { + this.type = type; + this.qualifiers = Set.of(qualifiers); + } + + @Override + public Type getType() { + return type; + } + + @Override + public Set getQualifiers() { + return qualifiers; + } + + @Override + public Bean getBean() { + throw new UnsupportedOperationException(); + } + + @Override + public Member getMember() { + throw new UnsupportedOperationException(); + } + + @Override + public Annotated getAnnotated() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isDelegate() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isTransient() { + throw new UnsupportedOperationException(); + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injectionpoint/InjectionPointMetadataWithDynamicLookupTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injectionpoint/InjectionPointMetadataWithDynamicLookupTest.java new file mode 100644 index 0000000000000..83f83529c7976 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injectionpoint/InjectionPointMetadataWithDynamicLookupTest.java @@ -0,0 +1,183 @@ +package io.quarkus.arc.test.injectionpoint; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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 java.util.Set; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.context.spi.CreationalContext; +import jakarta.enterprise.inject.Default; +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.spi.Bean; +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.enterprise.inject.spi.CDI; +import jakarta.enterprise.inject.spi.InjectionPoint; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; + +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 InjectionPointMetadataWithDynamicLookupTest { + @RegisterExtension + private ArcTestContainer container = new ArcTestContainer(BeanWithInjectionPointMetadata.class, + MyDependentBean.class, MySingletonBean.class); + + @Test + public void arcContainerInstance() { + // the "current" injection point of `Arc.container().instance(...)` doesn't seem to be well defined + BeanWithInjectionPointMetadata bean = Arc.container().instance(BeanWithInjectionPointMetadata.class).get(); + + // this is probably an implementation artifact, not an intentional choice + bean.assertPresent(ip -> { + assertEquals(Object.class, ip.getType()); + assertEquals(Set.of(), ip.getQualifiers()); + assertNull(ip.getMember()); + assertNull(ip.getBean()); + }); + } + + @Test + public void arcContainerSelect() { + BeanWithInjectionPointMetadata bean = Arc.container().select(BeanWithInjectionPointMetadata.class).get(); + + bean.assertPresent(ip -> { + // the `Instance` through which the `bean` was looked up + assertEquals(BeanWithInjectionPointMetadata.class, ip.getType()); + assertEquals(Set.of(), ip.getQualifiers()); + assertNull(ip.getMember()); + assertNull(ip.getBean()); + }); + } + + @Test + public void cdiCurrentSelect() { + BeanWithInjectionPointMetadata bean = CDI.current().select(BeanWithInjectionPointMetadata.class).get(); + + bean.assertPresent(ip -> { + // the `Instance` through which the `bean` was looked up + assertEquals(BeanWithInjectionPointMetadata.class, ip.getType()); + assertEquals(Set.of(), ip.getQualifiers()); + assertNull(ip.getMember()); + assertNull(ip.getBean()); + }); + } + + @Test + public void beanManagerCreateInstanceAndSelect() { + BeanManager bm = Arc.container().beanManager(); + BeanWithInjectionPointMetadata bean = bm.createInstance().select(BeanWithInjectionPointMetadata.class).get(); + + bean.assertPresent(ip -> { + // the `Instance` through which the `bean` was looked up + assertEquals(BeanWithInjectionPointMetadata.class, ip.getType()); + assertEquals(Set.of(), ip.getQualifiers()); + assertNull(ip.getMember()); + assertNull(ip.getBean()); + }); + } + + @Test + public void beanManagerGetReference() { + BeanManager bm = Arc.container().beanManager(); + Bean bean = (Bean) bm.resolve( + bm.getBeans(BeanWithInjectionPointMetadata.class)); + CreationalContext cc = bm.createCreationalContext(bean); + BeanWithInjectionPointMetadata instance = (BeanWithInjectionPointMetadata) bm.getReference( + bean, BeanWithInjectionPointMetadata.class, cc); + + instance.assertAbsent(); + } + + @Test + public void beanManagerGetInjectableReference() { + InjectionPoint lookup = new DummyInjectionPoint(BeanWithInjectionPointMetadata.class, Default.Literal.INSTANCE); + + BeanManager bm = Arc.container().beanManager(); + CreationalContext cc = bm.createCreationalContext(null); + BeanWithInjectionPointMetadata instance = (BeanWithInjectionPointMetadata) bm.getInjectableReference(lookup, cc); + + instance.assertPresent(ip -> { + assertSame(lookup, ip); + }); + } + + @Test + public void injectionIntoDependentBean() { + MyDependentBean bean = Arc.container().select(MyDependentBean.class).get(); + + // the `Instance` through which the `bean` was looked up + assertEquals(MyDependentBean.class, bean.ip.getType()); + assertEquals(Set.of(), bean.ip.getQualifiers()); + assertNull(bean.ip.getMember()); + assertNull(bean.ip.getBean()); + + assertNotNull(bean.dependency); + bean.dependency.assertPresent(ip -> { + assertEquals(BeanWithInjectionPointMetadata.class, ip.getType()); + assertEquals(Set.of(Default.Literal.INSTANCE), ip.getQualifiers()); + assertEquals(MyDependentBean.class, ip.getMember().getDeclaringClass()); + assertEquals("dependency", ip.getMember().getName()); + assertEquals(MyDependentBean.class, ip.getBean().getBeanClass()); + }); + + assertNotNull(bean.dependencyInstance); + bean.dependencyInstance.get().assertPresent(ip -> { + assertEquals(BeanWithInjectionPointMetadata.class, ip.getType()); + assertEquals(Set.of(Default.Literal.INSTANCE), ip.getQualifiers()); + assertEquals(MyDependentBean.class, ip.getMember().getDeclaringClass()); + assertEquals("dependencyInstance", ip.getMember().getName()); + assertEquals(MyDependentBean.class, ip.getBean().getBeanClass()); + }); + } + + @Test + public void injectionIntoSingletonBean() { + MySingletonBean bean = Arc.container().select(MySingletonBean.class).get(); + + assertNotNull(bean.dependency); + bean.dependency.assertPresent(ip -> { + assertEquals(BeanWithInjectionPointMetadata.class, ip.getType()); + assertEquals(Set.of(Default.Literal.INSTANCE), ip.getQualifiers()); + assertEquals(MySingletonBean.class, ip.getMember().getDeclaringClass()); + assertEquals("dependency", ip.getMember().getName()); + assertEquals(MySingletonBean.class, ip.getBean().getBeanClass()); + }); + assertNotNull(bean.dependencyInstance); + bean.dependencyInstance.get().assertPresent(ip -> { + assertEquals(BeanWithInjectionPointMetadata.class, ip.getType()); + assertEquals(Set.of(Default.Literal.INSTANCE), ip.getQualifiers()); + assertEquals(MySingletonBean.class, ip.getMember().getDeclaringClass()); + assertEquals("dependencyInstance", ip.getMember().getName()); + assertEquals(MySingletonBean.class, ip.getBean().getBeanClass()); + }); + } + + @Dependent + static class MyDependentBean { + @Inject + InjectionPoint ip; + + @Inject + BeanWithInjectionPointMetadata dependency; + + @Inject + Instance dependencyInstance; + } + + @Singleton + static class MySingletonBean { + @Inject + BeanWithInjectionPointMetadata dependency; + + @Inject + Instance dependencyInstance; + } + +}