From 6bfc614f9f2d647d72c563fef9a005cff944d480 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 10 Jun 2020 18:28:28 +0200 Subject: [PATCH] GH-531 Fail function registration for incompatible types Given that we now can auto-discover function type from provided target object, this fix will fail function registration if provided type is not compatible (assignableFrom. . .) Resolves #531 --- .../context/FunctionRegistration.java | 19 ++++++- ...BeanFactoryAwareFunctionRegistryTests.java | 56 +++++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionRegistration.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionRegistration.java index ea3852775..95f189e35 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionRegistration.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionRegistration.java @@ -27,10 +27,13 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; +import net.jodah.typetools.TypeResolver; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; + import org.springframework.beans.factory.BeanNameAware; +import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; import org.springframework.cloud.function.context.config.RoutingFunction; import org.springframework.cloud.function.core.FluxConsumer; import org.springframework.cloud.function.core.FluxFunction; @@ -43,6 +46,8 @@ import org.springframework.cloud.function.core.MonoToFluxFunction; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; + + /** * @param target type * @author Dave Syer @@ -114,11 +119,21 @@ public class FunctionRegistration implements BeanNameAware { } public FunctionRegistration type(Type type) { - this.type = FunctionType.of(type); - return this; + return type(FunctionType.of(type)); } public FunctionRegistration type(FunctionType type) { + + Type t = FunctionTypeUtils.discoverFunctionTypeFromFunctionalObject(this.target); + FunctionType discoveredFunctionType = FunctionType.of(t); + Class inputType = TypeResolver.resolveRawClass(discoveredFunctionType.getInputType(), null); + Class outputType = TypeResolver.resolveRawClass(discoveredFunctionType.getOutputType(), null); + + if (!(inputType.isAssignableFrom(TypeResolver.resolveRawClass(type.getInputType(), null)) + && outputType.isAssignableFrom(TypeResolver.resolveRawClass(type.getOutputType(), null)))) { + throw new IllegalStateException("Discovered function type does not match provided function type. Discovered: " + + discoveredFunctionType + "; Provided: " + type); + } this.type = type; return this; } 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 01dee90ec..60fe118fa 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,9 @@ import reactor.util.function.Tuples; 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.FunctionRegistration; +import org.springframework.cloud.function.context.FunctionRegistry; +import org.springframework.cloud.function.context.FunctionType; import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -59,6 +62,7 @@ import org.springframework.util.ReflectionUtils; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; /** * @@ -84,6 +88,7 @@ public class BeanFactoryAwareFunctionRegistryTests { System.clearProperty("spring.cloud.function.definition"); } + @SuppressWarnings("unchecked") @Test public void testDefaultLookup() throws Exception { FunctionCatalog catalog = this.configureCatalog(); @@ -320,6 +325,7 @@ public class BeanFactoryAwareFunctionRegistryTests { result.getT3().subscribe(v -> System.out.println("=> 3: " + v)); } + @SuppressWarnings("rawtypes") @Test public void SCF_GH_409ConfigurationTests() { FunctionCatalog catalog = this.configureCatalog(SCF_GH_409ConfigurationAsSupplier.class); @@ -433,6 +439,46 @@ public class BeanFactoryAwareFunctionRegistryTests { assertThat(f.apply(Flux.just(25)).blockFirst()).isEqualTo(25); } + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void testRegisteringWithTypeThatDoesNotMatchDiscoveredType() { + FunctionCatalog catalog = this.configureCatalog(EmptyConfiguration.class); + Function func = catalog.lookup("func"); + assertThat(func).isNull(); + FunctionRegistry registry = (FunctionRegistry) catalog; + try { + FunctionRegistration registration = new FunctionRegistration(new MyFunction(), "a").type(FunctionType.from(Integer.class).to(String.class)); + registry.register(registration); + fail(); + } + catch (IllegalStateException e) { + // good as we expect it to fail + } + // + try { + FunctionRegistration registration = new FunctionRegistration(new MyFunction(), "b").type(FunctionType.from(String.class).to(Integer.class)); + registry.register(registration); + fail(); + } + catch (IllegalStateException e) { + // good as we expect it to fail + } + // + FunctionRegistration c = new FunctionRegistration(new MyFunction(), "c").type(FunctionType.from(String.class).to(String.class)); + registry.register(c); + // + FunctionRegistration d = new FunctionRegistration(new RawFunction(), "d").type(FunctionType.from(Person.class).to(String.class)); + registry.register(d); + // + FunctionRegistration e = new FunctionRegistration(new RawFunction(), "e").type(FunctionType.from(Object.class).to(Object.class)); + registry.register(e); + } + + @EnableAutoConfiguration + public static class EmptyConfiguration { + + } + public interface ReactiveFunction extends Function, Flux> { } @@ -522,6 +568,7 @@ public class BeanFactoryAwareFunctionRegistryTests { super(singletonList(MimeType.valueOf(mimeType))); } + @SuppressWarnings("unchecked") @Override protected Object convertFromInternal( Message message, Class targetClass, @Nullable Object conversionHint) { @@ -793,6 +840,15 @@ public class BeanFactoryAwareFunctionRegistryTests { } + public static class RawFunction implements Function { + + @Override + public Object apply(Object t) { + return t; + } + + } + @EnableAutoConfiguration @Configuration @Component