diff --git a/inject-java/src/test/groovy/io/micronaut/inject/factory/beanfield/FactoryBeanFieldSpec.groovy b/inject-java/src/test/groovy/io/micronaut/inject/factory/beanfield/FactoryBeanFieldSpec.groovy index f447a1a3f65..b7394346e77 100644 --- a/inject-java/src/test/groovy/io/micronaut/inject/factory/beanfield/FactoryBeanFieldSpec.groovy +++ b/inject-java/src/test/groovy/io/micronaut/inject/factory/beanfield/FactoryBeanFieldSpec.groovy @@ -2,8 +2,12 @@ package io.micronaut.inject.factory.beanfield import io.micronaut.annotation.processing.test.AbstractTypeElementSpec import io.micronaut.context.ApplicationContext +import io.micronaut.context.annotation.Prototype +import io.micronaut.core.annotation.AnnotationUtil import io.micronaut.core.reflect.ReflectionUtils +import io.micronaut.inject.BeanDefinition import io.micronaut.inject.qualifiers.Qualifiers +import spock.lang.Issue import spock.lang.Unroll class FactoryBeanFieldSpec extends AbstractTypeElementSpec { @@ -379,4 +383,408 @@ class Test {} where: modifier << ['private', 'protected', 'static'] } + + @Issue("https://github.com/micronaut-projects/micronaut-core/pull/7165") + void "if a factory field bean defines Bean and Prototype scopes and the original bean type scope is Singleton BeanDefinition getScope returns Prototype and BeanDefinition getAnnotationNamesByStereotype for javax.inject.Scope returns Prototype The original qualifier is not present if the factory field bean does not define a Qualifier"() { + + given: + ApplicationContext context = buildContext('test.TestFactory$TestField', '''\ +package test; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import io.micronaut.inject.annotation.*; +import io.micronaut.aop.*; +import io.micronaut.context.annotation.*; +import jakarta.inject.*; +import jakarta.inject.Singleton; + +@Some +@Factory +class TestFactory$TestField { + + @Bean + @Prototype + Bar1 bar = new Bar1(); +} + +@Abc +@Singleton +class Bar1 { +} + +@Retention(RUNTIME) +@Qualifier +@interface Abc { +} + +@Retention(RUNTIME) +@Qualifier +@interface Some { +} + +''') + + when: + BeanDefinition bar1BeanDefinition = context.getBeanDefinitions(context.classLoader.loadClass('test.Bar1')) + .find { it.getDeclaringType().get().simpleName.contains("TestFactory") } + then: + bar1BeanDefinition.getScope().get() == Prototype.class + bar1BeanDefinition.declaredQualifier + bar1BeanDefinition.declaredQualifier.toString() == "@Abc and @Some" + bar1BeanDefinition.getAnnotationNamesByStereotype(AnnotationUtil.SCOPE).size() == 2 + + when: + Collection annotationNamesByScopeStereoType = bar1BeanDefinition.getAnnotationNamesByStereotype(AnnotationUtil.SCOPE).asCollection() + + then: + annotationNamesByScopeStereoType.any {it == "io.micronaut.context.annotation.Prototype" } + annotationNamesByScopeStereoType.any {it == AnnotationUtil.SINGLETON } + + cleanup: + context.close() + } + + @Issue("https://github.com/micronaut-projects/micronaut-core/pull/7165") + void "if a factory field bean defines a @Bean and @Singleton scopes, BeanDefinition::getScope return empty but BeanDefinition::getAnnotationNamesByStereotype for javax.inject.Scope returns javax.inject.Singleton The original qualifier is not present if the factory field bean does not define a Qualifier"() { + given: + ApplicationContext context = buildContext('test.TestFactory$TestField', '''\ +package test; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import io.micronaut.inject.annotation.*; +import io.micronaut.aop.*; +import io.micronaut.context.annotation.*; +import jakarta.inject.*; +import jakarta.inject.Singleton; + +@Some +@Factory +class TestFactory$TestField { + @Bean + @Singleton + Bar2 bar2 = new Bar2(); +} + +@Abc +class Bar2 { +} + +@Retention(RUNTIME) +@Qualifier +@interface Abc { +} + +@Retention(RUNTIME) +@Qualifier +@interface Some { +} +''') + when: + BeanDefinition bar2BeanDefinition = context.getBeanDefinitions(context.classLoader.loadClass('test.Bar2')) + .find { it.getDeclaringType().get().simpleName.contains("TestFactory") } + + then: + !bar2BeanDefinition.getScope().isPresent() // javax.inject.Singleton is not present :-/ + bar2BeanDefinition.singleton + bar2BeanDefinition.declaredQualifier + bar2BeanDefinition.declaredQualifier.toString() == "@Abc and @Some" + bar2BeanDefinition.getAnnotationNamesByStereotype(AnnotationUtil.SCOPE).size() == 1 + bar2BeanDefinition.getAnnotationNamesByStereotype(AnnotationUtil.SCOPE).iterator().next() == AnnotationUtil.SINGLETON + + cleanup: + context.close() + } + + @Issue("https://github.com/micronaut-projects/micronaut-core/pull/7165") + void "if a factory field bean defines a @Bean scope and the original type did not have any scope, BeanDefinition::getScope and BeanDefinition::getAnnotationNamesByStereotype for javax.inject.Scope return empty. if factory field bean defines a qualifier, the original Qualifier is overridden"() { + given: + ApplicationContext context = buildContext('test.TestFactory$TestField', '''\ +package test; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import io.micronaut.inject.annotation.*; +import io.micronaut.aop.*; +import io.micronaut.context.annotation.*; +import jakarta.inject.*; +import jakarta.inject.Singleton; + +@Some +@Factory +class TestFactory$TestField { + + @Bean + @Xyz + Bar3 bar3 = new Bar3(); +} + +@Abc +class Bar3 { +} + +@Retention(RUNTIME) +@Qualifier +@interface Abc { +} + +@Retention(RUNTIME) +@Qualifier +@interface Xyz { +} + +@Retention(RUNTIME) +@Qualifier +@interface Some { +} + +''') + when: + BeanDefinition bar3BeanDefinition = context.getBeanDefinitions(context.classLoader.loadClass('test.Bar3')) + .find { it.getDeclaringType().get().simpleName.contains("TestFactory") } + + then: + !bar3BeanDefinition.getScope().isPresent() + bar3BeanDefinition.declaredQualifier.toString() == "@Named('test.Xyz') and @Abc and @Some" + bar3BeanDefinition.getAnnotationNamesByStereotype(AnnotationUtil.SCOPE).size() == 0 + + cleanup: + context.close() + } + + @Issue("https://github.com/micronaut-projects/micronaut-core/pull/7165") + void "if factory field bean defines a @Bean scope and no qualifier, the original Scope and Qualifier are used"() { + given: + ApplicationContext context = buildContext('test.TestFactory$TestField', '''\ +package test; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import io.micronaut.inject.annotation.*; +import io.micronaut.aop.*; +import io.micronaut.context.annotation.*; +import jakarta.inject.*; +import jakarta.inject.Singleton; + +@Some +@Factory +class TestFactory$TestField { + + @Bean + Bar4 bar4 = new Bar4(); +} + +@Abc +@Singleton +class Bar4 { +} + +@Retention(RUNTIME) +@Qualifier +@interface Abc { +} + +@Retention(RUNTIME) +@Qualifier +@interface Some { +} + +''') + when: + BeanDefinition bar4BeanDefinition = context.getBeanDefinitions(context.classLoader.loadClass('test.Bar4')) + .find { it.getDeclaringType().get().simpleName.contains("TestFactory") } + + then: + !bar4BeanDefinition.getScope().isPresent() + bar4BeanDefinition.singleton + bar4BeanDefinition.declaredQualifier.toString() == "@Abc and @Some" + bar4BeanDefinition.getAnnotationNamesByStereotype(AnnotationUtil.SCOPE).size() == 1 + bar4BeanDefinition.getAnnotationNamesByStereotype(AnnotationUtil.SCOPE).iterator().next() == AnnotationUtil.SINGLETON + + cleanup: + context.close() + } + + @Issue("https://github.com/micronaut-projects/micronaut-core/pull/7165") + void "if factory field bean defines a @Bean scope, BeanDefinition::getScope and BeanDefinition::getAnnotationNamesByStereotype for javax.inject.Scope return empty, even if original bean defines @Singleton If factory field bean defines a qualifier, the original Qualifier is overridden"() { + given: + ApplicationContext context = buildContext('test.TestFactory$TestField', '''\ +package test; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import io.micronaut.inject.annotation.*; +import io.micronaut.aop.*; +import io.micronaut.context.annotation.*; +import jakarta.inject.*; +import jakarta.inject.Singleton; + +@Some +@Factory +class TestFactory$TestField { + @Bean + @Xyz + Bar5 bar5 = new Bar5(); +} + +@Abc +@Singleton +class Bar5 { +} + +@Retention(RUNTIME) +@Qualifier +@interface Abc { +} + +@Retention(RUNTIME) +@Qualifier +@interface Xyz { +} + +@Retention(RUNTIME) +@Qualifier +@interface Some { +} + +''') + when: + BeanDefinition bar5BeanDefinition = context.getBeanDefinitions(context.classLoader.loadClass('test.Bar5')) + .find {it.getDeclaringType().get().simpleName.contains("TestFactory")} + + then: + !bar5BeanDefinition.getScope().isPresent() + bar5BeanDefinition.declaredQualifier.toString() == "@Named('test.Xyz') and @Abc and @Some" + bar5BeanDefinition.getAnnotationNamesByStereotype(AnnotationUtil.SCOPE).size() == 1 + bar5BeanDefinition.getAnnotationNamesByStereotype(AnnotationUtil.SCOPE).iterator().next() == AnnotationUtil.SINGLETON + + cleanup: + context.close() + } + + @Issue("https://github.com/micronaut-projects/micronaut-core/pull/7165") + void "if factory field bean defines a @Bean and @Prototype scopes, BeanDefinition::getScope returns Prototype and BeanDefinition::getAnnotationNamesByStereotype for javax.inject.Scope returns Prototype If factory field bean defines a qualifier, the original Qualifier is overridden"() { + given: + ApplicationContext context = buildContext('test.TestFactory$TestField', '''\ +package test; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import io.micronaut.inject.annotation.*; +import io.micronaut.aop.*; +import io.micronaut.context.annotation.*; +import jakarta.inject.*; +import jakarta.inject.Singleton; + +@Some +@Factory +class TestFactory$TestField { + + @Bean + @Xyz + @Prototype + Bar6 bar6 = new Bar6(); +} + +@Abc +@Singleton +class Bar6 { +} + +@Retention(RUNTIME) +@Qualifier +@interface Abc { +} + +@Retention(RUNTIME) +@Qualifier +@interface Xyz { +} + +@Retention(RUNTIME) +@Qualifier +@interface Some { +} + +''') + when: + BeanDefinition bar6BeanDefinition = context.getBeanDefinitions(context.classLoader.loadClass('test.Bar6')) + .find {it.getDeclaringType().get().simpleName.contains("TestFactory")} + then: + bar6BeanDefinition.getScope().get() == Prototype.class + bar6BeanDefinition.declaredQualifier.toString() == "@Named('test.Xyz') and @Abc and @Some" + + when: + Collection annotationNamesByScopeStereoType = bar6BeanDefinition.getAnnotationNamesByStereotype(AnnotationUtil.SCOPE).asCollection() + + then: + annotationNamesByScopeStereoType.any {it == "io.micronaut.context.annotation.Prototype" } + annotationNamesByScopeStereoType.any {it == AnnotationUtil.SINGLETON } + + cleanup: + context.close() + } + + @Issue("https://github.com/micronaut-projects/micronaut-core/pull/7165") + void "if factory has a qualifier it is inherited by the factory field beans"() { + given: + ApplicationContext context = buildContext('test.TestFactory$TestField', '''\ +package test; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import io.micronaut.inject.annotation.*; +import io.micronaut.aop.*; +import io.micronaut.context.annotation.*; +import jakarta.inject.*; +import jakarta.inject.Singleton; + +@Some +@Factory +class TestFactory$TestField { + @Prototype + Bar7 bar7 = new Bar7(); +} + +class Bar7 { +} + +@Retention(RUNTIME) +@Qualifier +@interface Some { +} + +''') + when: + BeanDefinition bar7BeanDefinition = context.getBeanDefinitions(context.classLoader.loadClass('test.Bar7')) + .find {it.getDeclaringType().get().simpleName.contains("TestFactory")} + then: + bar7BeanDefinition.getScope().get() == Prototype.class + bar7BeanDefinition.declaredQualifier.toString() == "@Some" + + when: + Collection annotationNamesByScopeStereoType = bar7BeanDefinition.getAnnotationNamesByStereotype(AnnotationUtil.SCOPE).asCollection() + + then: + annotationNamesByScopeStereoType.size() == 1 + annotationNamesByScopeStereoType.any {it == "io.micronaut.context.annotation.Prototype" } + + cleanup: + context.close() + } }