diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionType.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionType.java index d379ac795..eedbd2688 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionType.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionType.java @@ -17,8 +17,10 @@ package org.springframework.cloud.function.context; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.util.function.Function; import org.springframework.cloud.function.context.catalog.FunctionInspector; +import org.springframework.core.ResolvableType; import org.springframework.messaging.Message; import reactor.core.publisher.Flux; @@ -29,6 +31,9 @@ import reactor.core.publisher.Flux; */ public class FunctionType { + public static FunctionType UNCLASSIFIED = new FunctionType(ResolvableType + .forClassWithGenerics(Function.class, Object.class, Object.class).getType()); + private Type type; public FunctionType(Type type) { @@ -60,6 +65,36 @@ public class FunctionType { || Message.class.isAssignableFrom(outputType); } + public static FunctionType compose(FunctionType input, FunctionType output) { + ResolvableType inputGeneric; + ResolvableType inputType = ResolvableType.forClass(input.getInputType()); + if (input.isMessage()) { + inputType = ResolvableType.forClassWithGenerics(Message.class, inputType); + } + ResolvableType outputGeneric; + ResolvableType outputType = ResolvableType.forClass(output.getOutputType()); + if (output.isMessage()) { + outputType = ResolvableType.forClassWithGenerics(Message.class, outputType); + } + if (FunctionInspector.isWrapper(input.getInputWrapper())) { + inputGeneric = ResolvableType.forClassWithGenerics(input.getInputWrapper(), + inputType); + } + else { + inputGeneric = inputType; + } + if (FunctionInspector.isWrapper(output.getInputWrapper())) { + outputGeneric = ResolvableType.forClassWithGenerics(output.getInputWrapper(), + outputType); + } + else { + outputGeneric = outputType; + } + return new FunctionType(ResolvableType + .forClassWithGenerics(Function.class, inputGeneric, outputGeneric) + .getType()); + } + private Class findType(ParamType paramType) { int index = paramType.isOutput() ? 1 : 0; Type type = this.type; diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogAutoConfiguration.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogAutoConfiguration.java index 61c53a6e3..4b7db0856 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogAutoConfiguration.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogAutoConfiguration.java @@ -46,6 +46,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.cloud.function.context.FunctionRegistration; import org.springframework.cloud.function.context.FunctionRegistry; import org.springframework.cloud.function.context.FunctionScan; +import org.springframework.cloud.function.context.FunctionType; import org.springframework.cloud.function.context.catalog.FunctionInspector; import org.springframework.cloud.function.context.catalog.FunctionRegistrationEvent; import org.springframework.cloud.function.context.catalog.FunctionUnregistrationEvent; @@ -68,14 +69,11 @@ import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.io.Resource; import org.springframework.core.type.StandardMethodMetadata; import org.springframework.core.type.classreading.MethodMetadataReadingVisitor; -import org.springframework.messaging.Message; import org.springframework.stereotype.Component; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; -import reactor.core.publisher.Flux; - /** * @author Dave Syer * @author Mark Fisher @@ -176,22 +174,22 @@ public class ContextFunctionCatalogAutoConfiguration { @Override public Class getInputWrapper(Object function) { - return processor.findType(function, ParamType.INPUT_WRAPPER); + return processor.findType(function).getInputWrapper(); } @Override public Class getOutputWrapper(Object function) { - return processor.findType(function, ParamType.OUTPUT_WRAPPER); + return processor.findType(function).getOutputWrapper(); } @Override public Class getInputType(Object function) { - return processor.findType(function, ParamType.INPUT); + return processor.findType(function).getInputType(); } @Override public Class getOutputType(Object function) { - return processor.findType(function, ParamType.OUTPUT); + return processor.findType(function).getOutputType(); } @Override @@ -217,6 +215,7 @@ public class ContextFunctionCatalogAutoConfiguration { @Autowired(required = false) private ApplicationEventPublisher publisher; + @Autowired private ConfigurableListableBeanFactory registry; @@ -224,7 +223,7 @@ public class ContextFunctionCatalogAutoConfiguration { private Map registrations = new HashMap<>(); - private Map>> types = new HashMap<>(); + private Map types = new HashMap<>(); public Set getSuppliers() { return this.suppliers.keySet(); @@ -295,16 +294,12 @@ public class ContextFunctionCatalogAutoConfiguration { } final Object value = function; lookup.computeIfAbsent(name, key -> value); - Map> values = types.computeIfAbsent(name, - key -> new HashMap<>()); - for (ParamType type : ParamType.values()) { - if (!values.containsKey(type)) { - if (type.isInput()) { - values.put(type, types.get(stages[0]).get(type)); - } - else { - values.put(type, types.get(stages[stages.length - 1]).get(type)); - } + if (!types.containsKey(name)) { + if (types.containsKey(stages[0]) + && types.containsKey(stages[stages.length - 1])) { + FunctionType input = types.get(stages[0]); + FunctionType output = types.get(stages[stages.length - 1]); + types.put(name, FunctionType.compose(input, output)); } } registrations.put(function, name); @@ -314,13 +309,7 @@ public class ContextFunctionCatalogAutoConfiguration { private Object lookup(String name, Map lookup) { Object result = lookup.get(name); if (result != null) { - Map> values = types.computeIfAbsent(name, - key -> new HashMap<>()); - for (ParamType type : ParamType.values()) { - if (!values.containsKey(type)) { - values.put(type, findType(result, type)); - } - } + findType(result); } return result; } @@ -431,7 +420,7 @@ public class ContextFunctionCatalogAutoConfiguration { this.conversionService = conversionService != null ? conversionService : new DefaultConversionService(); } - Class type = findType(function, ParamType.INPUT); + Class type = findType(function).getInputType(); return conversionService.canConvert(String.class, type) ? conversionService.convert(value, type) : value; @@ -453,9 +442,7 @@ public class ContextFunctionCatalogAutoConfiguration { Class type; if (target instanceof Supplier) { type = Supplier.class; - findType(target, ParamType.OUTPUT); - findType(target, ParamType.OUTPUT_WRAPPER); - isMessage(target); + findType(target); registration.target(target((Supplier) target, key)); for (String name : registration.getNames()) { this.suppliers.put(name, registration.getTarget()); @@ -463,9 +450,7 @@ public class ContextFunctionCatalogAutoConfiguration { } else if (target instanceof Consumer) { type = Consumer.class; - findType(target, ParamType.INPUT); - findType(target, ParamType.INPUT_WRAPPER); - isMessage(target); // cache wrapper types + findType(target); registration.target(target((Consumer) target, key)); for (String name : registration.getNames()) { this.consumers.put(name, registration.getTarget()); @@ -473,11 +458,7 @@ public class ContextFunctionCatalogAutoConfiguration { } else if (target instanceof Function) { type = Function.class; - findType(target, ParamType.INPUT); - findType(target, ParamType.OUTPUT); - findType(target, ParamType.INPUT_WRAPPER); - findType(target, ParamType.OUTPUT_WRAPPER); - isMessage(target); // cache wrapper types + findType(target); registration.target(target((Function) target, key)); for (String name : registration.getNames()) { this.functions.put(name, registration.getTarget()); @@ -560,92 +541,55 @@ public class ContextFunctionCatalogAutoConfiguration { } private boolean hasFluxTypes(Object function) { - return FunctionInspector - .isWrapper(findType(function, ParamType.INPUT_WRAPPER)) - || FunctionInspector - .isWrapper(findType(function, ParamType.OUTPUT_WRAPPER)); + return FunctionInspector.isWrapper(findType(function).getInputWrapper()) + || FunctionInspector.isWrapper(findType(function).getOutputWrapper()); } - private Class findType(String name, AbstractBeanDefinition definition, - ParamType paramType) { + private FunctionType findType(String name, AbstractBeanDefinition definition) { Object source = definition.getSource(); - Type param = null; + FunctionType param = null; // Start by assuming output -> Function - int index = paramType.isOutput() ? 1 : 0; if (source instanceof StandardMethodMetadata) { // Standard @Bean metadata Type beanType = ((StandardMethodMetadata) source).getIntrospectedMethod() .getGenericReturnType(); if (beanType instanceof ParameterizedType) { ParameterizedType type = (ParameterizedType) beanType; - param = extractType(type, paramType, index); + param = new FunctionType(type); } else { - param = findTypeFromBeanClass((Class) beanType, paramType); + param = new FunctionType(beanType); } } else if (source instanceof MethodMetadataReadingVisitor) { // A component scan with @Beans MethodMetadataReadingVisitor visitor = (MethodMetadataReadingVisitor) source; Type type = findBeanType(definition, visitor); - param = extractType(type, paramType, index); + param = new FunctionType(type); } else if (source instanceof Resource) { Class beanType = this.registry.getType(name); - param = findTypeFromBeanClass(beanType, paramType); - if (param == null) { - return Object.class; - } + param = new FunctionType(beanType); } else { ResolvableType resolvable = (ResolvableType) getField(definition, "targetType"); if (resolvable != null) { - param = resolvable.getGeneric(index).getGeneric(0).getType(); + param = new FunctionType(resolvable.getType()); } else { Object bean = this.registry.getBean(name); if (bean instanceof FunctionFactoryMetadata) { FunctionFactoryMetadata factory = (FunctionFactoryMetadata) bean; Type type = factory.getFactoryMethod().getGenericReturnType(); - param = extractType(type, paramType, index); + param = new FunctionType(type); + } + else { + param = new FunctionType(bean.getClass()); } } } - return extractClass(name, param, paramType); - } - - private Class extractClass(String name, Type param, ParamType paramType) { - if (param instanceof ParameterizedType) { - ParameterizedType concrete = (ParameterizedType) param; - param = concrete.getRawType(); - } - if (param == null) { - // Last ditch attempt to guess: Flux - if (paramType.isWrapper()) { - param = Flux.class; - } - else { - param = String.class; - } - } - Class result = param instanceof Class ? (Class) param : null; - if (result != null) { - Map> values = types.computeIfAbsent(name, - key -> new HashMap<>()); - values.put(paramType, result); - } - return result; - } - - private Type findTypeFromBeanClass(Class beanType, ParamType paramType) { - int index = paramType.isOutput() ? 1 : 0; - for (Type type : beanType.getGenericInterfaces()) { - if (type.getTypeName().startsWith("java.util.function")) { - return extractType(type, paramType, index); - } - } - return null; + return param; } private Type findBeanType(AbstractBeanDefinition definition, @@ -663,48 +607,6 @@ public class ContextFunctionCatalogAutoConfiguration { return type; } - private Type extractType(Type type, ParamType paramType, int index) { - Type param; - if (type instanceof ParameterizedType) { - ParameterizedType parameterizedType = (ParameterizedType) type; - if (parameterizedType.getActualTypeArguments().length == 1) { - // There's only one - index = 0; - } - Type typeArgumentAtIndex = parameterizedType - .getActualTypeArguments()[index]; - if (typeArgumentAtIndex instanceof ParameterizedType - && !paramType.isWrapper()) { - if (FunctionInspector.isWrapper( - ((ParameterizedType) typeArgumentAtIndex).getRawType())) { - param = ((ParameterizedType) typeArgumentAtIndex) - .getActualTypeArguments()[0]; - param = extractNestedType(paramType, param); - } - else { - param = extractNestedType(paramType, typeArgumentAtIndex); - } - } - else { - param = extractNestedType(paramType, typeArgumentAtIndex); - } - } - else { - param = Object.class; - } - return param; - } - - private Type extractNestedType(ParamType paramType, Type param) { - if (!paramType.isInnerWrapper() - && param.getTypeName().startsWith(Message.class.getName())) { - if (param instanceof ParameterizedType) { - param = ((ParameterizedType) param).getActualTypeArguments()[0]; - } - } - return param; - } - private Object getField(Object target, String name) { Field field = ReflectionUtils.findField(target.getClass(), name); if (field == null) { @@ -715,58 +617,32 @@ public class ContextFunctionCatalogAutoConfiguration { } private boolean isMessage(Object function) { - Class inputType = findType(function, ParamType.INPUT_INNER_WRAPPER); - Class outputType = findType(function, ParamType.OUTPUT_INNER_WRAPPER); - return inputType.getName().startsWith(Message.class.getName()) - || Message.class.isAssignableFrom(inputType) - || outputType.getName().startsWith(Message.class.getName()) - || Message.class.isAssignableFrom(outputType); + return findType(function).isMessage(); } - private Class findType(Object function, ParamType type) { + private FunctionType findType(Object function) { String name = registrations.get(function); if (types.containsKey(name)) { - Map> values = types.get(name); - if (values.containsKey(type)) { - return values.get(type); - } + return types.get(name); } + FunctionType param; if (name == null || registry == null || !registry.containsBeanDefinition(name)) { if (function != null) { - Type param = findTypeFromBeanClass(function.getClass(), type); - if (param != null) { - Class result = extractClass(name, param, type); - if (result != null) { - return result; - } - } + param = new FunctionType(function.getClass()); + } + else { + param = FunctionType.UNCLASSIFIED; } - return Object.class; } - return findType(name, - (AbstractBeanDefinition) registry.getBeanDefinition(name), type); + else { + param = findType(name, + (AbstractBeanDefinition) registry.getBeanDefinition(name)); + } + types.computeIfAbsent(name, str -> param); + return param; } + } - enum ParamType { - INPUT, OUTPUT, INPUT_WRAPPER, OUTPUT_WRAPPER, INPUT_INNER_WRAPPER, OUTPUT_INNER_WRAPPER; - - public boolean isOutput() { - return this == OUTPUT || this == OUTPUT_WRAPPER - || this == OUTPUT_INNER_WRAPPER; - } - - public boolean isInput() { - return this == INPUT || this == INPUT_WRAPPER || this == INPUT_INNER_WRAPPER; - } - - public boolean isWrapper() { - return this == OUTPUT_WRAPPER || this == INPUT_WRAPPER; - } - - public boolean isInnerWrapper() { - return this == OUTPUT_INNER_WRAPPER || this == INPUT_INNER_WRAPPER; - } - } }