GH-596 Add support for handling conversion of complex types
This commit is contained in:
@@ -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) {
|
||||||
|
|||||||
@@ -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("\"")) ||
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user