Refactor FunctionCatalog implementation
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user