From 222aac77ccbcc9dd9018101f131fdf93a0f466f0 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 21 Oct 2020 14:34:38 +0200 Subject: [PATCH] GH-596 Add support for handling conversion of complex types Resolves #596 --- .../catalog/SimpleFunctionRegistry.java | 33 +++++---- .../cloud/function/json/JsonMapper.java | 4 ++ ...BeanFactoryAwareFunctionRegistryTests.java | 67 +++++++++++++++++++ .../catalog/SimpleFunctionRegistryTests.java | 3 +- 4 files changed, 93 insertions(+), 14 deletions(-) diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java index 1c625bed1..cd5675b72 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java @@ -676,11 +676,7 @@ public class SimpleFunctionRegistry implements FunctionRegistry, FunctionInspect } } else { - Class inputType = this.isTypePublisher(type) || this.isInputTypeMessage() - ? TypeResolver.resolveRawClass(FunctionTypeUtils.getImmediateGenericType(type, 0), null) - : this.getRawClassFor(type); - - convertedInput = this.convertNonMessageInputIfNecessary(inputType, input); + convertedInput = this.convertNonMessageInputIfNecessary(type, input); } // wrap in Message if necessary if (this.isWrapConvertedInputInMessage(convertedInput)) { @@ -721,18 +717,25 @@ public class SimpleFunctionRegistry implements FunctionRegistry, FunctionInspect /* * */ - private Object convertNonMessageInputIfNecessary(Class inputType, Object input) { + private Object convertNonMessageInputIfNecessary(Type inputType, Object input) { Object convertedInput = input; - if (!inputType.isAssignableFrom(input.getClass())) { - if (inputType != input.getClass() - && SimpleFunctionRegistry.this.conversionService != null - && SimpleFunctionRegistry.this.conversionService.canConvert(input.getClass(), inputType)) { - convertedInput = SimpleFunctionRegistry.this.conversionService.convert(input, inputType); + Class rawInputType = this.isTypePublisher(inputType) || this.isInputTypeMessage() + ? TypeResolver.resolveRawClass(FunctionTypeUtils.getImmediateGenericType(inputType, 0), null) + : this.getRawClassFor(inputType); + + if (JsonMapper.isJsonString(input) && !Message.class.isAssignableFrom(rawInputType)) { + if (FunctionTypeUtils.isMessage(inputType)) { + inputType = FunctionTypeUtils.getGenericType(inputType); } - else { + if (Object.class != inputType) { convertedInput = SimpleFunctionRegistry.this.jsonMapper.fromJson(input, inputType); } } + else if (SimpleFunctionRegistry.this.conversionService != null + && !rawInputType.equals(input.getClass()) + && SimpleFunctionRegistry.this.conversionService.canConvert(input.getClass(), rawInputType)) { + convertedInput = SimpleFunctionRegistry.this.conversionService.convert(input, rawInputType); + } return convertedInput; } @@ -764,6 +767,10 @@ public class SimpleFunctionRegistry implements FunctionRegistry, FunctionInspect return type; } + private boolean isConversionHintRequired(Object actualType, Class rawType) { + return rawType != actualType; + } + /* * */ @@ -778,7 +785,7 @@ public class SimpleFunctionRegistry implements FunctionRegistry, FunctionInspect Object convertedInput = message; type = this.extractActualValueTypeIfNecessary(type); Class rawType = TypeResolver.resolveRawClass(type, null); - convertedInput = FunctionTypeUtils.isTypeCollection(type) + convertedInput = this.isConversionHintRequired(type, rawType) ? SimpleFunctionRegistry.this.messageConverter.fromMessage(message, rawType, type) : SimpleFunctionRegistry.this.messageConverter.fromMessage(message, rawType); diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/json/JsonMapper.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/json/JsonMapper.java index 55f5ab641..f0b99f7dd 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/json/JsonMapper.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/json/JsonMapper.java @@ -98,12 +98,16 @@ public abstract class JsonMapper { */ public static boolean isJsonString(Object value) { boolean isJson = false; + if (value instanceof byte[]) { + value = new String((byte[]) value, StandardCharsets.UTF_8); + } if (value instanceof String) { String str = ((String) value).trim(); isJson = (str.startsWith("\"") && str.endsWith("\"")) || (str.startsWith("{") && str.endsWith("}")) || (str.startsWith("[") && str.endsWith("]")); } + return isJson; } } 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 bab28e682..47171fbc6 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 @@ -269,6 +269,29 @@ public class BeanFactoryAwareFunctionRegistryTests { assertThat(block).isNull(); } + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test + public void textTypeConversionWithComplexInputType() { + FunctionCatalog catalog = this.configureCatalog(ComplexTypeFunctionConfiguration.class); + Function function = catalog.lookup("function"); + + // as String + String result = (String) function.apply("{\"key\":\"purchase\",\"data\":{\"name\":\"bike\"}}"); + assertThat(result).isEqualTo("BIKE"); + + // as byte[] + result = (String) function.apply("{\"key\":\"purchase\",\"data\":{\"name\":\"bike\"}}".getBytes()); + assertThat(result).isEqualTo("BIKE"); + + // as Message + result = (String) function.apply(MessageBuilder.withPayload("{\"key\":\"purchase\",\"data\":{\"name\":\"bike\"}}").build()); + assertThat(result).isEqualTo("BIKE"); + + // as Message + result = (String) function.apply(MessageBuilder.withPayload("{\"key\":\"purchase\",\"data\":{\"name\":\"bike\"}}".getBytes()).build()); + assertThat(result).isEqualTo("BIKE"); + } + // MULTI INPUT/OUTPUT @@ -919,4 +942,48 @@ public class BeanFactoryAwareFunctionRegistryTests { } } + + @EnableAutoConfiguration + @Configuration + public static class ComplexTypeFunctionConfiguration { + @Bean + public Function, String> function() { + return v -> v.getData().getName().toUpperCase(); + } + } + + private static class Product { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + private static class Event { + + private K key; + + public K getKey() { + return key; + } + + public void setKey(K key) { + this.key = key; + } + + private V data; + + public V getData() { + return data; + } + + public void setData(V data) { + this.data = data; + } + } } diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistryTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistryTests.java index 7434e0adb..a2f75e1df 100644 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistryTests.java +++ b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistryTests.java @@ -127,6 +127,8 @@ public class SimpleFunctionRegistryTests { assertThat(lookedUpFunction).isNull(); } + + @Test public void testFunctionComposition() { FunctionRegistration upperCaseRegistration = new FunctionRegistration<>( @@ -500,5 +502,4 @@ public class SimpleFunctionRegistryTests { .map(lst -> lst.stream().map(Person::getName).collect(Collectors.toList())); } } - }