Refactor FunctionCatalog implementation

This commit is contained in:
Oleg Zhurakousky
2020-09-17 14:02:51 +02:00
parent 978a474c81
commit 72f05fc591
34 changed files with 1597 additions and 1643 deletions

View File

@@ -171,7 +171,14 @@ public abstract class AbstractSpringFunctionAdapterInitializer<C> implements Clo
protected Publisher<?> apply(Publisher<?> input) {
if (this.function != null) {
return Flux.from(this.function.apply(input));
//return Flux.from(this.function.apply(input));
Object result = this.function.apply(input);
if (result instanceof Publisher) {
return Flux.from((Publisher) result);
}
else {
return Flux.just(result);
}
}
if (this.consumer != null) {
this.consumer.accept(input);

View File

@@ -30,6 +30,33 @@ import javax.activation.MimeType;
*/
public interface FunctionCatalog {
/**
* Will look up the instance of the functional interface by name only.
*
* @param <T> instance type
* @param functionDefinition the definition of the functional interface. Must
* not be null;
* @return instance of the functional interface registered with this catalog
*/
default <T> T lookup(String functionDefinition) {
return this.lookup(null, functionDefinition, (String[]) null);
}
/**
* Will look up the instance of the functional interface by name and type which
* can only be Supplier, Consumer or Function. If type is not provided, the
* lookup will be made based on name only.
*
* @param <T> instance type
* @param type the type of functional interface. Can be null
* @param functionDefinition the definition of the functional interface. Must
* not be null;
* @return instance of the functional interface registered with this catalog
*/
default <T> T lookup(Class<?> type, String functionDefinition) {
return this.lookup(type, functionDefinition, (String[]) null);
}
/**
* Will look up the instance of the functional interface by name only.
@@ -56,34 +83,17 @@ public interface FunctionCatalog {
* used to convert function output back to {@code Message<byte[]>}.
* @return instance of the functional interface registered with this catalog
*/
default <T> T lookup(String functionDefinition, String... acceptedOutputMimeTypes) {
throw new UnsupportedOperationException("This instance of FunctionCatalog does not support this operation");
default <T> T lookup(String functionDefinition, String... expectedOutputMimeTypes) {
return this.lookup(null, functionDefinition, expectedOutputMimeTypes);
}
/**
* Will look up the instance of the functional interface by name only.
*
* @param <T> instance type
* @param functionDefinition the definition of the functional interface. Must
* not be null;
* @return instance of the functional interface registered with this catalog
*/
default <T> T lookup(String functionDefinition) {
return this.lookup(null, functionDefinition);
}
<T> T lookup(Class<?> type, String functionDefinition, String... expectedOutputMimeTypes); //{
// throw new UnsupportedOperationException("This instance of FunctionCatalog does not support this operation");
// }
/**
* Will look up the instance of the functional interface by name and type which
* can only be Supplier, Consumer or Function. If type is not provided, the
* lookup will be made based on name only.
*
* @param <T> instance type
* @param type the type of functional interface. Can be null
* @param functionDefinition the definition of the functional interface. Must
* not be null;
* @return instance of the functional interface registered with this catalog
*/
<T> T lookup(Class<?> type, String functionDefinition);
Set<String> getNames(Class<?> type);

View File

@@ -33,10 +33,20 @@ public class FunctionProperties {
public final static String PREFIX = "spring.cloud.function";
/**
* Name of he header to be used to instruct function catalog to skip type conversion.
* Name of the header to be used to instruct function catalog to skip type conversion.
*/
public final static String SKIP_CONVERSION_HEADER = "skip-type-conversion";
/**
* Name of the header to be used to instruct function to apply this content type for output conversion.
*/
public final static String EXPECT_CONTENT_TYPE_HEADER = "expected-content-type";
/**
* The name of function definition property.
*/
public final static String FUNCTION_DEFINITION = PREFIX + ".definition";
/**
* Definition of the function to be used. This could be function name (e.g., 'myFunction')
* or function composition definition (e.g., 'myFunction|yourFunction')
@@ -44,7 +54,7 @@ public class FunctionProperties {
private String definition;
private String accept;
private String expectedContentType;
/**
* SpEL expression which should result in function definition (e.g., function name or composition instruction).
@@ -68,11 +78,11 @@ public class FunctionProperties {
this.routingExpression = routingExpression;
}
public String getAccept() {
return accept;
public String getExpectedContentType() {
return this.expectedContentType;
}
public void setAccept(String accept) {
this.accept = accept;
public void setExpectedContentType(String expectedContentType) {
this.expectedContentType = expectedContentType;
}
}

View File

@@ -16,74 +16,63 @@
package org.springframework.cloud.function.context.catalog;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.cloud.function.context.FunctionCatalog;
import org.springframework.cloud.function.context.FunctionProperties;
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.config.FunctionContextUtils;
import org.springframework.cloud.function.context.config.RoutingFunction;
import org.springframework.cloud.function.json.JsonMapper;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.type.StandardMethodMetadata;
import org.springframework.lang.Nullable;
import org.springframework.messaging.converter.CompositeMessageConverter;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
public class BeanFactoryAwareFunctionRegistry extends SimpleFunctionRegistry implements ApplicationContextAware {
/**
* Implementation of {@link FunctionRegistry} and {@link FunctionCatalog} which is aware of the
* underlying {@link BeanFactory} to access available functions. Functions that are registered via
* {@link #register(FunctionRegistration)} operation are stored/cached locally.
*
* @author Oleg Zhurakousky
* @author Eric Botard
* @since 3.0
*/
public class BeanFactoryAwareFunctionRegistry extends SimpleFunctionRegistry implements ApplicationContextAware, InitializingBean {
private GenericApplicationContext applicationContext;
private ConfigurableApplicationContext applicationContext;
public BeanFactoryAwareFunctionRegistry(ConversionService conversionService,
@Nullable CompositeMessageConverter messageConverter) {
super(conversionService, messageConverter);
public BeanFactoryAwareFunctionRegistry(ConversionService conversionService, CompositeMessageConverter messageConverter, JsonMapper jsonMapper) {
super(conversionService, messageConverter, jsonMapper);
}
@Override
public void afterPropertiesSet() throws Exception {
String userDefinition = this.applicationContext.getEnvironment().getProperty("spring.cloud.function.definition");
init(userDefinition);
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = (GenericApplicationContext) applicationContext;
}
/*
* Basically gives an approximation only including function registrations and SFC.
* Excludes possible POJOs that can be treated as functions
*/
@Override
public int size() {
return this.applicationContext.getBeanNamesForType(Supplier.class).length +
this.applicationContext.getBeanNamesForType(Function.class).length +
this.applicationContext.getBeanNamesForType(Consumer.class).length;
this.applicationContext.getBeanNamesForType(Consumer.class).length +
super.size();
}
/*
* Doesn't account for POJO so we really don't know until it's been lookedup
*/
@Override
public Set<String> getNames(Class<?> type) {
Set<String> registeredNames = super.getNames(type);
@@ -101,172 +90,162 @@ public class BeanFactoryAwareFunctionRegistry extends SimpleFunctionRegistry imp
return registeredNames;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
public <T> T lookup(Class<?> type, String functionDefinition, String... expectedOutputMimeTypes) {
functionDefinition = StringUtils.hasText(functionDefinition)
? functionDefinition
: this.applicationContext.getEnvironment().getProperty(FunctionProperties.FUNCTION_DEFINITION, "");
functionDefinition = this.normalizeFunctionDefinition(functionDefinition);
if (!StringUtils.hasText(functionDefinition)) {
logger.debug("Can't determine default function name");
return null;
}
FunctionInvocationWrapper function = this.doLookup(type, functionDefinition, expectedOutputMimeTypes);
if (function == null) {
Set<String> functionRegistratioinNames = super.getNames(null);
String[] functionNames = StringUtils.delimitedListToStringArray(functionDefinition.replaceAll(",", "|").trim(), "|");
for (String functionName : functionNames) {
if (functionRegistratioinNames.contains(functionName)) {
logger.info("Skipping function '" + functionName + "' since it is already present");
}
else {
Object functionCandidate = this.discoverFunctionInBeanFactory(functionName);
if (functionCandidate != null) {
Type functionType = null;
FunctionRegistration functionRegistration = null;
if (functionCandidate instanceof FunctionRegistration) {
functionRegistration = (FunctionRegistration) functionCandidate;
}
else if (this.isFunctionPojo(functionCandidate, functionName)) {
Method functionalMethod = FunctionTypeUtils.discoverFunctionalMethod(functionCandidate.getClass());
functionCandidate = this.proxyTarget(functionCandidate, functionalMethod);
functionType = FunctionTypeUtils.fromFunctionMethod(functionalMethod);
}
else if (this.isSpecialFunctionRegistration(functionNames, functionName)) {
functionRegistration = this.applicationContext
.getBean(functionName + FunctionRegistration.REGISTRATION_NAME_SUFFIX, FunctionRegistration.class);
}
else {
functionType = this.discoverFunctionType(functionCandidate, functionName);
}
if (functionRegistration == null) {
functionRegistration = new FunctionRegistration(functionCandidate, functionName).type(functionType);
}
this.register(functionRegistration);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Function '" + functionName + "' is not available in FunctionCatalog or BeanFactory");
}
}
}
}
function = super.doLookup(type, functionDefinition, expectedOutputMimeTypes);
}
return (T) function;
}
@Override
Object locateFunction(String name) {
Object function = super.locateFunction(name);
if (function == null) {
private Object discoverFunctionInBeanFactory(String functionName) {
Object functionCandidate = null;
if (this.applicationContext.containsBean(functionName)) {
functionCandidate = this.applicationContext.getBean(functionName);
}
else {
try {
function = BeanFactoryAnnotationUtils.qualifiedBeanOfType(this.applicationContext.getBeanFactory(), Object.class, name);
functionCandidate = BeanFactoryAnnotationUtils.qualifiedBeanOfType(this.applicationContext.getBeanFactory(), Object.class, functionName);
}
catch (Exception e) {
// ignore
// ignore since there is no safe isAvailable-kind of method
}
}
if (function == null && this.applicationContext.containsBean(name)) {
function = this.applicationContext.getBean(name);
}
if (function != null && this.notFunction(function.getClass())
&& this.applicationContext
.containsBean(name + FunctionRegistration.REGISTRATION_NAME_SUFFIX)) { // e.g., Kotlin lambdas
function = this.applicationContext
.getBean(name + FunctionRegistration.REGISTRATION_NAME_SUFFIX, FunctionRegistration.class);
}
return function;
return functionCandidate;
}
@Override
Type discoverFunctionType(Object function, String... names) {
protected boolean containsFunction(String functionName) {
return super.containsFunction(functionName) ? true : this.applicationContext.containsBean(functionName);
}
@SuppressWarnings("rawtypes")
Type discoverFunctionType(Object function, String functionName) {
if (function instanceof RoutingFunction) {
return FunctionType.of(FunctionContextUtils.findType(applicationContext.getBeanFactory(), names)).getType();
return FunctionType.of(FunctionContextUtils.findType(applicationContext.getBeanFactory(), functionName)).getType();
}
else if (function instanceof FunctionRegistration) {
return ((FunctionRegistration) function).getType().getType();
}
boolean beanDefinitionExists = false;
for (int i = 0; i < names.length && !beanDefinitionExists; i++) {
beanDefinitionExists = this.applicationContext.getBeanFactory().containsBeanDefinition(names[i]);
if (this.applicationContext.containsBean("&" + names[i])) {
Class<?> objectType = this.applicationContext.getBean("&" + names[i], FactoryBean.class)
.getObjectType();
return FunctionTypeUtils.discoverFunctionTypeFromClass(objectType);
}
}
if (!beanDefinitionExists) {
logger.info("BeanDefinition for function name(s) '" + Arrays.asList(names) +
"' can not be located. FunctionType will be based on " + function.getClass());
String functionBeanDefinitionName = this.discoverDefinitionName(functionName);
beanDefinitionExists = this.applicationContext.getBeanFactory().containsBeanDefinition(functionBeanDefinitionName);
if (this.applicationContext.containsBean("&" + functionName)) {
Class<?> objectType = this.applicationContext.getBean("&" + functionName, FactoryBean.class)
.getObjectType();
return FunctionTypeUtils.discoverFunctionTypeFromClass(objectType);
}
// if (!beanDefinitionExists) {
// logger.info("BeanDefinition for function name(s) '" + Arrays.asList(names) +
// "' can not be located. FunctionType will be based on " + function.getClass());
// }
Type type = FunctionTypeUtils.discoverFunctionTypeFromClass(function.getClass());
if (beanDefinitionExists) {
Type t = FunctionTypeUtils.getImmediateGenericType(type, 0);
if (t == null || t == Object.class) {
type = FunctionType.of(FunctionContextUtils.findType(this.applicationContext.getBeanFactory(), names)).getType();
type = FunctionType.of(FunctionContextUtils.findType(this.applicationContext.getBeanFactory(), functionBeanDefinitionName)).getType();
}
}
return type;
}
@Override
String discoverDefaultDefinitionIfNecessary(String definition) {
if (StringUtils.isEmpty(definition) || definition.endsWith("|")) {
// the underscores are for Kotlin function registrations (see KotlinLambdaToFunctionAutoConfiguration)
String[] functionNames = Stream.of(this.applicationContext.getBeanNamesForType(Function.class))
.filter(n -> !n.endsWith(FunctionRegistration.REGISTRATION_NAME_SUFFIX) && !n
.equals(RoutingFunction.FUNCTION_NAME)).toArray(String[]::new);
String[] consumerNames = Stream.of(this.applicationContext.getBeanNamesForType(Consumer.class))
.filter(n -> !n.endsWith(FunctionRegistration.REGISTRATION_NAME_SUFFIX) && !n
.equals(RoutingFunction.FUNCTION_NAME)).toArray(String[]::new);
String[] supplierNames = Stream.of(this.applicationContext.getBeanNamesForType(Supplier.class))
.filter(n -> !n.endsWith(FunctionRegistration.REGISTRATION_NAME_SUFFIX) && !n
.equals(RoutingFunction.FUNCTION_NAME)).toArray(String[]::new);
/*
* we may need to add BiFunction and BiConsumer at some point
*/
List<String> names = Stream
.concat(Stream.of(functionNames), Stream.concat(Stream.of(consumerNames), Stream.of(supplierNames)))
.collect(Collectors.toList());
if (definition.endsWith("|")) {
Set<String> fNames = this.getNames(null);
definition = this.determinImpliedDefinition(fNames, definition);
}
else if (!ObjectUtils.isEmpty(names)) {
if (names.size() > 1) {
logger.warn("Found more than one function bean in BeanFactory: " + names
+ ". If you did not intend to use functions, ignore this message. However, if you did "
+ "intend to use functions in the context of spring-cloud-function, consider "
+ "providing 'spring.cloud.function.definition' property pointing to a function bean(s) "
+ "you intend to use. For example, 'spring.cloud.function.definition=myFunction'");
return null;
}
definition = names.get(0);
}
else {
definition = this.discoverDefaultDefinitionFromRegistration();
}
if (StringUtils.hasText(definition) && this.applicationContext.containsBean(definition)) {
Type functionType = discoverFunctionType(this.applicationContext.getBean(definition), definition);
if (!FunctionTypeUtils.isSupplier(functionType) && !FunctionTypeUtils
.isFunction(functionType) && !FunctionTypeUtils.isConsumer(functionType)) {
logger.debug("Discovered functional instance of bean '" + definition + "' as a default function, however its "
+ "function argument types can not be determined. Discarding.");
definition = null;
}
private String discoverDefinitionName(String functionDefinition) {
String[] aliases = this.applicationContext.getAliases(functionDefinition);
for (String alias : aliases) {
if (this.applicationContext.getBeanFactory().containsBeanDefinition(alias)) {
return alias;
}
}
if (!StringUtils.hasText(definition)) {
String[] functionRegistrationNames = Stream.of(applicationContext.getBeanNamesForType(FunctionRegistration.class))
.filter(n -> !n.endsWith(FunctionRegistration.REGISTRATION_NAME_SUFFIX) && !n
.equals(RoutingFunction.FUNCTION_NAME)).toArray(String[]::new);
if (functionRegistrationNames != null) {
if (functionRegistrationNames.length == 1) {
definition = functionRegistrationNames[0];
}
else {
logger.debug("Found more than one function registration bean in BeanFactory: " + functionRegistrationNames
+ ". If you did not intend to use functions, ignore this message. However, if you did "
+ "intend to use functions in the context of spring-cloud-function, consider "
+ "providing 'spring.cloud.function.definition' property pointing to a function bean(s) "
+ "you intend to use. For example, 'spring.cloud.function.definition=myFunction'");
}
return functionDefinition;
}
private boolean isFunctionPojo(Object functionCandidate, String functionName) {
return !functionCandidate.getClass().isSynthetic()
&& !(functionCandidate instanceof Supplier)
&& !(functionCandidate instanceof Function)
&& !(functionCandidate instanceof Consumer)
&& !this.applicationContext.containsBean(functionName + FunctionRegistration.REGISTRATION_NAME_SUFFIX);
}
/**
* At the moment 'special function registration' simply implies that a bean under the provided functionName
* may have already been wrapped and registered as FunuctionRegistration with BeanFactory under the name of
* the function suffixed with {@link FunctionRegistration#REGISTRATION_NAME_SUFFIX}
* (e.g., 'myKotlinFunction_registration').
* <br><br>
* At the moment only Kotlin module does this
*
* @param functionCandidate candidate for FunctionInvocationWrapper instance
* @param functionName the name of the function
* @return true if this function candidate qualifies
*/
private boolean isSpecialFunctionRegistration(Object functionCandidate, String functionName) {
return this.applicationContext.containsBean(functionName + FunctionRegistration.REGISTRATION_NAME_SUFFIX);
}
private Object proxyTarget(Object targetFunction, Method actualMethodToCall) {
ProxyFactory pf = new ProxyFactory(targetFunction);
pf.setProxyTargetClass(true);
pf.setInterfaces(Function.class);
pf.addAdvice(new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
return actualMethodToCall.invoke(invocation.getThis(), invocation.getArguments());
}
}
return definition;
}
@Override
Type discoverFunctionTypeByName(String name) {
return FunctionContextUtils.findType(applicationContext.getBeanFactory(), name);
}
@Override
Collection<String> getAliases(String key) {
Collection<String> names = new LinkedHashSet<>();
String value = getQualifier(key);
if (value.equals(key) && this.applicationContext != null) {
names.addAll(Arrays.asList(this.applicationContext.getBeanFactory().getAliases(key)));
}
names.add(value);
return names;
}
private boolean notFunction(Class<?> functionClass) {
return !Function.class.isAssignableFrom(functionClass)
&& !Supplier.class.isAssignableFrom(functionClass)
&& !Consumer.class.isAssignableFrom(functionClass);
}
private String getQualifier(String key) {
if (this.applicationContext != null && this.applicationContext.getBeanFactory().containsBeanDefinition(key)) {
BeanDefinition beanDefinition = this.applicationContext.getBeanFactory().getBeanDefinition(key);
Object source = beanDefinition.getSource();
if (source instanceof StandardMethodMetadata) {
StandardMethodMetadata metadata = (StandardMethodMetadata) source;
Qualifier qualifier = AnnotatedElementUtils.findMergedAnnotation(metadata.getIntrospectedMethod(),
Qualifier.class);
if (qualifier != null && qualifier.value().length() > 0) {
return qualifier.value();
}
}
}
return key;
});
return pf.getProxy();
}
}

View File

@@ -16,11 +16,17 @@
package org.springframework.cloud.function.context.catalog;
import java.util.Collections;
import java.util.Set;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import net.jodah.typetools.TypeResolver;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.cloud.function.context.FunctionRegistration;
import org.springframework.cloud.function.context.config.RoutingFunction;
import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper;
/**
* @author Dave Syer
@@ -31,42 +37,84 @@ public interface FunctionInspector {
FunctionRegistration<?> getRegistration(Object function);
default boolean isMessage(Object function) {
FunctionRegistration<?> registration = getRegistration(function);
if (registration != null && registration.getTarget() instanceof RoutingFunction) {
return true;
if (function == null) {
return false;
}
return registration == null ? false : registration.getType().isMessage();
return ((FunctionInvocationWrapper) function).isInputTypeMessage();
}
default Class<?> getInputType(Object function) {
FunctionRegistration<?> registration = getRegistration(function);
return registration == null ? Object.class
: registration.getType().getInputType();
if (function == null) {
return Object.class;
}
Type type = ((FunctionInvocationWrapper) function).getInputType();
Class<?> inputType;
if (type instanceof ParameterizedType) {
if (function != null && (((FunctionInvocationWrapper) function).isInputTypePublisher() || ((FunctionInvocationWrapper) function).isInputTypeMessage())) {
inputType = TypeResolver.resolveRawClass(FunctionTypeUtils.getImmediateGenericType(type, 0), null);
}
else {
inputType = ((FunctionInvocationWrapper) function).getRawInputType();
}
}
else {
inputType = type instanceof TypeVariable || type instanceof WildcardType ? Object.class : (Class<?>) type;
}
return inputType;
}
default Class<?> getOutputType(Object function) {
FunctionRegistration<?> registration = getRegistration(function);
return registration == null ? Object.class
: registration.getType().getOutputType();
if (function == null) {
return Object.class;
}
Type type = ((FunctionInvocationWrapper) function).getOutputType();
Class<?> outputType;
if (type instanceof ParameterizedType) {
if (function != null && ((FunctionInvocationWrapper) function).isOutputTypePublisher() || ((FunctionInvocationWrapper) function).isOutputTypeMessage()) {
outputType = TypeResolver.resolveRawClass(FunctionTypeUtils.getImmediateGenericType(type, 0), null);
}
else {
outputType = ((FunctionInvocationWrapper) function).getRawOutputType();
}
}
else {
outputType = type instanceof TypeVariable || type instanceof WildcardType ? Object.class : (Class<?>) type;
}
return outputType;
}
default Class<?> getInputWrapper(Object function) {
FunctionRegistration<?> registration = getRegistration(function);
return registration == null ? Object.class
: registration.getType().getInputWrapper();
Class c = function == null ? Object.class : TypeResolver.resolveRawClass(((FunctionInvocationWrapper) function).getInputType(), null);
if (Flux.class.isAssignableFrom(c)) {
return c;
}
else if (Mono.class.isAssignableFrom(c)) {
return c;
}
else {
return this.getInputType(function);
}
}
default Class<?> getOutputWrapper(Object function) {
FunctionRegistration<?> registration = getRegistration(function);
return registration == null ? Object.class
: registration.getType().getOutputWrapper();
Class c = function == null ? Object.class : TypeResolver.resolveRawClass(((FunctionInvocationWrapper) function).getOutputType(), null);
if (Flux.class.isAssignableFrom(c)) {
return c;
}
else if (Mono.class.isAssignableFrom(c)) {
return c;
}
else {
return this.getOutputType(function);
}
}
default String getName(Object function) {
FunctionRegistration<?> registration = getRegistration(function);
Set<String> names = registration == null ? Collections.emptySet()
: registration.getNames();
return names.isEmpty() ? null : names.iterator().next();
if (function == null) {
return null;
}
return ((FunctionInvocationWrapper) function).getFunctionDefinition();
}
}

View File

@@ -129,7 +129,9 @@ public final class FunctionTypeUtils {
public static Type discoverFunctionTypeFromFunctionalObject(Object functionalObject) {
if (functionalObject instanceof FunctionInvocationWrapper) {
return ((FunctionInvocationWrapper) functionalObject).getFunctionType();
// return ((FunctionInvocationWrapper) functionalObject).getFunctionType();
// return null;
throw new UnsupportedOperationException("Code is temporarily comented");
}
else {
return discoverFunctionTypeFromClass(functionalObject.getClass());
@@ -235,9 +237,9 @@ public final class FunctionTypeUtils {
Type inputType = isSupplier(functionType) ? null : Object.class;
if ((isFunction(functionType) || isConsumer(functionType)) && functionType instanceof ParameterizedType) {
inputType = ((ParameterizedType) functionType).getActualTypeArguments()[0];
inputType = isMulti(inputType)
? ((ParameterizedType) inputType).getActualTypeArguments()[index]
: inputType;
// inputType = isMulti(inputType)
// ? ((ParameterizedType) inputType).getActualTypeArguments()[index]
// : inputType;
}
return inputType;
@@ -245,15 +247,34 @@ public final class FunctionTypeUtils {
public static Type getOutputType(Type functionType, int index) {
assertSupportedTypes(functionType);
Type outputType = isConsumer(functionType) ? null : Object.class;
if ((isFunction(functionType) || isSupplier(functionType)) && functionType instanceof ParameterizedType) {
outputType = ((ParameterizedType) functionType).getActualTypeArguments()[isFunction(functionType) ? 1 : 0];
outputType = isMulti(outputType)
? ((ParameterizedType) outputType).getActualTypeArguments()[index]
: outputType;
if (isFunction(functionType)) {
if (functionType instanceof ParameterizedType) {
return ((ParameterizedType) functionType).getActualTypeArguments()[1];
}
else {
return Object.class;
}
}
return outputType;
else if (isSupplier(functionType)) {
if (functionType instanceof ParameterizedType) {
return ((ParameterizedType) functionType).getActualTypeArguments()[0];
}
else {
return Object.class;
}
}
else {
return null;
}
// Type outputType = isConsumer(functionType) ? null : Object.class;
// if ((isFunction(functionType) || isSupplier(functionType)) && functionType instanceof ParameterizedType) {
// outputType = ((ParameterizedType) functionType).getActualTypeArguments()[isFunction(functionType) ? 1 : 0];
// outputType = isMulti(outputType)
// ? ((ParameterizedType) outputType).getActualTypeArguments()[index]
// : outputType;
// }
//
// return outputType;
}
public static Type getImmediateGenericType(Type type, int index) {
@@ -284,6 +305,9 @@ public final class FunctionTypeUtils {
if (isPublisher(type)) {
type = getImmediateGenericType(type, 0);
}
if (type instanceof ParameterizedType && !type.getTypeName().startsWith("org.springframework.messaging.Message")) {
type = getImmediateGenericType(type, 0);
}
return type.getTypeName().startsWith("org.springframework.messaging.Message");
}
@@ -358,13 +382,15 @@ public final class FunctionTypeUtils {
|| Consumer.class.isAssignableFrom(candidateType);
}
public static boolean isMultipleInputArguments(Type functionType) {
boolean multipleInputs = false;
if (functionType instanceof ParameterizedType && !isSupplier(functionType)) {
Type inputType = ((ParameterizedType) functionType).getActualTypeArguments()[0];
multipleInputs = isMulti(inputType);
public static boolean isMultipleArgumentType(Type type) {
if (type != null) {
if (TypeResolver.resolveRawClass(type, null).isArray()) {
return false;
}
Class<?> clazz = TypeResolver.resolveRawClass(TypeResolver.reify(type), null);
return clazz.getName().startsWith("reactor.util.function.Tuple");
}
return multipleInputs;
return false;
}
public static boolean isMultipleArgumentsHolder(Object argument) {

View File

@@ -1,53 +0,0 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.function.context.catalog;
import java.util.Collections;
import java.util.Set;
import org.springframework.cloud.function.context.FunctionRegistration;
import org.springframework.cloud.function.context.FunctionType;
import org.springframework.util.Assert;
/**
* @author Dave Syer
* @author Mark Fisher
* @author Oleg Zhurakousky
*
* @deprecated since 3.1. End-of-life. Not used by the framework anymore in favor of SimpleFunctionRegistry
*/
@Deprecated
public class InMemoryFunctionCatalog extends AbstractComposableFunctionRegistry {
public InMemoryFunctionCatalog() {
this(Collections.emptySet());
}
public InMemoryFunctionCatalog(Set<FunctionRegistration<?>> registrations) {
Assert.notNull(registrations, "'registrations' must not be null");
registrations.stream().forEach(reg -> register(reg));
}
@Override
protected FunctionType findType(FunctionRegistration<?> functionRegistration, String name) {
FunctionType functionType = super.findType(functionRegistration, name);
if (functionType == null) {
functionType = new FunctionType(functionRegistration.getTarget().getClass());
}
return functionType;
}
}

View File

@@ -34,7 +34,6 @@ import org.springframework.cloud.function.context.FunctionCatalog;
import org.springframework.cloud.function.context.FunctionProperties;
import org.springframework.cloud.function.context.FunctionRegistry;
import org.springframework.cloud.function.context.catalog.BeanFactoryAwareFunctionRegistry;
import org.springframework.cloud.function.context.catalog.FunctionInspector;
import org.springframework.cloud.function.json.GsonMapper;
import org.springframework.cloud.function.json.JacksonMapper;
import org.springframework.cloud.function.json.JsonMapper;
@@ -47,7 +46,6 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.messaging.converter.AbstractMessageConverter;
import org.springframework.messaging.converter.ByteArrayMessageConverter;
import org.springframework.messaging.converter.CompositeMessageConverter;
import org.springframework.messaging.converter.MessageConverter;
@@ -95,26 +93,30 @@ public class ContextFunctionCatalogAutoConfiguration {
mcList = mcList.stream()
.filter(c -> isConverterEligible(c))
.map(converter -> {
return converter instanceof AbstractMessageConverter
? NegotiatingMessageConverterWrapper.wrap((AbstractMessageConverter) converter)
: converter;
})
// .map(converter -> {
// return converter instanceof AbstractMessageConverter
// ? NegotiatingMessageConverterWrapper.wrap((AbstractMessageConverter) converter)
// : converter;
// })
.collect(Collectors.toList());
mcList.add(NegotiatingMessageConverterWrapper.wrap(new JsonMessageConverter(jsonMapper)));
mcList.add(NegotiatingMessageConverterWrapper.wrap(new ByteArrayMessageConverter()));
mcList.add(NegotiatingMessageConverterWrapper.wrap(new StringMessageConverter()));
// mcList.add(NegotiatingMessageConverterWrapper.wrap(new JsonMessageConverter(jsonMapper)));
// mcList.add(NegotiatingMessageConverterWrapper.wrap(new ByteArrayMessageConverter()));
// mcList.add(NegotiatingMessageConverterWrapper.wrap(new StringMessageConverter()));
mcList.add(new JsonMessageConverter(jsonMapper));
mcList.add(new ByteArrayMessageConverter());
mcList.add(new StringMessageConverter());
if (!CollectionUtils.isEmpty(mcList)) {
messageConverter = new CompositeMessageConverter(mcList);
messageConverter = new SmartCompositeMessageConverter(mcList);
}
return new BeanFactoryAwareFunctionRegistry(conversionService, messageConverter);
return new BeanFactoryAwareFunctionRegistry(conversionService, messageConverter, jsonMapper);
}
@Bean(RoutingFunction.FUNCTION_NAME)
RoutingFunction functionRouter(FunctionCatalog functionCatalog, FunctionInspector functionInspector, FunctionProperties functionProperties) {
return new RoutingFunction(functionCatalog, functionInspector, functionProperties);
RoutingFunction functionRouter(FunctionCatalog functionCatalog, FunctionProperties functionProperties) {
return new RoutingFunction(functionCatalog, functionProperties);
}
private boolean isConverterEligible(Object messageConverter) {

View File

@@ -169,13 +169,16 @@ public class ContextFunctionCatalogInitializer implements ApplicationContextInit
List<MessageConverter> messageConverters = new ArrayList<>();
JsonMapper jsonMapper = this.context.getBean(JsonMapper.class);
messageConverters.add(NegotiatingMessageConverterWrapper.wrap(new JsonMessageConverter(jsonMapper)));
messageConverters.add(NegotiatingMessageConverterWrapper.wrap(new ByteArrayMessageConverter()));
messageConverters.add(NegotiatingMessageConverterWrapper.wrap(new StringMessageConverter()));
// messageConverters.add(NegotiatingMessageConverterWrapper.wrap(new JsonMessageConverter(jsonMapper)));
// messageConverters.add(NegotiatingMessageConverterWrapper.wrap(new ByteArrayMessageConverter()));
// messageConverters.add(NegotiatingMessageConverterWrapper.wrap(new StringMessageConverter()));
messageConverters.add(new JsonMessageConverter(jsonMapper));
messageConverters.add(new ByteArrayMessageConverter());
messageConverters.add(new StringMessageConverter());
CompositeMessageConverter messageConverter = new CompositeMessageConverter(messageConverters);
ConversionService conversionService = new DefaultConversionService();
return new SimpleFunctionRegistry(conversionService, messageConverter);
return new SimpleFunctionRegistry(conversionService, messageConverter, this.context.getBean(JsonMapper.class));
});
this.context.registerBean(FunctionRegistrationPostProcessor.class,
() -> new FunctionRegistrationPostProcessor(this.context.getAutowireCapableBeanFactory()

View File

@@ -1,146 +0,0 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.function.context.config;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.stream.Collectors;
import org.springframework.cloud.function.context.catalog.FunctionTypeUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.AbstractMessageConverter;
import org.springframework.messaging.converter.SmartMessageConverter;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MimeType;
/**
* A {@link org.springframework.messaging.converter.AbstractMessageConverter} wrapper that supports the concept of wildcard
* negotiation when <em>producing</em> messages. To that effect, messages should contain an "accept" header, that may
* contain a wildcard type (such as {@code text/*}, which may be tested against every
* {@link AbstractMessageConverter#getSupportedMimeTypes() supported mime type} of the delegate MessageConverter.
*
* @author Eric Bottard
* @author Oleg Zhurakousky
*/
public final class NegotiatingMessageConverterWrapper implements SmartMessageConverter {
/**
* The Message Header key that may contain the list of (possibly wildcard) MimeTypes to convert to.
*/
public static final String ACCEPT = "accept";
private final AbstractMessageConverter delegate;
private NegotiatingMessageConverterWrapper(AbstractMessageConverter delegate) {
this.delegate = delegate;
}
public static NegotiatingMessageConverterWrapper wrap(AbstractMessageConverter delegate) {
return new NegotiatingMessageConverterWrapper(delegate);
}
@Override
public Object fromMessage(Message<?> message, Class<?> targetClass) {
return fromMessage(message, targetClass, null);
}
private boolean isJsonContentType(Message<?> message) {
Object ct = message.getHeaders().get(MessageHeaders.CONTENT_TYPE);
if (ct != null) {
ct = ct.toString();
return ((String) ct).startsWith("application/json");
}
return false;
}
@Override
public Object fromMessage(Message<?> message, Class<?> targetClass, Object conversionHint) {
if (!this.isJsonContentType(message) && message.getPayload() instanceof Collection) {
Collection<?> collection = ((Collection<?>) message.getPayload()).stream()
.map(value -> {
try {
Message<?> m = new Message<Object>() {
@Override
public Object getPayload() {
return value;
}
@Override
public MessageHeaders getHeaders() {
return message.getHeaders();
}
};
if (conversionHint != null && conversionHint instanceof ParameterizedType) {
Type tClass = FunctionTypeUtils.getImmediateGenericType((ParameterizedType) conversionHint, 0);
if (byte[].class.isAssignableFrom((Class<?>) tClass)) {
return message;
}
return delegate.fromMessage(m, (Class<?>) tClass);
}
return delegate.fromMessage(m, targetClass, conversionHint);
}
catch (Exception e) {
e.printStackTrace();
//logger.error("Failed to convert payload " + value, e);
}
return null;
}).filter(v -> v != null).collect(Collectors.toList());
return CollectionUtils.isEmpty(collection) ? null : collection;
}
return delegate.fromMessage(message, targetClass, conversionHint);
}
@Override
public Message<?> toMessage(Object payload, MessageHeaders headers, Object conversionHint) {
MimeType accepted = headers.get(ACCEPT, MimeType.class);
MessageHeaderAccessor accessor = new MessageHeaderAccessor();
accessor.copyHeaders(headers);
accessor.removeHeader(ACCEPT);
// Fall back to (concrete) 'contentType' header if 'accept' is not present.
// MimeType.includes() below should then amount to equality.
if (accepted == null) {
accepted = headers.get(MessageHeaders.CONTENT_TYPE, MimeType.class);
}
if (accepted != null) {
Message<?> result = null;
for (MimeType supportedConcreteType : delegate.getSupportedMimeTypes()) {
if (supportedConcreteType.isWildcardType() || supportedConcreteType.isWildcardSubtype()) {
result = delegate.toMessage(payload, accessor.toMessageHeaders(), conversionHint);
}
if (result == null && accepted.includes(supportedConcreteType)) {
// Note the use of setHeader() which will set the value even if already present.
accessor.setHeader(MessageHeaders.CONTENT_TYPE, supportedConcreteType);
result = delegate.toMessage(payload, accessor.toMessageHeaders(), conversionHint);
}
if (result != null) {
return result;
}
}
}
return null;
}
@Override
public Message<?> toMessage(Object payload, MessageHeaders headers) {
return toMessage(payload, headers, null);
}
}

View File

@@ -16,7 +16,6 @@
package org.springframework.cloud.function.context.config;
import java.lang.reflect.Type;
import java.util.function.Function;
import org.apache.commons.logging.Log;
@@ -27,8 +26,7 @@ import reactor.core.publisher.Mono;
import org.springframework.cloud.function.context.FunctionCatalog;
import org.springframework.cloud.function.context.FunctionProperties;
import org.springframework.cloud.function.context.catalog.FunctionInspector;
import org.springframework.cloud.function.context.catalog.FunctionTypeUtils;
import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper;
import org.springframework.context.expression.MapAccessor;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@@ -46,6 +44,7 @@ import org.springframework.util.StringUtils;
* @since 2.1
*
*/
//TODO - perhaps change to Function<Message<Object>, Message<Object>>
public class RoutingFunction implements Function<Object, Object> {
/**
@@ -63,12 +62,9 @@ public class RoutingFunction implements Function<Object, Object> {
private final FunctionProperties functionProperties;
private final FunctionInspector functionInspector;
public RoutingFunction(FunctionCatalog functionCatalog, FunctionInspector functionInspector, FunctionProperties functionProperties) {
public RoutingFunction(FunctionCatalog functionCatalog, FunctionProperties functionProperties) {
this.functionCatalog = functionCatalog;
this.functionProperties = functionProperties;
this.functionInspector = functionInspector;
this.evalContext.addPropertyAccessor(new MapAccessor());
}
@@ -86,22 +82,19 @@ public class RoutingFunction implements Function<Object, Object> {
* If NOT
* - Fail
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private Object route(Object input, boolean originalInputIsPublisher) {
Function function;
FunctionInvocationWrapper function;
if (input instanceof Message) {
Message<?> message = (Message<?>) input;
if (StringUtils.hasText((String) message.getHeaders().get("spring.cloud.function.definition"))) {
function = functionFromDefinition((String) message.getHeaders().get("spring.cloud.function.definition"));
Type functionType = functionInspector.getRegistration(function).getType().getType();
if (FunctionTypeUtils.isReactive(FunctionTypeUtils.getInputType(functionType, 0))) {
if (function.isInputTypePublisher()) {
this.assertOriginalInputIsNotPublisher(originalInputIsPublisher);
}
}
else if (StringUtils.hasText((String) message.getHeaders().get("spring.cloud.function.routing-expression"))) {
function = this.functionFromExpression((String) message.getHeaders().get("spring.cloud.function.routing-expression"), message);
Type functionType = functionInspector.getRegistration(function).getType().getType();
if (FunctionTypeUtils.isReactive(FunctionTypeUtils.getInputType(functionType, 0))) {
if (function.isInputTypePublisher()) {
this.assertOriginalInputIsNotPublisher(originalInputIsPublisher);
}
}
@@ -156,9 +149,8 @@ public class RoutingFunction implements Function<Object, Object> {
+ "spring.cloud.function.routing-expression' as application properties.");
}
@SuppressWarnings("rawtypes")
private Function functionFromDefinition(String definition) {
Function function = functionCatalog.lookup(definition);
private FunctionInvocationWrapper functionFromDefinition(String definition) {
FunctionInvocationWrapper function = functionCatalog.lookup(definition);
Assert.notNull(function, "Failed to lookup function to route based on the value of 'spring.cloud.function.definition' property '"
+ functionProperties.getDefinition() + "'");
if (logger.isInfoEnabled()) {
@@ -167,12 +159,11 @@ public class RoutingFunction implements Function<Object, Object> {
return function;
}
@SuppressWarnings("rawtypes")
private Function functionFromExpression(String routingExpression, Object input) {
private FunctionInvocationWrapper functionFromExpression(String routingExpression, Object input) {
Expression expression = spelParser.parseExpression(routingExpression);
String functionName = expression.getValue(this.evalContext, input, String.class);
Assert.hasText(functionName, "Failed to resolve function name based on routing expression '" + functionProperties.getRoutingExpression() + "'");
Function function = functionCatalog.lookup(functionName);
FunctionInvocationWrapper function = functionCatalog.lookup(functionName);
Assert.notNull(function, "Failed to lookup function to route to based on the expression '"
+ functionProperties.getRoutingExpression() + "' whcih resolved to '" + functionName + "' function name.");
if (logger.isInfoEnabled()) {

View File

@@ -0,0 +1,127 @@
/*
* Copyright 2020-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.function.context.config;
import java.util.Collection;
import java.util.List;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.AbstractMessageConverter;
import org.springframework.messaging.converter.CompositeMessageConverter;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.converter.SmartMessageConverter;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MimeType;
/**
*
* @author Oleg Zhurakousky
*
*/
public class SmartCompositeMessageConverter extends CompositeMessageConverter {
public SmartCompositeMessageConverter(Collection<MessageConverter> converters) {
super(converters);
}
@Override
@Nullable
public Object fromMessage(Message<?> message, Class<?> targetClass) {
for (MessageConverter converter : getConverters()) {
Object result = converter.fromMessage(message, targetClass);
if (result != null) {
return result;
}
}
return null;
}
@Override
@Nullable
public Object fromMessage(Message<?> message, Class<?> targetClass, @Nullable Object conversionHint) {
for (MessageConverter converter : getConverters()) {
Object result = (converter instanceof SmartMessageConverter ?
((SmartMessageConverter) converter).fromMessage(message, targetClass, conversionHint) :
converter.fromMessage(message, targetClass));
if (result != null) {
return result;
}
}
return null;
}
@Override
@Nullable
public Message<?> toMessage(Object payload, @Nullable MessageHeaders headers) {
for (MessageConverter converter : getConverters()) {
MessageHeaderAccessor accessor = new MessageHeaderAccessor();
accessor.copyHeaders(headers);
if (this.isNotConcreteContentType(accessor, converter)) {
List<MimeType> supportedMimeTypes = ((AbstractMessageConverter) converter).getSupportedMimeTypes();
for (MimeType supportedMimeType : supportedMimeTypes) {
accessor.setHeader(MessageHeaders.CONTENT_TYPE, supportedMimeType);
Message<?> result = converter.toMessage(payload, accessor.getMessageHeaders());
if (result != null) {
return result;
}
}
}
else {
Message<?> result = converter.toMessage(payload, headers);
if (result != null) {
return result;
}
}
}
return null;
}
@Override
@Nullable
public Message<?> toMessage(Object payload, @Nullable MessageHeaders headers, @Nullable Object conversionHint) {
for (MessageConverter converter : getConverters()) {
MessageHeaderAccessor accessor = new MessageHeaderAccessor();
accessor.copyHeaders(headers);
if (this.isNotConcreteContentType(accessor, converter)) {
List<MimeType> supportedMimeTypes = ((AbstractMessageConverter) converter).getSupportedMimeTypes();
for (MimeType supportedMimeType : supportedMimeTypes) {
accessor.setHeader(MessageHeaders.CONTENT_TYPE, supportedMimeType);
Message<?> result = ((AbstractMessageConverter) converter).toMessage(payload, accessor.getMessageHeaders(), conversionHint);
if (result != null) {
return result;
}
}
}
else {
Message<?> result = ((AbstractMessageConverter) converter).toMessage(payload, headers, conversionHint);
if (result != null) {
return result;
}
}
}
return null;
}
private boolean isNotConcreteContentType(MessageHeaderAccessor accessor, MessageConverter converter) {
return !accessor.getContentType().isConcrete() && converter instanceof AbstractMessageConverter
&& !CollectionUtils.isEmpty(((AbstractMessageConverter) converter).getSupportedMimeTypes());
}
}