diff --git a/spring-cloud-function-context/pom.xml b/spring-cloud-function-context/pom.xml index f6d6f25bf59f3c8f507578082a4bab2fcc1bf6cd..f1dd23ca316c21e940e317f54395bccd37bc33e7 100644 --- a/spring-cloud-function-context/pom.xml +++ b/spring-cloud-function-context/pom.xml @@ -16,6 +16,11 @@ </parent> <dependencies> + <dependency> + <groupId>net.jodah</groupId> + <artifactId>typetools</artifactId> + <version>0.6.2</version> + </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistry.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistry.java index 386835a198116846c6b7af33935a77c37d2673b5..fc332e64a9a53fe42331c1040e62416466afde9c 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistry.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistry.java @@ -254,6 +254,9 @@ public class BeanFactoryAwareFunctionRegistry } private Type discoverFunctionType(Object function, String... names) { + if (function instanceof RoutingFunction) { + return FunctionType.of(FunctionContextUtils.findType(applicationContext.getBeanFactory(), names)).getType(); + } boolean beanDefinitionExists = false; for (int i = 0; i < names.length && !beanDefinitionExists; i++) { beanDefinitionExists = this.applicationContext.getBeanFactory().containsBeanDefinition(names[i]); @@ -267,9 +270,15 @@ public class BeanFactoryAwareFunctionRegistry logger.info("BeanDefinition for function name(s) '" + Arrays.asList(names) + "' can not be located. FunctionType will be based on " + function.getClass()); } - return beanDefinitionExists - ? FunctionType.of(FunctionContextUtils.findType(applicationContext.getBeanFactory(), names)).getType() - : new FunctionType(function.getClass()).getType(); + + Type type = FunctionTypeUtils.discoverFunctionTypeFromClass(function.getClass()); + if (beanDefinitionExists) { + Type t = FunctionTypeUtils.getImmediateGenericType(type, 0); + if (t == null || t == Object.class) { + type = FunctionType.of(FunctionContextUtils.findType(this.applicationContext.getBeanFactory(), names)).getType(); + } + } + return type; } private String discoverDefaultDefinitionIfNecessary(String definition) { diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtils.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtils.java index 3390a0739a7fcb4e610466d6b4efdad64168088b..a87db468f7de2b7224ade5de2ef72ce9449a6594 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtils.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtils.java @@ -30,6 +30,7 @@ import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Stream; +import net.jodah.typetools.TypeResolver; import org.reactivestreams.Publisher; import reactor.util.function.Tuple2; @@ -118,29 +119,18 @@ public final class FunctionTypeUtils { } } + @SuppressWarnings("unchecked") public static Type discoverFunctionTypeFromClass(Class<?> functionalClass) { Assert.isTrue(isFunctional(functionalClass), "Type must be one of Supplier, Function or Consumer"); - Type[] generics = functionalClass.getGenericInterfaces(); - if (ObjectUtils.isEmpty(generics)) { - return functionalClass; + + if (Function.class.isAssignableFrom(functionalClass)) { + return TypeResolver.reify(Function.class, (Class<Function<?, ?>>) functionalClass); } - else { - for (Type generic : generics) { - if (generic instanceof ParameterizedType) { - Class<?> rawClsss = (Class<?>) ((ParameterizedType) generic).getRawType(); - if (Function.class.isAssignableFrom(rawClsss) - || Consumer.class.isAssignableFrom(rawClsss) - || Supplier.class.isAssignableFrom(rawClsss)) { - return generic; - } - else { - return discoverFunctionTypeFromClass(rawClsss); - } - } - else { - return discoverFunctionTypeFromClass((Class<?>) generic); - } - } + else if (Consumer.class.isAssignableFrom(functionalClass)) { + return TypeResolver.reify(Consumer.class, (Class<Consumer<?>>) functionalClass); + } + else if (Supplier.class.isAssignableFrom(functionalClass)) { + return TypeResolver.reify(Supplier.class, (Class<Supplier<?>>) functionalClass); } return null; } diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistryTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistryTests.java index 27ccad0a2e6d3caa31ef2e06e6ddf762627dfc6f..f170529c068f3759c32bd36b83cdb561ae3735e0 100644 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistryTests.java +++ b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistryTests.java @@ -41,6 +41,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.cloud.function.context.FunctionCatalog; import org.springframework.cloud.function.context.catalog.BeanFactoryAwareFunctionRegistry.FunctionInvocationWrapper; +import org.springframework.cloud.function.context.catalog.FunctionTypeUtilsTests.ReactiveFunction; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -356,7 +357,6 @@ public class BeanFactoryAwareFunctionRegistryTests { assertThat(result.getPayload()).isEqualTo("\"b2xsZWg=\"".getBytes()); } - @SuppressWarnings("rawtypes") @Test public void testMultipleValuesInOutputHandling() throws Exception { FunctionCatalog catalog = this.configureCatalog(CollectionOutConfiguration.class); @@ -421,6 +421,29 @@ public class BeanFactoryAwareFunctionRegistryTests { assertThat(dateResult.getHeaders().get("accept")).isNull(); } + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void testWithComplexHierarchyAndTypeConversion() { + FunctionCatalog catalog = this.configureCatalog(ReactiveFunctionImpl.class); + Function<Object, Flux> f = catalog.lookup(""); + assertThat(f.apply(new GenericMessage("23")).blockFirst()).isEqualTo(23); + assertThat(f.apply(Flux.just("25")).blockFirst()).isEqualTo(25); + assertThat(f.apply(Flux.just(25)).blockFirst()).isEqualTo(25); + } + + public interface ReactiveFunction<S, T> extends Function<Flux<S>, Flux<T>> { + + } + + @Component + @EnableAutoConfiguration + public static class ReactiveFunctionImpl implements ReactiveFunction<String, Integer> { + @Override + public Flux<Integer> apply(Flux<String> inFlux) { + return inFlux.map(v -> Integer.parseInt(v)); + } + } + @SuppressWarnings("unchecked") @EnableAutoConfiguration public static class CollectionOutConfiguration { diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtilsTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtilsTests.java index 3066418f758d57840cdcff80386818bd6e856620..a7b91fffb4b54f7ab1e3127030522948baf16dbd 100644 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtilsTests.java +++ b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtilsTests.java @@ -132,33 +132,12 @@ public class FunctionTypeUtilsTests { } @Test - public void foo() { + public void testWithComplexHierarchy() { FunctionType type = FunctionType.of(FunctionTypeUtils.discoverFunctionTypeFromClass(ReactiveFunctionImpl.class)); assertThat(String.class).isAssignableFrom(type.getInputType()); assertThat(Integer.class).isAssignableFrom(type.getOutputType()); } -// @Test -// public void testInputTypeByIndex() throws Exception { -// Type functionType = getReturnType("function"); -// Type inputType = FunctionTypeUtils.getInputType(functionType, 0); -// assertThat(inputType.getTypeName()).isEqualTo(String.class.getName()); -// inputType = FunctionTypeUtils.getInputType(functionType, 1); -// assertThat(inputType.getTypeName()).isEqualTo(Integer.class.getName()); -// -// functionType = getReturnType("multiInputOutputPublisherFunction"); -// inputType = FunctionTypeUtils.getInputType(functionType, 0); -// System.out.println("Reactive: " + FunctionTypeUtils.isReactive(inputType)); -// System.out.println("Reactive: " + FunctionTypeUtils.getPublisherType(inputType)); -// System.out.println("Reactive: " + FunctionTypeUtils.getImmediateGenericType(inputType, 0)); -// System.out.println(inputType); -// -// functionType = getReturnType("typelessFunction"); -// inputType = FunctionTypeUtils.getInputType(functionType, 0); -// System.out.println(inputType); -// } - - private static Function<String, Integer> function() { return null; } @@ -246,6 +225,5 @@ public class FunctionTypeUtilsTests { public Flux<Integer> apply(Flux<String> inFlux) { return inFlux.map(v -> Integer.parseInt(v)); } - } }