INT-4536: Support Kafka Tombstones

JIRA: https://jira.spring.io/browse/INT-4536

Represent `@KafkaNull` as `null` when `@Payload(required = false)`.
This commit is contained in:
Gary Russell
2018-10-04 11:12:32 -04:00
committed by Artem Bilan
parent 8b4d1e66e5
commit a05004ae38
4 changed files with 112 additions and 11 deletions

View File

@@ -58,6 +58,7 @@ import org.springframework.integration.handler.support.MapArgumentResolver;
import org.springframework.integration.handler.support.PayloadExpressionArgumentResolver;
import org.springframework.integration.handler.support.PayloadsArgumentResolver;
import org.springframework.integration.support.DefaultMessageBuilderFactory;
import org.springframework.integration.support.NullAwarePayloadArgumentResolver;
import org.springframework.integration.support.SmartLifecycleRoleController;
import org.springframework.integration.support.converter.ConfigurableCompositeMessageConverter;
import org.springframework.integration.support.converter.DefaultDatatypeChannelMessageConverter;
@@ -99,6 +100,7 @@ class DefaultConfiguringBeanFactoryPostProcessor implements BeanFactoryPostProce
this.classLoader = classLoader;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof BeanDefinitionRegistry) {
this.beanFactory = beanFactory;
@@ -477,9 +479,13 @@ class DefaultConfiguringBeanFactoryPostProcessor implements BeanFactoryPostProce
}
}
private static BeanDefinition internalArgumentResolversBuilder(boolean listCapable) {
private BeanDefinition internalArgumentResolversBuilder(boolean listCapable) {
ManagedList<BeanDefinition> resolvers = new ManagedList<>();
resolvers.add(new RootBeanDefinition(PayloadExpressionArgumentResolver.class));
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(NullAwarePayloadArgumentResolver.class);
builder.addConstructorArgReference(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME);
// TODO Validator ?
resolvers.add(builder.getBeanDefinition());
resolvers.add(new RootBeanDefinition(PayloadsArgumentResolver.class));
resolvers.add(new RootBeanDefinition(MapArgumentResolver.class));

View File

@@ -74,6 +74,8 @@ import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.annotation.UseSpelInvoker;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.support.MutableMessage;
import org.springframework.integration.support.NullAwarePayloadArgumentResolver;
import org.springframework.integration.support.converter.ConfigurableCompositeMessageConverter;
import org.springframework.integration.support.json.JsonObjectMapper;
import org.springframework.integration.support.json.JsonObjectMapperProvider;
import org.springframework.integration.util.AbstractExpressionEvaluator;
@@ -538,6 +540,19 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
* that don't run in an application context.
*/
private void configureLocalMessageHandlerFactory() {
MessageConverter messageConverter = null;
if (getBeanFactory() != null &&
getBeanFactory()
.containsBean(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME)) {
messageConverter = getBeanFactory()
.getBean(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME,
MessageConverter.class);
this.messageHandlerMethodFactory.setMessageConverter(messageConverter);
}
else {
messageConverter = new ConfigurableCompositeMessageConverter();
}
NullAwarePayloadArgumentResolver nullResolver = new NullAwarePayloadArgumentResolver(messageConverter);
PayloadExpressionArgumentResolver payloadExpressionArgumentResolver = new PayloadExpressionArgumentResolver();
payloadExpressionArgumentResolver.setBeanFactory(getBeanFactory());
@@ -549,6 +564,7 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
List<HandlerMethodArgumentResolver> customArgumentResolvers = new LinkedList<>();
customArgumentResolvers.add(payloadExpressionArgumentResolver);
customArgumentResolvers.add(nullResolver);
customArgumentResolvers.add(payloadsArgumentResolver);
if (this.canProcessMessageList) {
@@ -560,15 +576,6 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
customArgumentResolvers.add(mapArgumentResolver);
this.messageHandlerMethodFactory.setCustomArgumentResolvers(customArgumentResolvers);
if (getBeanFactory() != null &&
getBeanFactory()
.containsBean(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME)) {
this.messageHandlerMethodFactory
.setMessageConverter(getBeanFactory()
.getBean(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME,
MessageConverter.class));
}
}
@SuppressWarnings("unchecked")

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2018 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
*
* http://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.integration.support;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.support.PayloadArgumentResolver;
import org.springframework.validation.Validator;
/**
* A {@link PayloadArgumentResolver} that treats KafkaNull payloads as null.
* {@link Payload @Paylaod} annotation must have required = false.
*
* @author Gary Russell
* @since 5.1
*
*/
public class NullAwarePayloadArgumentResolver extends PayloadArgumentResolver {
public NullAwarePayloadArgumentResolver(MessageConverter messageConverter) {
super(messageConverter, null, false);
}
public NullAwarePayloadArgumentResolver(MessageConverter messageConverter, Validator validator) {
super(messageConverter, validator, false);
}
@Override
protected boolean isEmptyPayload(Object payload) {
return super.isEmptyPayload(payload) || "KafkaNull".equals(payload.getClass().getSimpleName());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2018 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.
@@ -30,10 +30,12 @@ import org.springframework.integration.gateway.RequestReplyExchanger;
import org.springframework.integration.handler.ServiceActivatingHandler;
import org.springframework.messaging.Message;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.support.GenericMessage;
/**
* @author Mark Fisher
* @author Gary Russell
*/
public class ServiceActivatorMethodResolutionTests {
@@ -231,6 +233,17 @@ public class ServiceActivatorMethodResolutionTests {
assertNotEquals("FOO", outputChannel.receive(10).getPayload());
}
@Test
public void nullOk() {
NullOkTestBean testBean = new NullOkTestBean();
ServiceActivatingHandler serviceActivator = new ServiceActivatingHandler(testBean);
QueueChannel outputChannel = new QueueChannel();
serviceActivator.setOutputChannel(outputChannel);
serviceActivator.handleMessage(new GenericMessage<>(new KafkaNull()));
Message<?> result = outputChannel.receive(0);
assertEquals("gotNull", result.getPayload());
}
@SuppressWarnings("unused")
private static class SingleAnnotationTestBean {
@@ -305,4 +318,32 @@ public class ServiceActivatorMethodResolutionTests {
}
@SuppressWarnings("unused")
private static class NullOkTestBean {
NullOkTestBean() {
super();
}
@ServiceActivator
public String nullOK(@Payload(required = false) String s) {
if (s == null) {
return "gotNull";
}
else {
return s;
}
}
}
private static class KafkaNull {
KafkaNull() {
super();
}
}
}