Introduce better content-type negotiation when converting to messages.

This commit introduces a MessageConverter wrapper that supports a
wildcard-aware Accept header.
This commit is contained in:
Eric Bottard
2020-03-11 19:32:19 +01:00
committed by Oleg Zhurakousky
parent e71cf28c1a
commit 161ac0efae
3 changed files with 235 additions and 96 deletions

View File

@@ -90,7 +90,6 @@ import org.springframework.util.StringUtils;
* *
* @author Oleg Zhurakousky * @author Oleg Zhurakousky
* @author Eric Botard * @author Eric Botard
*
* @since 3.0 * @since 3.0
*/ */
public class BeanFactoryAwareFunctionRegistry public class BeanFactoryAwareFunctionRegistry
@@ -142,7 +141,8 @@ public class BeanFactoryAwareFunctionRegistry
if (!StringUtils.hasText(definition)) { if (!StringUtils.hasText(definition)) {
definition = this.applicationContext.getEnvironment().getProperty("spring.cloud.function.definition"); definition = this.applicationContext.getEnvironment().getProperty("spring.cloud.function.definition");
} }
Object function = this.proxyInvokerIfNecessary((FunctionInvocationWrapper) this.compose(null, definition, acceptedOutputTypes)); Object function = this
.proxyInvokerIfNecessary((FunctionInvocationWrapper) this.compose(null, definition, acceptedOutputTypes));
return (T) function; return (T) function;
} }
@@ -152,9 +152,12 @@ public class BeanFactoryAwareFunctionRegistry
Set<String> registeredNames = registrationsByFunction.values().stream().flatMap(reg -> reg.getNames().stream()) Set<String> registeredNames = registrationsByFunction.values().stream().flatMap(reg -> reg.getNames().stream())
.collect(Collectors.toSet()); .collect(Collectors.toSet());
if (type == null) { if (type == null) {
registeredNames.addAll(CollectionUtils.arrayToList(this.applicationContext.getBeanNamesForType(Function.class))); registeredNames
registeredNames.addAll(CollectionUtils.arrayToList(this.applicationContext.getBeanNamesForType(Supplier.class))); .addAll(CollectionUtils.arrayToList(this.applicationContext.getBeanNamesForType(Function.class)));
registeredNames.addAll(CollectionUtils.arrayToList(this.applicationContext.getBeanNamesForType(Consumer.class))); registeredNames
.addAll(CollectionUtils.arrayToList(this.applicationContext.getBeanNamesForType(Supplier.class)));
registeredNames
.addAll(CollectionUtils.arrayToList(this.applicationContext.getBeanNamesForType(Consumer.class)));
} }
else { else {
registeredNames.addAll(CollectionUtils.arrayToList(this.applicationContext.getBeanNamesForType(type))); registeredNames.addAll(CollectionUtils.arrayToList(this.applicationContext.getBeanNamesForType(type)));
@@ -194,8 +197,10 @@ public class BeanFactoryAwareFunctionRegistry
} }
if (function != null && this.notFunction(function.getClass()) if (function != null && this.notFunction(function.getClass())
&& this.applicationContext.containsBean(name + FunctionRegistration.REGISTRATION_NAME_SUFFIX)) { // e.g., Kotlin lambdas && this.applicationContext
function = this.applicationContext.getBean(name + FunctionRegistration.REGISTRATION_NAME_SUFFIX, FunctionRegistration.class); .containsBean(name + FunctionRegistration.REGISTRATION_NAME_SUFFIX)) { // e.g., Kotlin lambdas
function = this.applicationContext
.getBean(name + FunctionRegistration.REGISTRATION_NAME_SUFFIX, FunctionRegistration.class);
} }
return function; return function;
} }
@@ -211,7 +216,8 @@ public class BeanFactoryAwareFunctionRegistry
for (int i = 0; i < names.length && !beanDefinitionExists; i++) { for (int i = 0; i < names.length && !beanDefinitionExists; i++) {
beanDefinitionExists = this.applicationContext.getBeanFactory().containsBeanDefinition(names[i]); beanDefinitionExists = this.applicationContext.getBeanFactory().containsBeanDefinition(names[i]);
if (this.applicationContext.containsBean("&" + names[i])) { if (this.applicationContext.containsBean("&" + names[i])) {
Class<?> objectType = this.applicationContext.getBean("&" + names[i], FactoryBean.class).getObjectType(); Class<?> objectType = this.applicationContext.getBean("&" + names[i], FactoryBean.class)
.getObjectType();
return FunctionTypeUtils.discoverFunctionTypeFromClass(objectType); return FunctionTypeUtils.discoverFunctionTypeFromClass(objectType);
} }
} }
@@ -228,17 +234,21 @@ public class BeanFactoryAwareFunctionRegistry
if (StringUtils.isEmpty(definition)) { if (StringUtils.isEmpty(definition)) {
// the underscores are for Kotlin function registrations (see KotlinLambdaToFunctionAutoConfiguration) // the underscores are for Kotlin function registrations (see KotlinLambdaToFunctionAutoConfiguration)
String[] functionNames = Stream.of(this.applicationContext.getBeanNamesForType(Function.class)) 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); .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)) 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); .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)) 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); .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 * we may need to add BiFunction and BiConsumer at some point
*/ */
List<String> names = Stream List<String> names = Stream
.concat(Stream.of(functionNames), Stream.concat(Stream.of(consumerNames), Stream.of(supplierNames))).collect(Collectors.toList()); .concat(Stream.of(functionNames), Stream.concat(Stream.of(consumerNames), Stream.of(supplierNames)))
.collect(Collectors.toList());
if (!ObjectUtils.isEmpty(names)) { if (!ObjectUtils.isEmpty(names)) {
if (names.size() > 1) { if (names.size() > 1) {
@@ -253,15 +263,18 @@ public class BeanFactoryAwareFunctionRegistry
} }
else { else {
if (this.registrationsByName.size() > 0) { if (this.registrationsByName.size() > 0) {
Assert.isTrue(this.registrationsByName.size() == 1, "Found more then one function in local registry"); Assert
.isTrue(this.registrationsByName.size() == 1, "Found more then one function in local registry");
definition = this.registrationsByName.keySet().iterator().next(); definition = this.registrationsByName.keySet().iterator().next();
} }
} }
if (StringUtils.hasText(definition) && this.applicationContext.containsBean(definition)) { if (StringUtils.hasText(definition) && this.applicationContext.containsBean(definition)) {
Type functionType = discoverFunctionType(this.applicationContext.getBean(definition), definition); Type functionType = discoverFunctionType(this.applicationContext.getBean(definition), definition);
if (!FunctionTypeUtils.isSupplier(functionType) && !FunctionTypeUtils.isFunction(functionType) && !FunctionTypeUtils.isConsumer(functionType)) { if (!FunctionTypeUtils.isSupplier(functionType) && !FunctionTypeUtils
logger.info("Discovered functional instance of bean '" + definition + "' as a default function, however its " .isFunction(functionType) && !FunctionTypeUtils.isConsumer(functionType)) {
logger
.info("Discovered functional instance of bean '" + definition + "' as a default function, however its "
+ "function argument types can not be determined. Discarding."); + "function argument types can not be determined. Discarding.");
definition = null; definition = null;
} }
@@ -273,7 +286,8 @@ public class BeanFactoryAwareFunctionRegistry
@SuppressWarnings({"unchecked", "rawtypes"}) @SuppressWarnings({"unchecked", "rawtypes"})
private Function<?, ?> compose(Class<?> type, String definition, String... acceptedOutputTypes) { private Function<?, ?> compose(Class<?> type, String definition, String... acceptedOutputTypes) {
if (logger.isInfoEnabled()) { if (logger.isInfoEnabled()) {
logger.info("Looking up function '" + definition + "' with acceptedOutputTypes: " + Arrays.asList(acceptedOutputTypes)); logger.info("Looking up function '" + definition + "' with acceptedOutputTypes: " + Arrays
.asList(acceptedOutputTypes));
} }
definition = discoverDefaultDefinitionIfNecessary(definition); definition = discoverDefaultDefinitionIfNecessary(definition);
if (StringUtils.isEmpty(definition)) { if (StringUtils.isEmpty(definition)) {
@@ -301,7 +315,8 @@ public class BeanFactoryAwareFunctionRegistry
else { else {
Type functionType = FunctionContextUtils.findType(applicationContext.getBeanFactory(), name); Type functionType = FunctionContextUtils.findType(applicationContext.getBeanFactory(), name);
if (functionType != null && functionType.toString().contains("org.apache.kafka.streams.")) { if (functionType != null && functionType.toString().contains("org.apache.kafka.streams.")) {
logger.debug("Kafka Streams function '" + definition + "' is not supported by spring-cloud-function."); logger
.debug("Kafka Streams function '" + definition + "' is not supported by spring-cloud-function.");
return null; return null;
} }
} }
@@ -314,7 +329,8 @@ public class BeanFactoryAwareFunctionRegistry
if (function instanceof FunctionRegistration) { if (function instanceof FunctionRegistration) {
registration = (FunctionRegistration<Object>) function; registration = (FunctionRegistration<Object>) function;
currentFunctionType = currentFunctionType == null ? registration.getType().getType() : currentFunctionType; currentFunctionType = currentFunctionType == null ? registration.getType()
.getType() : currentFunctionType;
function = registration.getTarget(); function = registration.getTarget();
} }
else { else {
@@ -324,7 +340,8 @@ public class BeanFactoryAwareFunctionRegistry
function = this.proxyTarget(function, functionalMethod); function = this.proxyTarget(function, functionalMethod);
} }
String[] aliasNames = this.getAliases(name).toArray(new String[] {}); String[] aliasNames = this.getAliases(name).toArray(new String[] {});
currentFunctionType = currentFunctionType == null ? this.discoverFunctionType(function, aliasNames) : currentFunctionType; currentFunctionType = currentFunctionType == null ? this
.discoverFunctionType(function, aliasNames) : currentFunctionType;
registration = new FunctionRegistration<>(function, name).type(currentFunctionType); registration = new FunctionRegistration<>(function, name).type(currentFunctionType);
} }
@@ -375,7 +392,9 @@ public class BeanFactoryAwareFunctionRegistry
private Object proxyInvokerIfNecessary(FunctionInvocationWrapper functionInvoker) { private Object proxyInvokerIfNecessary(FunctionInvocationWrapper functionInvoker) {
if (functionInvoker != null && AopUtils.isCglibProxy(functionInvoker.getTarget())) { if (functionInvoker != null && AopUtils.isCglibProxy(functionInvoker.getTarget())) {
if (logger.isInfoEnabled()) { if (logger.isInfoEnabled()) {
logger.info("Proxying POJO function: " + functionInvoker.functionDefinition + ". . ." + functionInvoker.target.getClass()); logger
.info("Proxying POJO function: " + functionInvoker.functionDefinition + ". . ." + functionInvoker.target
.getClass());
} }
ProxyFactory pf = new ProxyFactory(functionInvoker.getTarget()); ProxyFactory pf = new ProxyFactory(functionInvoker.getTarget());
pf.setProxyTargetClass(true); pf.setProxyTargetClass(true);
@@ -453,7 +472,6 @@ public class BeanFactoryAwareFunctionRegistry
* catalog. * catalog.
* *
* @author Oleg Zhurakousky * @author Oleg Zhurakousky
*
*/ */
public class FunctionInvocationWrapper implements Function<Object, Object>, Consumer<Object>, Supplier<Object> { public class FunctionInvocationWrapper implements Function<Object, Object>, Consumer<Object>, Supplier<Object> {
@@ -491,6 +509,7 @@ public class BeanFactoryAwareFunctionRegistry
/** /**
* !! Experimental, may change. Is not yet intended as public API !! * !! Experimental, may change. Is not yet intended as public API !!
*
* @param input input value * @param input input value
* @param enricher enricher function instance * @param enricher enricher function instance
* @return the result * @return the result
@@ -507,6 +526,7 @@ public class BeanFactoryAwareFunctionRegistry
/** /**
* !! Experimental, may change. Is not yet intended as public API !! * !! Experimental, may change. Is not yet intended as public API !!
*
* @param enricher enricher function instance * @param enricher enricher function instance
* @return the result * @return the result
*/ */
@@ -563,7 +583,8 @@ public class BeanFactoryAwareFunctionRegistry
} }
if (!(this.target instanceof Consumer) && logger.isDebugEnabled()) { if (!(this.target instanceof Consumer) && logger.isDebugEnabled()) {
logger.debug("Result of invocation of \"" + this.functionDefinition + "\" function is '" + invocationResult + "'"); logger
.debug("Result of invocation of \"" + this.functionDefinition + "\" function is '" + invocationResult + "'");
} }
return invocationResult; return invocationResult;
} }
@@ -577,7 +598,8 @@ public class BeanFactoryAwareFunctionRegistry
Object result; Object result;
if (input instanceof Publisher) { if (input instanceof Publisher) {
input = this.composed ? input : input = this.composed ? input :
this.convertInputPublisherIfNecessary((Publisher<?>) input, FunctionTypeUtils.getInputType(this.functionType, 0)); this.convertInputPublisherIfNecessary((Publisher<?>) input, FunctionTypeUtils
.getInputType(this.functionType, 0));
if (FunctionTypeUtils.isReactive(FunctionTypeUtils.getInputType(this.functionType, 0))) { if (FunctionTypeUtils.isReactive(FunctionTypeUtils.getInputType(this.functionType, 0))) {
result = this.invokeFunction(input); result = this.invokeFunction(input);
} }
@@ -603,7 +625,8 @@ public class BeanFactoryAwareFunctionRegistry
} }
else { else {
Type type = FunctionTypeUtils.getInputType(this.functionType, 0); Type type = FunctionTypeUtils.getInputType(this.functionType, 0);
if (!this.composed && !FunctionTypeUtils.isMultipleInputArguments(this.functionType) && FunctionTypeUtils.isReactive(type)) { if (!this.composed && !FunctionTypeUtils
.isMultipleInputArguments(this.functionType) && FunctionTypeUtils.isReactive(type)) {
Publisher<?> publisher = FunctionTypeUtils.isFlux(type) Publisher<?> publisher = FunctionTypeUtils.isFlux(type)
? input == null ? Flux.empty() : Flux.just(input) ? input == null ? Flux.empty() : Flux.just(input)
: input == null ? Mono.empty() : Mono.just(input); : input == null ? Mono.empty() : Mono.just(input);
@@ -612,18 +635,21 @@ public class BeanFactoryAwareFunctionRegistry
+ "should at least assume reactive output (e.g., Function<String, Flux<String>> f3 = catalog.lookup(\"echoFlux\");), " + "should at least assume reactive output (e.g., Function<String, Flux<String>> f3 = catalog.lookup(\"echoFlux\");), "
+ "otherwise invocation will result in ClassCastException."); + "otherwise invocation will result in ClassCastException.");
} }
result = this.invokeFunction(this.convertInputPublisherIfNecessary(publisher, FunctionTypeUtils.getInputType(this.functionType, 0))); result = this.invokeFunction(this.convertInputPublisherIfNecessary(publisher, FunctionTypeUtils
.getInputType(this.functionType, 0)));
} }
else { else {
result = this.invokeFunction(this.composed ? input result = this.invokeFunction(this.composed ? input
: (input == null ? input : this.convertInputValueIfNecessary(input, FunctionTypeUtils.getInputType(this.functionType, 0)))); : (input == null ? input : this
.convertInputValueIfNecessary(input, FunctionTypeUtils.getInputType(this.functionType, 0))));
} }
} }
// Outputs will be converted only if we're told how (via acceptedOutputMimeTypes), otherwise output returned as is. // Outputs will be converted only if we're told how (via acceptedOutputMimeTypes), otherwise output returned as is.
if (result != null && !ObjectUtils.isEmpty(this.acceptedOutputMimeTypes)) { if (result != null && !ObjectUtils.isEmpty(this.acceptedOutputMimeTypes)) {
result = result instanceof Publisher result = result instanceof Publisher
? this.convertOutputPublisherIfNecessary((Publisher<?>) result, enricher, this.acceptedOutputMimeTypes) ? this
.convertOutputPublisherIfNecessary((Publisher<?>) result, enricher, this.acceptedOutputMimeTypes)
: this.convertOutputValueIfNecessary(result, enricher, this.acceptedOutputMimeTypes); : this.convertOutputValueIfNecessary(result, enricher, this.acceptedOutputMimeTypes);
} }
@@ -642,7 +668,8 @@ public class BeanFactoryAwareFunctionRegistry
Object outputArgument = parsed.getValue(value); Object outputArgument = parsed.getValue(value);
try { try {
convertedInputArray[i] = outputArgument instanceof Publisher convertedInputArray[i] = outputArgument instanceof Publisher
? this.convertOutputPublisherIfNecessary((Publisher<?>) outputArgument, enricher, acceptedOutputMimeTypes[i]) ? this
.convertOutputPublisherIfNecessary((Publisher<?>) outputArgument, enricher, acceptedOutputMimeTypes[i])
: this.convertOutputValueIfNecessary(outputArgument, enricher, acceptedOutputMimeTypes[i]); : this.convertOutputValueIfNecessary(outputArgument, enricher, acceptedOutputMimeTypes[i]);
} }
catch (ArrayIndexOutOfBoundsException e) { catch (ArrayIndexOutOfBoundsException e) {
@@ -655,7 +682,8 @@ public class BeanFactoryAwareFunctionRegistry
convertedValue = Tuples.fromArray(convertedInputArray); convertedValue = Tuples.fromArray(convertedInputArray);
} }
else { else {
List<MimeType> acceptedContentTypes = MimeTypeUtils.parseMimeTypes(acceptedOutputMimeTypes[0].toString()); List<MimeType> acceptedContentTypes = MimeTypeUtils
.parseMimeTypes(acceptedOutputMimeTypes[0].toString());
if (CollectionUtils.isEmpty(acceptedContentTypes)) { if (CollectionUtils.isEmpty(acceptedContentTypes)) {
convertedValue = value; convertedValue = value;
} }
@@ -672,7 +700,8 @@ public class BeanFactoryAwareFunctionRegistry
} }
} }
else if (value instanceof byte[]) { else if (value instanceof byte[]) {
convertedValue = MessageBuilder.withPayload(value).setHeader(MessageHeaders.CONTENT_TYPE, acceptedContentType).build(); convertedValue = MessageBuilder.withPayload(value)
.setHeader(MessageHeaders.CONTENT_TYPE, acceptedContentType).build();
} }
else if (value instanceof Iterable || ObjectUtils.isArray(value)) { else if (value instanceof Iterable || ObjectUtils.isArray(value)) {
boolean isArray = ObjectUtils.isArray(value); boolean isArray = ObjectUtils.isArray(value);
@@ -681,7 +710,9 @@ public class BeanFactoryAwareFunctionRegistry
} }
AtomicReference<List<Message>> messages = new AtomicReference<List<Message>>(new ArrayList<>()); AtomicReference<List<Message>> messages = new AtomicReference<List<Message>>(new ArrayList<>());
((Iterable) value).forEach(element -> ((Iterable) value).forEach(element ->
messages.get().add((Message) convertOutputValueIfNecessary(element, enricher, acceptedContentType.toString()))); messages.get()
.add((Message) convertOutputValueIfNecessary(element, enricher, acceptedContentType
.toString())));
convertedValue = messages.get(); convertedValue = messages.get();
} }
else { else {
@@ -703,14 +734,25 @@ public class BeanFactoryAwareFunctionRegistry
Message outputMessage = null; Message outputMessage = null;
if (value instanceof Message) { if (value instanceof Message) {
MessageHeaders headers = ((Message) value).getHeaders(); MessageHeaders headers = ((Message) value).getHeaders();
if (!headers.containsKey(MessageHeaders.CONTENT_TYPE)) { if (!headers.containsKey(NegotiatingMessageConverterWrapper.ACCEPT)) {
Map<String, Object> headersMap = (Map<String, Object>) ReflectionUtils Map<String, Object> headersMap = (Map<String, Object>) ReflectionUtils
.getField(this.headersField, headers); .getField(this.headersField, headers);
headersMap.put(NegotiatingMessageConverterWrapper.ACCEPT, acceptedContentType);
// Set the contentType header to the value of accept for "legacy" reasons. But, do not set the
// contentType header to the value of accept if it is a wildcard type, as this doesn't make sense.
// This also applies to the else branch below.
if (acceptedContentType.isConcrete()) {
headersMap.put(MessageHeaders.CONTENT_TYPE, acceptedContentType); headersMap.put(MessageHeaders.CONTENT_TYPE, acceptedContentType);
} }
} }
}
else { else {
value = MessageBuilder.withPayload(value).setHeader(MessageHeaders.CONTENT_TYPE, acceptedContentType).build(); MessageBuilder<Object> builder = MessageBuilder.withPayload(value)
.setHeader(NegotiatingMessageConverterWrapper.ACCEPT, acceptedContentType);
if (acceptedContentType.isConcrete()) {
builder.setHeader(MessageHeaders.CONTENT_TYPE, acceptedContentType);
}
value = builder.build();
} }
if (enricher != null) { if (enricher != null) {
value = enricher.apply((Message) value); value = enricher.apply((Message) value);
@@ -726,8 +768,10 @@ public class BeanFactoryAwareFunctionRegistry
} }
Publisher<?> result = publisher instanceof Mono Publisher<?> result = publisher instanceof Mono
? Mono.from(publisher) .map(value -> this.convertOutputValueIfNecessary(value, enricher, acceptedOutputMimeTypes)) ? Mono.from(publisher)
: Flux.from(publisher).map(value -> this.convertOutputValueIfNecessary(value, enricher, acceptedOutputMimeTypes)); .map(value -> this.convertOutputValueIfNecessary(value, enricher, acceptedOutputMimeTypes))
: Flux.from(publisher)
.map(value -> this.convertOutputValueIfNecessary(value, enricher, acceptedOutputMimeTypes));
return result; return result;
} }
@@ -756,8 +800,10 @@ public class BeanFactoryAwareFunctionRegistry
Expression parsed = new SpelExpressionParser().parseExpression("getT" + (i + 1) + "()"); Expression parsed = new SpelExpressionParser().parseExpression("getT" + (i + 1) + "()");
Object inptArgument = parsed.getValue(value); Object inptArgument = parsed.getValue(value);
inptArgument = inptArgument instanceof Publisher inptArgument = inptArgument instanceof Publisher
? this.convertInputPublisherIfNecessary((Publisher<?>) inptArgument, FunctionTypeUtils.getInputType(functionType, i)) ? this.convertInputPublisherIfNecessary((Publisher<?>) inptArgument, FunctionTypeUtils
: this.convertInputValueIfNecessary(inptArgument, FunctionTypeUtils.getInputType(functionType, i)); .getInputType(functionType, i))
: this
.convertInputValueIfNecessary(inptArgument, FunctionTypeUtils.getInputType(functionType, i));
convertedInputArray[i] = inptArgument; convertedInputArray[i] = inptArgument;
} }
convertedValue = Tuples.fromArray(convertedInputArray); convertedValue = Tuples.fromArray(convertedInputArray);
@@ -781,7 +827,8 @@ public class BeanFactoryAwareFunctionRegistry
logger.debug("Converted from Message: " + convertedValue); logger.debug("Converted from Message: " + convertedValue);
} }
if (FunctionTypeUtils.isMessage(type)) { if (FunctionTypeUtils.isMessage(type)) {
convertedValue = MessageBuilder.withPayload(convertedValue).copyHeaders(((Message<?>) value).getHeaders()).build(); convertedValue = MessageBuilder.withPayload(convertedValue)
.copyHeaders(((Message<?>) value).getHeaders()).build();
} }
} }
else if (!FunctionTypeUtils.isMessage(type)) { else if (!FunctionTypeUtils.isMessage(type)) {
@@ -794,7 +841,8 @@ public class BeanFactoryAwareFunctionRegistry
} }
catch (Exception e) { catch (Exception e) {
if (value instanceof String || value instanceof byte[]) { if (value instanceof String || value instanceof byte[]) {
convertedValue = messageConverter.fromMessage(new GenericMessage<Object>(value), (Class<?>) rawType); convertedValue = messageConverter
.fromMessage(new GenericMessage<Object>(value), (Class<?>) rawType);
} }
} }
} }

View File

@@ -0,0 +1,90 @@
/*
* 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.catalog;
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.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.
*/
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, Object conversionHint) {
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) {
for (MimeType supportedConcreteType : delegate.getSupportedMimeTypes()) {
if (accepted.includes(supportedConcreteType)) {
// Note the use of setHeader() which will set the value even if already present.
accessor.setHeader(MessageHeaders.CONTENT_TYPE, supportedConcreteType);
Message<?> result = delegate.toMessage(payload, accessor.toMessageHeaders(), conversionHint);
if (result != null) {
return result;
}
}
}
}
return null;
}
@Override
public Object fromMessage(Message<?> message, Class<?> targetClass) {
return fromMessage(message, targetClass, null);
}
@Override
public Message<?> toMessage(Object payload, MessageHeaders headers) {
return toMessage(payload, headers, null);
}
}

View File

@@ -38,6 +38,7 @@ import org.springframework.cloud.function.context.FunctionProperties;
import org.springframework.cloud.function.context.FunctionRegistry; import org.springframework.cloud.function.context.FunctionRegistry;
import org.springframework.cloud.function.context.catalog.BeanFactoryAwareFunctionRegistry; import org.springframework.cloud.function.context.catalog.BeanFactoryAwareFunctionRegistry;
import org.springframework.cloud.function.context.catalog.FunctionInspector; import org.springframework.cloud.function.context.catalog.FunctionInspector;
import org.springframework.cloud.function.context.catalog.NegotiatingMessageConverterWrapper;
import org.springframework.cloud.function.json.GsonMapper; import org.springframework.cloud.function.json.GsonMapper;
import org.springframework.cloud.function.json.JacksonMapper; import org.springframework.cloud.function.json.JacksonMapper;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
@@ -105,9 +106,9 @@ public class ContextFunctionCatalogAutoConfiguration {
} }
MappingJackson2MessageConverter jsonConverter = new MappingJackson2MessageConverter(); MappingJackson2MessageConverter jsonConverter = new MappingJackson2MessageConverter();
jsonConverter.setObjectMapper(objectMapper); jsonConverter.setObjectMapper(objectMapper);
mcList.add(jsonConverter); mcList.add(NegotiatingMessageConverterWrapper.wrap(jsonConverter));
mcList.add(new ByteArrayMessageConverter()); mcList.add(NegotiatingMessageConverterWrapper.wrap(new ByteArrayMessageConverter()));
mcList.add(new StringMessageConverter()); mcList.add(NegotiatingMessageConverterWrapper.wrap(new StringMessageConverter()));
} }
if (!CollectionUtils.isEmpty(mcList)) { if (!CollectionUtils.isEmpty(mcList)) {
messageConverter = new CompositeMessageConverter(mcList); messageConverter = new CompositeMessageConverter(mcList);