GH-596 Add support for handling conversion of complex types

This commit is contained in:
Oleg Zhurakousky
2020-10-21 15:22:25 +02:00
parent 53592469eb
commit 5b68421732
3 changed files with 140 additions and 17 deletions

View File

@@ -21,6 +21,7 @@ import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -39,6 +40,7 @@ import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import net.jodah.typetools.TypeResolver;
import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation; import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
@@ -49,8 +51,10 @@ import reactor.core.publisher.Mono;
import reactor.util.function.Tuples; import reactor.util.function.Tuples;
import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.function.context.FunctionProperties; import org.springframework.cloud.function.context.FunctionProperties;
import org.springframework.cloud.function.context.FunctionRegistration; import org.springframework.cloud.function.context.FunctionRegistration;
import org.springframework.cloud.function.context.FunctionRegistry; import org.springframework.cloud.function.context.FunctionRegistry;
@@ -74,6 +78,8 @@ import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
* *
* Basic implementation of FunctionRegistry which maintains the cache of registered functions while * Basic implementation of FunctionRegistry which maintains the cache of registered functions while
@@ -109,6 +115,9 @@ public class SimpleFunctionRegistry implements FunctionRegistry, FunctionInspect
private List<String> declaredFunctionDefinitions; private List<String> declaredFunctionDefinitions;
@Autowired
private JsonMapper jsonMapper;
public SimpleFunctionRegistry(ConversionService conversionService, @Nullable CompositeMessageConverter messageConverter) { public SimpleFunctionRegistry(ConversionService conversionService, @Nullable CompositeMessageConverter messageConverter) {
this.conversionService = conversionService; this.conversionService = conversionService;
this.messageConverter = messageConverter; this.messageConverter = messageConverter;
@@ -791,7 +800,23 @@ public class SimpleFunctionRegistry implements FunctionRegistry, FunctionInspect
} }
if (value instanceof Message<?>) { // see AWS adapter with Optional payload if (value instanceof Message<?>) { // see AWS adapter with Optional payload
if (messageNeedsConversion(rawType, (Message<?>) value)) { if (messageNeedsConversion(rawType, (Message<?>) value)) {
convertedValue = FunctionTypeUtils.isTypeCollection(type)
boolean convertWithHint = false;
Type hint = type;
if (FunctionTypeUtils.isTypeCollection(type)) {
hint = FunctionTypeUtils.getGenericType(type);
convertWithHint = true;
}
else if (!rawType.equals(type)) {
// hint = FunctionTypeUtils.getGenericType(type);
convertWithHint = true;
}
convertedValue = convertWithHint
? this.fromMessage((Message<?>) value, (Class<?>) rawType, FunctionTypeUtils.getGenericType(type)) ? this.fromMessage((Message<?>) value, (Class<?>) rawType, FunctionTypeUtils.getGenericType(type))
: this.fromMessage((Message<?>) value, (Class<?>) rawType, null); : this.fromMessage((Message<?>) value, (Class<?>) rawType, null);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
@@ -812,22 +837,23 @@ public class SimpleFunctionRegistry implements FunctionRegistry, FunctionInspect
} }
} }
} }
else if (rawType instanceof Class<?>) { // see AWS adapter with WildardTypeImpl and Azure with Voids else { //if (rawType instanceof Class<?>) { // see AWS adapter with WildardTypeImpl and Azure with Voids
if (this.isJson(value)) { convertedValue = this.convertNonMessageInputIfNecessary(type, value);
convertedValue = messageConverter // if (this.isJson(value)) {
.fromMessage(new GenericMessage<Object>(value), (Class<?>) rawType); // convertedValue = messageConverter
} // .fromMessage(new GenericMessage<Object>(value), (Class<?>) rawType);
else { // }
try { // else {
convertedValue = conversionService.convert(value, (Class<?>) rawType); // try {
} // convertedValue = conversionService.convert(value, (Class<?>) rawType);
catch (Exception e) { // }
if (value instanceof String || value instanceof byte[]) { // catch (Exception e) {
convertedValue = messageConverter // if (value instanceof String || value instanceof byte[]) {
.fromMessage(new GenericMessage<Object>(value), (Class<?>) rawType); // convertedValue = messageConverter
} // .fromMessage(new GenericMessage<Object>(value), (Class<?>) rawType);
} // }
} // }
// }
} }
} }
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
@@ -839,6 +865,35 @@ public class SimpleFunctionRegistry implements FunctionRegistry, FunctionInspect
return convertedValue; return convertedValue;
} }
private Object convertNonMessageInputIfNecessary(Type inputType, Object input) {
Object convertedInput = input;
Class<?> rawInputType = FunctionTypeUtils.isReactive(inputType) || FunctionTypeUtils.isMessage(inputType)
? TypeResolver.resolveRawClass(FunctionTypeUtils.getImmediateGenericType(inputType, 0), null)
: TypeResolver.resolveRawClass(inputType, null);
if (JsonMapper.isJsonString(input) && !Message.class.isAssignableFrom(rawInputType)) {
if (FunctionTypeUtils.isMessage(inputType)) {
inputType = FunctionTypeUtils.getGenericType(inputType);
}
if (!(inputType instanceof WildcardType) && Object.class != inputType && SimpleFunctionRegistry.this.jsonMapper != null) {
try {
convertedInput = SimpleFunctionRegistry.this.jsonMapper.fromJson(input, inputType);
}
catch (Exception e) {
// ignore
}
}
}
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;
}
private Object fromMessage(Message<?> message, Class<?> rawType, Object conversionHint) { private Object fromMessage(Message<?> message, Class<?> rawType, Object conversionHint) {
Stream<?> stream = null; Stream<?> stream = null;
if (message.getPayload() instanceof Collection) { if (message.getPayload() instanceof Collection) {

View File

@@ -98,6 +98,9 @@ public abstract class JsonMapper {
*/ */
public static boolean isJsonString(Object value) { public static boolean isJsonString(Object value) {
boolean isJson = false; boolean isJson = false;
if (value instanceof byte[]) {
value = new String((byte[]) value, StandardCharsets.UTF_8);
}
if (value instanceof String) { if (value instanceof String) {
String str = ((String) value).trim(); String str = ((String) value).trim();
isJson = (str.startsWith("\"") && str.endsWith("\"")) || isJson = (str.startsWith("\"") && str.endsWith("\"")) ||

View File

@@ -483,6 +483,28 @@ public class BeanFactoryAwareFunctionRegistryTests {
assertThat(f.apply("Bubbles")).isEqualTo("BUBBLES"); assertThat(f.apply("Bubbles")).isEqualTo("BUBBLES");
} }
@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<String>
result = (String) function.apply(MessageBuilder.withPayload("{\"key\":\"purchase\",\"data\":{\"name\":\"bike\"}}").build());
assertThat(result).isEqualTo("BIKE");
// as Message<BYTE[]>
result = (String) function.apply(MessageBuilder.withPayload("{\"key\":\"purchase\",\"data\":{\"name\":\"bike\"}}".getBytes()).build());
assertThat(result).isEqualTo("BIKE");
}
@EnableAutoConfiguration @EnableAutoConfiguration
public static class PojoToMessageFunctionCompositionConfiguration { public static class PojoToMessageFunctionCompositionConfiguration {
@@ -919,4 +941,47 @@ public class BeanFactoryAwareFunctionRegistryTests {
} }
} }
@EnableAutoConfiguration
@Configuration
public static class ComplexTypeFunctionConfiguration {
@Bean
public Function<Event<String, Product>, 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<K, V> {
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;
}
}
} }