diff --git a/core-processor/src/main/java/io/micronaut/inject/processing/AbstractBeanElementCreator.java b/core-processor/src/main/java/io/micronaut/inject/processing/AbstractBeanElementCreator.java index ebbff433052..1d6ad7dcb4a 100644 --- a/core-processor/src/main/java/io/micronaut/inject/processing/AbstractBeanElementCreator.java +++ b/core-processor/src/main/java/io/micronaut/inject/processing/AbstractBeanElementCreator.java @@ -109,20 +109,20 @@ public static AnnotationMetadata getElementAnnotationMetadata(MemberElement memb return memberElement.getAnnotationMetadata(); } - protected boolean visitIntrospectedMethod(BeanDefinitionVisitor visitor, ClassElement typeElement, MethodElement methodElement) { + protected boolean visitIntrospectedMethod(BeanDefinitionVisitor visitor, ClassElement classElement, MethodElement methodElement) { AopProxyWriter aopProxyWriter = (AopProxyWriter) visitor; - final AnnotationMetadata resolvedTypeMetadata = typeElement.getAnnotationMetadata(); + final AnnotationMetadata resolvedTypeMetadata = classElement.getAnnotationMetadata(); final boolean resolvedTypeMetadataIsAopProxyType = InterceptedMethodUtil.hasDeclaredAroundAdvice(resolvedTypeMetadata); if (methodElement.isAbstract() || resolvedTypeMetadataIsAopProxyType || InterceptedMethodUtil.hasDeclaredAroundAdvice(methodElement.getAnnotationMetadata())) { - addToIntroduction(aopProxyWriter, typeElement, methodElement, false); + addToIntroduction(aopProxyWriter, classElement, methodElement, false); return true; - } else if (!methodElement.isAbstract() && methodElement.hasDeclaredStereotype(Executable.class)) { + } else if (methodElement.hasDeclaredStereotype(Executable.class)) { aopProxyWriter.visitExecutableMethod( - typeElement, + classElement, methodElement, visitorContext ); diff --git a/core-processor/src/main/java/io/micronaut/inject/processing/AopIntroductionProxySupportedBeanElementCreator.java b/core-processor/src/main/java/io/micronaut/inject/processing/AopIntroductionProxySupportedBeanElementCreator.java index 7e4b4d985ab..373b7740f57 100644 --- a/core-processor/src/main/java/io/micronaut/inject/processing/AopIntroductionProxySupportedBeanElementCreator.java +++ b/core-processor/src/main/java/io/micronaut/inject/processing/AopIntroductionProxySupportedBeanElementCreator.java @@ -69,7 +69,7 @@ protected AopProxyWriter getAroundAopProxyVisitor(BeanDefinitionVisitor visitor, @Override protected boolean visitPropertyReadElement(BeanDefinitionVisitor visitor, PropertyElement propertyElement, MethodElement readElement) { - if (readElement.isAbstract() && visitIntrospectedMethod(visitor, classElement, readElement)) { + if (intercept(visitor, readElement)) { return true; } return super.visitPropertyReadElement(visitor, propertyElement, readElement); @@ -77,7 +77,7 @@ protected boolean visitPropertyReadElement(BeanDefinitionVisitor visitor, Proper @Override protected boolean visitPropertyWriteElement(BeanDefinitionVisitor visitor, PropertyElement propertyElement, MethodElement writeElement) { - if (writeElement.isAbstract() && visitIntrospectedMethod(visitor, classElement, writeElement)) { + if (intercept(visitor, writeElement)) { return true; } return super.visitPropertyWriteElement(visitor, propertyElement, writeElement); @@ -85,10 +85,14 @@ protected boolean visitPropertyWriteElement(BeanDefinitionVisitor visitor, Prope @Override protected boolean visitMethod(BeanDefinitionVisitor visitor, MethodElement methodElement) { - if (methodElement.isAbstract() && visitIntrospectedMethod(visitor, classElement, methodElement)) { + if (intercept(visitor, methodElement)) { return true; } return super.visitMethod(visitor, methodElement); } + private boolean intercept(BeanDefinitionVisitor visitor, MethodElement methodElement) { + return !methodElement.isFinal() && visitIntrospectedMethod(visitor, classElement, methodElement); + } + } diff --git a/inject-java/src/test/groovy/io/micronaut/aop/introduction/MyAbstractRepoSpec.groovy b/inject-java/src/test/groovy/io/micronaut/aop/introduction/MyAbstractRepoSpec.groovy new file mode 100644 index 00000000000..d351b5796f8 --- /dev/null +++ b/inject-java/src/test/groovy/io/micronaut/aop/introduction/MyAbstractRepoSpec.groovy @@ -0,0 +1,84 @@ +/* + * Copyright 2017-2019 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aop.introduction + +import io.micronaut.annotation.processing.test.AbstractTypeElementSpec + +class MyAbstractRepoSpec extends AbstractTypeElementSpec { + + void "test abstract interceptor method"() { + given: + def context = buildContext(""" +package test; + +import io.micronaut.aop.introduction.Tx; +import io.micronaut.aop.introduction.RepoDef; +import io.micronaut.aop.introduction.DeleteByIdCrudRepo; +import io.micronaut.context.annotation.Executable; + +@Tx +@RepoDef +abstract class MyAbstractRepo4 implements DeleteByIdCrudRepo { + + public String findById(Integer id) { + return "ABC"; + } + +} + +""") + + when: + def beanDef1 = context.getBeanDefinition(context.classLoader.loadClass("test.MyAbstractRepo4")) + def findById = beanDef1.getRequiredMethod("findById", Integer) + then: + findById + + cleanup: + context.close() + } + + void "test default interceptor method"() { + given: + def context = buildContext(""" +package test; + +import io.micronaut.aop.introduction.Tx; +import io.micronaut.aop.introduction.RepoDef; +import io.micronaut.aop.introduction.DeleteByIdCrudRepo; + +@Tx +@RepoDef +interface MyDefaultRepo extends DeleteByIdCrudRepo { + + default String findById(Integer id) { + return "ABC"; + } + +} + +""") + + when: + def beanDef1 = context.getBeanDefinition(context.classLoader.loadClass("test.MyDefaultRepo")) + def findById = beanDef1.getRequiredMethod("findById", Integer) + then: + findById + + cleanup: + context.close() + } +} diff --git a/inject-java/src/test/groovy/io/micronaut/aop/introduction/MyRepo3.java b/inject-java/src/test/groovy/io/micronaut/aop/introduction/MyRepo3.java new file mode 100644 index 00000000000..3ad0abcb043 --- /dev/null +++ b/inject-java/src/test/groovy/io/micronaut/aop/introduction/MyRepo3.java @@ -0,0 +1,27 @@ +/* + * Copyright 2017-2020 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aop.introduction; + +import jakarta.validation.constraints.NotNull; + +@Tx +@RepoDef +public interface MyRepo3 extends DeleteByIdCrudRepo { + + @Override + void deleteById(@NotNull Integer id); + +} diff --git a/inject-java/src/test/groovy/io/micronaut/aop/introduction/MyRepo4.java b/inject-java/src/test/groovy/io/micronaut/aop/introduction/MyRepo4.java new file mode 100644 index 00000000000..6d1cb6e46e6 --- /dev/null +++ b/inject-java/src/test/groovy/io/micronaut/aop/introduction/MyRepo4.java @@ -0,0 +1,26 @@ +/* + * Copyright 2017-2020 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aop.introduction; + +@Tx +@RepoDef +public abstract class MyRepo4 implements DeleteByIdCrudRepo { + + public String findById(Integer id) { + return "ABC"; + } + +} diff --git a/inject-java/src/test/groovy/io/micronaut/aop/introduction/MyRepo5.java b/inject-java/src/test/groovy/io/micronaut/aop/introduction/MyRepo5.java new file mode 100644 index 00000000000..d54ba358ab8 --- /dev/null +++ b/inject-java/src/test/groovy/io/micronaut/aop/introduction/MyRepo5.java @@ -0,0 +1,26 @@ +/* + * Copyright 2017-2020 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aop.introduction; + +@Tx +@RepoDef +public interface MyRepo5 extends DeleteByIdCrudRepo { + + default String findById(Integer id) { + return "ABC"; + } + +} diff --git a/inject-java/src/test/groovy/io/micronaut/aop/introduction/MyRepoIntroductionSpec.groovy b/inject-java/src/test/groovy/io/micronaut/aop/introduction/MyRepoIntroductionSpec.groovy index c0c6cf1e3b5..5a1689d0a16 100644 --- a/inject-java/src/test/groovy/io/micronaut/aop/introduction/MyRepoIntroductionSpec.groovy +++ b/inject-java/src/test/groovy/io/micronaut/aop/introduction/MyRepoIntroductionSpec.groovy @@ -137,4 +137,49 @@ class MyRepoIntroductionSpec extends Specification { MyRepoIntroducer.EXECUTED_METHODS.clear() } + void "test tx interface repo methods"() { + when: + def bean = applicationContext.getBean(MyRepo3) + bean.deleteById(1) + then: + MyRepoIntroducer.EXECUTED_METHODS.size() == 1 + MyRepoIntroducer.EXECUTED_METHODS.clear() + TxInterceptor.EXECUTED_METHODS.size() == 1 + TxInterceptor.EXECUTED_METHODS.clear() + } + + void "test tx abstract repo methods"() { + given: + def bean = applicationContext.getBean(MyRepo4) + when: + bean.deleteById(1) + then: + MyRepoIntroducer.EXECUTED_METHODS.size() == 1 + MyRepoIntroducer.EXECUTED_METHODS.clear() + TxInterceptor.EXECUTED_METHODS.size() == 1 + TxInterceptor.EXECUTED_METHODS.clear() + when: + bean.findById(1) + then: + TxInterceptor.EXECUTED_METHODS.size() == 1 + TxInterceptor.EXECUTED_METHODS.clear() + } + + void "test tx default repo methods"() { + given: + def bean = applicationContext.getBean(MyRepo5) + when: + bean.deleteById(1) + then: + MyRepoIntroducer.EXECUTED_METHODS.size() == 1 + MyRepoIntroducer.EXECUTED_METHODS.clear() + TxInterceptor.EXECUTED_METHODS.size() == 1 + TxInterceptor.EXECUTED_METHODS.clear() + when: + bean.findById(1) + then: + TxInterceptor.EXECUTED_METHODS.size() == 1 + TxInterceptor.EXECUTED_METHODS.clear() + } + } diff --git a/inject-java/src/test/groovy/io/micronaut/aop/introduction/Tx.java b/inject-java/src/test/groovy/io/micronaut/aop/introduction/Tx.java new file mode 100644 index 00000000000..c827d5e36ea --- /dev/null +++ b/inject-java/src/test/groovy/io/micronaut/aop/introduction/Tx.java @@ -0,0 +1,38 @@ +/* + * Copyright 2017-2020 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aop.introduction; + +// tag::imports[] + +import io.micronaut.aop.Around; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +// end::imports[] + +// tag::annotation[] +@Documented +@Retention(RUNTIME) // <1> +@Target({TYPE, METHOD}) // <2> +@Around // <3> +public @interface Tx { +} +// end::annotation[] diff --git a/inject-java/src/test/groovy/io/micronaut/aop/introduction/TxInterceptor.java b/inject-java/src/test/groovy/io/micronaut/aop/introduction/TxInterceptor.java new file mode 100644 index 00000000000..1ffb1285884 --- /dev/null +++ b/inject-java/src/test/groovy/io/micronaut/aop/introduction/TxInterceptor.java @@ -0,0 +1,63 @@ +/* + * Copyright 2017-2020 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aop.introduction; + + +import io.micronaut.aop.InterceptedMethod; +import io.micronaut.aop.InterceptorBean; +import io.micronaut.aop.MethodInterceptor; +import io.micronaut.aop.MethodInvocationContext; +import io.micronaut.core.annotation.Nullable; +import io.micronaut.core.convert.ConversionService; +import jakarta.inject.Singleton; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +@Singleton +@InterceptorBean(Tx.class) +public class TxInterceptor implements MethodInterceptor { + + public static final List EXECUTED_METHODS = new ArrayList<>(); + + private final ConversionService conversionService; + + public TxInterceptor(ConversionService conversionService) { + this.conversionService = conversionService; + } + + @Override + public int getOrder() { + return 0; + } + + @Nullable + @Override + public Object intercept(MethodInvocationContext context) { + EXECUTED_METHODS.add(context.getExecutableMethod().getTargetMethod()); + InterceptedMethod interceptedMethod = InterceptedMethod.of(context, conversionService); + try { + return interceptedMethod.handleResult( + interceptedMethod.interceptResult() + ); + } catch (Exception e) { + return interceptedMethod.handleException(e); + } + } + + +} diff --git a/inject-kotlin/src/test/kotlin/io/micronaut/kotlin/processing/aop/introduction/AbstractClass.kt b/inject-kotlin/src/test/kotlin/io/micronaut/kotlin/processing/aop/introduction/AbstractClass.kt index 733e19c634d..27c227efb7b 100644 --- a/inject-kotlin/src/test/kotlin/io/micronaut/kotlin/processing/aop/introduction/AbstractClass.kt +++ b/inject-kotlin/src/test/kotlin/io/micronaut/kotlin/processing/aop/introduction/AbstractClass.kt @@ -10,7 +10,7 @@ abstract class AbstractClass : AbstractSuperClass() { abstract fun test(name: String): String - fun nonAbstract(name: String): String { + open fun nonAbstract(name: String): String { return test(name) } }