diff --git a/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/common/FunctionalChannelBeanBuilder.java b/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/common/FunctionalChannelBeanBuilder.java index 820e62642..0aa9001bf 100644 --- a/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/common/FunctionalChannelBeanBuilder.java +++ b/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/common/FunctionalChannelBeanBuilder.java @@ -2,6 +2,9 @@ package io.github.springwolf.plugins.cloudstream.asyncapi.scanners.common; import io.github.springwolf.core.asyncapi.scanners.common.payload.internal.TypeExtractor; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.core.ResolvableType; @@ -12,6 +15,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.BiFunction; @@ -24,139 +28,163 @@ public class FunctionalChannelBeanBuilder { private final TypeExtractor typeExtractor; public Set build(AnnotatedElement element) { - Class type = getRawType(element); + final AnnotatedClassOrMethod annotatedClassOrMethod = getAnnotatedClassOrMethodOrThrow(element, typeExtractor); + + Class type = annotatedClassOrMethod.getRawType(); if (Consumer.class.isAssignableFrom(type) || BiConsumer.class.isAssignableFrom(type)) { - List typeGenerics = getTypeGenerics(element); + List typeGenerics = annotatedClassOrMethod.getTypeGenerics(); if (typeGenerics.isEmpty()) { return Collections.emptySet(); } Type payloadType = typeGenerics.get(0); - return Set.of(ofConsumer(element, payloadType)); + return Set.of(ofConsumer(annotatedClassOrMethod, payloadType)); } if (Supplier.class.isAssignableFrom(type)) { - List typeGenerics = getTypeGenerics(element); + List typeGenerics = annotatedClassOrMethod.getTypeGenerics(); if (typeGenerics.isEmpty()) { return Collections.emptySet(); } Type payloadType = typeGenerics.get(0); - return Set.of(ofSupplier(element, payloadType)); + return Set.of(ofSupplier(annotatedClassOrMethod, payloadType)); } if (Function.class.isAssignableFrom(type)) { - List typeGenerics = getTypeGenerics(element); + List typeGenerics = annotatedClassOrMethod.getTypeGenerics(); if (typeGenerics.size() != 2) { return Collections.emptySet(); } Type inputType = typeGenerics.get(0); Type outputType = typeGenerics.get(1); - return Set.of(ofConsumer(element, inputType), ofSupplier(element, outputType)); + return Set.of( + ofConsumer(annotatedClassOrMethod, inputType), ofSupplier(annotatedClassOrMethod, outputType)); } if (BiFunction.class.isAssignableFrom(type)) { - List typeGenerics = getTypeGenerics(element); + List typeGenerics = annotatedClassOrMethod.getTypeGenerics(); if (typeGenerics.size() != 3) { return Collections.emptySet(); } Type inputType = typeGenerics.get(0); Type outputType = typeGenerics.get(2); - return Set.of(ofConsumer(element, inputType), ofSupplier(element, outputType)); + return Set.of( + ofConsumer(annotatedClassOrMethod, inputType), ofSupplier(annotatedClassOrMethod, outputType)); } return Collections.emptySet(); } - private static Class getRawType(AnnotatedElement element) { - if (element instanceof Method m) { - return m.getReturnType(); + private static AnnotatedClassOrMethod getAnnotatedClassOrMethodOrThrow( + AnnotatedElement annotatedElement, TypeExtractor typeExtractor) { + if (annotatedElement instanceof Method m) { + return AnnotatedClassOrMethod.forMethod(annotatedElement, m, typeExtractor); } - if (element instanceof Class c) { - return c; + if (annotatedElement instanceof Class c) { + return AnnotatedClassOrMethod.forClass(annotatedElement, c, typeExtractor); } throw new IllegalArgumentException("Must be a Method or Class"); } - private static FunctionalChannelBeanData ofConsumer(AnnotatedElement element, Type payloadType) { - String name = getElementName(element); + private static FunctionalChannelBeanData ofConsumer( + AnnotatedClassOrMethod annotatedClassOrMethod, Type payloadType) { + String name = annotatedClassOrMethod.getName(); String cloudStreamBinding = firstCharToLowerCase(name) + "-in-0"; return new FunctionalChannelBeanData( - name, element, payloadType, FunctionalChannelBeanData.BeanType.CONSUMER, cloudStreamBinding); + name, + annotatedClassOrMethod.annotatedElement, + payloadType, + FunctionalChannelBeanData.BeanType.CONSUMER, + cloudStreamBinding); } - private static FunctionalChannelBeanData ofSupplier(AnnotatedElement element, Type payloadType) { - String name = getElementName(element); + private static FunctionalChannelBeanData ofSupplier( + AnnotatedClassOrMethod annotatedClassOrMethod, Type payloadType) { + String name = annotatedClassOrMethod.getName(); String cloudStreamBinding = firstCharToLowerCase(name) + "-out-0"; return new FunctionalChannelBeanData( - name, element, payloadType, FunctionalChannelBeanData.BeanType.SUPPLIER, cloudStreamBinding); + name, + annotatedClassOrMethod.annotatedElement, + payloadType, + FunctionalChannelBeanData.BeanType.SUPPLIER, + cloudStreamBinding); } private static String firstCharToLowerCase(String name) { return name.substring(0, 1).toLowerCase() + name.substring(1); } - private static String getElementName(AnnotatedElement element) { - if (element instanceof Method m) { - return m.getName(); + @Getter + @AllArgsConstructor(access = AccessLevel.PRIVATE) + private static class AnnotatedClassOrMethod { + + private final AnnotatedElement annotatedElement; + private final String name; + private final Class rawType; + private final List typeGenerics; + + public static AnnotatedClassOrMethod forClass( + AnnotatedElement element, Class clazz, TypeExtractor typeExtractor) { + return new AnnotatedClassOrMethod( + element, clazz.getSimpleName(), clazz, getTypeGenericsForClass(clazz, typeExtractor)); } - if (element instanceof Class c) { - return c.getSimpleName(); + public static AnnotatedClassOrMethod forMethod( + AnnotatedElement element, Method method, TypeExtractor typeExtractor) { + return new AnnotatedClassOrMethod( + element, method.getName(), method.getReturnType(), getTypeGenericsForMethod(method, typeExtractor)); } - throw new IllegalArgumentException("Must be a Method or Class"); - } + private static List getTypeGenericsForClass(Class clazz, TypeExtractor typeExtractor) { - private List getTypeGenerics(AnnotatedElement element) { - if (element instanceof Method m) { - Type returnType = getMethodReturnType(m); - if (returnType instanceof ParameterizedType) { - return getTypeGenerics(((ParameterizedType) returnType)); + ResolvableType resolvableType = ResolvableType.forClass(clazz); + Optional type = getParameterizedType(resolvableType); + if (type.isPresent() && type.get() instanceof ParameterizedType) { + return getTypeGenerics((ParameterizedType) type.get(), typeExtractor); } else { return Collections.emptyList(); } } - if (element instanceof Class c) { - ResolvableType resolvableType = ResolvableType.forClass(c); - Type type = getParameterizedType(resolvableType); - if (type instanceof ParameterizedType) { - return getTypeGenerics((ParameterizedType) type); + private static List getTypeGenericsForMethod(Method method, TypeExtractor typeExtractor) { + + Optional returnType = getMethodReturnType(method); + if (returnType.isPresent() && returnType.get() instanceof ParameterizedType) { + return getTypeGenerics(((ParameterizedType) returnType.get()), typeExtractor); } else { return Collections.emptyList(); } } - throw new IllegalArgumentException("Must be a Method or Class"); - } - - private Type getMethodReturnType(Method m) { - ResolvableType resolvableType = ResolvableType.forMethodReturnType(m); - return getParameterizedType(resolvableType); - } + private static List getTypeGenerics(ParameterizedType parameterizedType, TypeExtractor typeExtractor) { + return Arrays.stream(parameterizedType.getActualTypeArguments()) + .map(typeExtractor::extractActualType) + .toList(); + } - private Type getParameterizedType(ResolvableType resolvableType) { - if (Consumer.class.isAssignableFrom(resolvableType.resolve(Object.class))) { - return resolvableType.as(Consumer.class).getType(); - } else if (BiConsumer.class.isAssignableFrom(resolvableType.resolve(Object.class))) { - return resolvableType.as(BiConsumer.class).getType(); - } else if (Supplier.class.isAssignableFrom(resolvableType.resolve(Object.class))) { - return resolvableType.as(Supplier.class).getType(); - } else if (Function.class.isAssignableFrom(resolvableType.resolve(Object.class))) { - return resolvableType.as(Function.class).getType(); - } else if (BiFunction.class.isAssignableFrom(resolvableType.resolve(Object.class))) { - return resolvableType.as(BiFunction.class).getType(); + private static Optional getParameterizedType(ResolvableType resolvableType) { + + if (Consumer.class.isAssignableFrom(resolvableType.resolve(Object.class))) { + return Optional.of(resolvableType.as(Consumer.class).getType()); + } else if (BiConsumer.class.isAssignableFrom(resolvableType.resolve(Object.class))) { + return Optional.of(resolvableType.as(BiConsumer.class).getType()); + } else if (Supplier.class.isAssignableFrom(resolvableType.resolve(Object.class))) { + return Optional.of(resolvableType.as(Supplier.class).getType()); + } else if (Function.class.isAssignableFrom(resolvableType.resolve(Object.class))) { + return Optional.of(resolvableType.as(Function.class).getType()); + } else if (BiFunction.class.isAssignableFrom(resolvableType.resolve(Object.class))) { + return Optional.of(resolvableType.as(BiFunction.class).getType()); + } + return Optional.empty(); } - throw new IllegalArgumentException("Illegal type: " + resolvableType.getRawClass()); - } - private List getTypeGenerics(ParameterizedType parameterizedType) { - return Arrays.stream(parameterizedType.getActualTypeArguments()) - .map(typeExtractor::extractActualType) - .toList(); + private static Optional getMethodReturnType(Method m) { + ResolvableType resolvableType = ResolvableType.forMethodReturnType(m); + return getParameterizedType(resolvableType); + } } } diff --git a/springwolf-plugins/springwolf-cloud-stream-plugin/src/test/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/common/FunctionalChannelBeanBuilderTest.java b/springwolf-plugins/springwolf-cloud-stream-plugin/src/test/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/common/FunctionalChannelBeanBuilderTest.java index dbe76b44b..4c8d1b14d 100644 --- a/springwolf-plugins/springwolf-cloud-stream-plugin/src/test/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/common/FunctionalChannelBeanBuilderTest.java +++ b/springwolf-plugins/springwolf-cloud-stream-plugin/src/test/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/common/FunctionalChannelBeanBuilderTest.java @@ -4,12 +4,12 @@ import io.github.springwolf.core.asyncapi.scanners.common.payload.internal.TypeExtractor; import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties; import org.apache.kafka.streams.kstream.KStream; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.Bean; import org.springframework.messaging.Message; +import java.lang.reflect.AnnotatedType; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Map; @@ -22,6 +22,9 @@ import static io.github.springwolf.plugins.cloudstream.asyncapi.scanners.common.FunctionalChannelBeanData.BeanType.CONSUMER; import static io.github.springwolf.plugins.cloudstream.asyncapi.scanners.common.FunctionalChannelBeanData.BeanType.SUPPLIER; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; class FunctionalChannelBeanBuilderTest { private final SpringwolfConfigProperties properties = new SpringwolfConfigProperties(); @@ -39,7 +42,7 @@ void testNotAFunctionalChannelBean() throws NoSuchMethodException { Set data = functionalChannelBeanBuilder.build(method); - Assertions.assertThat(data).isEmpty(); + assertThat(data).isEmpty(); } @Bean @@ -56,7 +59,7 @@ void testConsumerBean() throws NoSuchMethodException { Set data = functionalChannelBeanBuilder.build(method); - Assertions.assertThat(data) + assertThat(data) .containsExactly(new FunctionalChannelBeanData( "consumerBean", method, String.class, CONSUMER, "consumerBean-in-0")); } @@ -75,7 +78,7 @@ void testBiConsumerBean() throws NoSuchMethodException { Set data = functionalChannelBeanBuilder.build(method); - Assertions.assertThat(data) + assertThat(data) .containsExactly(new FunctionalChannelBeanData( "biConsumerBean", method, String.class, CONSUMER, "biConsumerBean-in-0")); } @@ -94,7 +97,7 @@ void testSupplierBean() throws NoSuchMethodException { Set data = functionalChannelBeanBuilder.build(method); - Assertions.assertThat(data) + assertThat(data) .containsExactly(new FunctionalChannelBeanData( "supplierBean", method, String.class, SUPPLIER, "supplierBean-out-0")); } @@ -113,7 +116,7 @@ void testFunctionBean() throws NoSuchMethodException { Set data = functionalChannelBeanBuilder.build(method); - Assertions.assertThat(data) + assertThat(data) .containsExactlyInAnyOrder( new FunctionalChannelBeanData( "functionBean", method, String.class, CONSUMER, "functionBean-in-0"), @@ -135,7 +138,7 @@ void testBiFunctionBean() throws NoSuchMethodException { Set data = functionalChannelBeanBuilder.build(method); - Assertions.assertThat(data) + assertThat(data) .containsExactlyInAnyOrder( new FunctionalChannelBeanData( "biFunctionBean", method, String.class, CONSUMER, "biFunctionBean-in-0"), @@ -159,7 +162,7 @@ void testConsumerBeanWithGenericPayload() throws NoSuchMethodException { Set data = functionalChannelBeanBuilder.build(method); - Assertions.assertThat(data) + assertThat(data) .containsExactly(new FunctionalChannelBeanData( "consumerBeanWithGenericPayload", method, @@ -184,7 +187,7 @@ void testKafkaStreamsConsumerBean() throws NoSuchMethodException { Set data = functionalChannelBeanBuilder.build(method); - Assertions.assertThat(data) + assertThat(data) .containsExactly(new FunctionalChannelBeanData( "kafkaStreamsConsumerBean", method, String.class, CONSUMER, methodName + "-in-0")); } @@ -207,7 +210,7 @@ void testNotAFunctionalChannelBean() { Set data = functionalChannelBeanBuilder.build(testClass); - Assertions.assertThat(data).isEmpty(); + assertThat(data).isEmpty(); } private static class TestClass {} @@ -221,7 +224,7 @@ void testConsumerClass() { Set data = functionalChannelBeanBuilder.build(testClass); - Assertions.assertThat(data) + assertThat(data) .containsExactly(new FunctionalChannelBeanData( "TestClass", testClass, String.class, CONSUMER, "testClass-in-0")); } @@ -240,7 +243,7 @@ void testSupplierClass() { Set data = functionalChannelBeanBuilder.build(testClass); - Assertions.assertThat(data) + assertThat(data) .containsExactly(new FunctionalChannelBeanData( "TestClass", testClass, String.class, SUPPLIER, "testClass-out-0")); } @@ -262,7 +265,7 @@ void testFunctionClass() { Set data = functionalChannelBeanBuilder.build(testClass); - Assertions.assertThat(data) + assertThat(data) .containsExactlyInAnyOrder( new FunctionalChannelBeanData( "TestClass", testClass, String.class, CONSUMER, "testClass-in-0"), @@ -287,7 +290,7 @@ void testConsumerClassWithGenericPayload() { Set data = functionalChannelBeanBuilder.build(testClass); - Assertions.assertThat(data) + assertThat(data) .containsExactly(new FunctionalChannelBeanData( "TestClass", testClass, String.class, CONSUMER, "testClass-in-0")); } @@ -308,7 +311,7 @@ void testKafkaStreamsConsumerClass() { Set data = functionalChannelBeanBuilder.build(testClass); - Assertions.assertThat(data) + assertThat(data) .containsExactly(new FunctionalChannelBeanData( "TestClass", testClass, String.class, CONSUMER, "testClass-in-0")); } @@ -328,7 +331,7 @@ void testBiConsumerChildBean() throws NoSuchMethodException { Set data = functionalChannelBeanBuilder.build(method); - Assertions.assertThat(data) + assertThat(data) .containsExactly(new FunctionalChannelBeanData( "biConsumerChildBean", method, String.class, CONSUMER, "biConsumerChildBean-in-0")); } @@ -339,7 +342,7 @@ void testBiConsumerRawChildBean() throws NoSuchMethodException { Set data = functionalChannelBeanBuilder.build(method); - Assertions.assertThat(data).isEmpty(); + assertThat(data).isEmpty(); } @Test @@ -348,7 +351,7 @@ void testSupplierRawChildBean() throws NoSuchMethodException { Set data = functionalChannelBeanBuilder.build(method); - Assertions.assertThat(data).isEmpty(); + assertThat(data).isEmpty(); } @Test @@ -357,7 +360,7 @@ void testFunctionRawChildBean() throws NoSuchMethodException { Set data = functionalChannelBeanBuilder.build(method); - Assertions.assertThat(data).isEmpty(); + assertThat(data).isEmpty(); } @Test @@ -366,7 +369,7 @@ void testBiFunctionRawChildBean() throws NoSuchMethodException { Set data = functionalChannelBeanBuilder.build(method); - Assertions.assertThat(data).isEmpty(); + assertThat(data).isEmpty(); } @Test @@ -375,7 +378,7 @@ void testBiConsumerChildChildBean() throws NoSuchMethodException { Set data = functionalChannelBeanBuilder.build(method); - Assertions.assertThat(data) + assertThat(data) .containsExactly(new FunctionalChannelBeanData( "biConsumerChildChildBean", method, @@ -390,7 +393,7 @@ void testConsumerChildClassBean() throws NoSuchMethodException { Set data = functionalChannelBeanBuilder.build(method); - Assertions.assertThat(data) + assertThat(data) .containsExactly(new FunctionalChannelBeanData( "consumerChildClassBean", method, @@ -405,7 +408,7 @@ void testConsumerChildChildClassBean() throws NoSuchMethodException { Set data = functionalChannelBeanBuilder.build(method); - Assertions.assertThat(data) + assertThat(data) .containsExactly(new FunctionalChannelBeanData( "consumerChildChildClassBean", method, @@ -475,6 +478,23 @@ private ConsumerChildChildClass consumerChildChildClassBean() { } } + @Nested + class ErrorCases { + + @Test + void elementsOtherThanMethodOrClassThrowException() { + // given + AnnotatedType annotatedType = mock(AnnotatedType.class); + + // when + assertThatThrownBy(() -> { + functionalChannelBeanBuilder.build(annotatedType); + }) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Must be a Method or Class"); + } + } + private static Method getMethod(Class clazz, String methodName) throws NoSuchMethodException { return clazz.getDeclaredMethod(methodName); }