Fix SIK module for latest SK compatibility
Spring for Apache Kafka does not produce `ListenableFuture` anymore. * Rework `KafkaProducerMessageHandler` to deal with the `CompletableFuture` from now on * Add support for `CompletableFuture` replies handling into `AbstractMessageProducingHandler` * Remove redundant cast in the `DispatcherHasNoSubscribersTests`
This commit is contained in:
committed by
Gary Russell
parent
0422486409
commit
573c29793a
@@ -77,7 +77,7 @@ public class DispatcherHasNoSubscribersTests {
|
||||
amqpChannel.setBeanFactory(mock(BeanFactory.class));
|
||||
amqpChannel.afterPropertiesSet();
|
||||
|
||||
MessageListener listener = (MessageListener) container.getMessageListener();
|
||||
MessageListener listener = container.getMessageListener();
|
||||
|
||||
assertThatExceptionOfType(MessageDeliveryException.class)
|
||||
.isThrownBy(() -> listener.onMessage(new Message("Hello world!".getBytes())))
|
||||
@@ -101,7 +101,7 @@ public class DispatcherHasNoSubscribersTests {
|
||||
amqpChannel.afterPropertiesSet();
|
||||
|
||||
List<String> logList = insertMockLoggerInListener(amqpChannel);
|
||||
MessageListener listener = (MessageListener) container.getMessageListener();
|
||||
MessageListener listener = container.getMessageListener();
|
||||
listener.onMessage(new Message("Hello world!".getBytes()));
|
||||
verifyLogReceived(logList);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2021 the original author or authors.
|
||||
* Copyright 2014-2022 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.
|
||||
@@ -23,7 +23,9 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
@@ -50,8 +52,6 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
import org.springframework.util.concurrent.ListenableFutureCallback;
|
||||
import org.springframework.util.concurrent.SettableListenableFuture;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -307,7 +307,10 @@ public abstract class AbstractMessageProducingHandler extends AbstractMessageHan
|
||||
replyChannel = getOutputChannel();
|
||||
}
|
||||
|
||||
if (this.async && (reply instanceof ListenableFuture<?> || reply instanceof Publisher<?>)) {
|
||||
if (this.async && (reply instanceof ListenableFuture<?>
|
||||
|| reply instanceof CompletableFuture<?>
|
||||
|| reply instanceof Publisher<?>)) {
|
||||
|
||||
if (reply instanceof Publisher<?> &&
|
||||
replyChannel instanceof ReactiveStreamsSubscribableChannel) {
|
||||
|
||||
@@ -349,12 +352,14 @@ public abstract class AbstractMessageProducingHandler extends AbstractMessageHan
|
||||
}
|
||||
|
||||
private void asyncNonReactiveReply(Message<?> requestMessage, Object reply, @Nullable Object replyChannel) {
|
||||
ListenableFuture<?> future;
|
||||
if (reply instanceof ListenableFuture<?>) {
|
||||
future = (ListenableFuture<?>) reply;
|
||||
CompletableFuture<?> future;
|
||||
if (reply instanceof CompletableFuture<?>) {
|
||||
future = (CompletableFuture<?>) reply;
|
||||
}
|
||||
else if (reply instanceof ListenableFuture<?>) {
|
||||
future = ((ListenableFuture<?>) reply).completable();
|
||||
}
|
||||
else {
|
||||
SettableListenableFuture<Object> settableListenableFuture = new SettableListenableFuture<>();
|
||||
Mono<?> reactiveReply;
|
||||
ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(null, reply);
|
||||
if (adapter != null && adapter.isMultiValue()) {
|
||||
@@ -363,12 +368,10 @@ public abstract class AbstractMessageProducingHandler extends AbstractMessageHan
|
||||
else {
|
||||
reactiveReply = Mono.from((Publisher<?>) reply);
|
||||
}
|
||||
reactiveReply
|
||||
.publishOn(Schedulers.boundedElastic())
|
||||
.subscribe(settableListenableFuture::set, settableListenableFuture::setException);
|
||||
future = settableListenableFuture;
|
||||
|
||||
future = reactiveReply.publishOn(Schedulers.boundedElastic()).toFuture();
|
||||
}
|
||||
future.addCallback(new ReplyFutureCallback(requestMessage, replyChannel));
|
||||
future.whenComplete(new ReplyFutureCallback(requestMessage, replyChannel));
|
||||
}
|
||||
|
||||
private Object getOutputChannelFromRoutingSlip(Object reply, Message<?> requestMessage, List<?> routingSlip,
|
||||
@@ -517,7 +520,7 @@ public abstract class AbstractMessageProducingHandler extends AbstractMessageHan
|
||||
return errorChannel;
|
||||
}
|
||||
|
||||
private final class ReplyFutureCallback implements ListenableFutureCallback<Object> {
|
||||
private final class ReplyFutureCallback implements BiConsumer<Object, Throwable> {
|
||||
|
||||
private final Message<?> requestMessage;
|
||||
|
||||
@@ -529,29 +532,32 @@ public abstract class AbstractMessageProducingHandler extends AbstractMessageHan
|
||||
this.replyChannel = replyChannel;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onSuccess(Object result) {
|
||||
Message<?> replyMessage = null;
|
||||
try {
|
||||
replyMessage = createOutputMessage(result, this.requestMessage.getHeaders());
|
||||
sendOutput(replyMessage, this.replyChannel, false);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Exception exceptionToLogAndSend = ex;
|
||||
if (!(ex instanceof MessagingException)) { // NOSONAR
|
||||
exceptionToLogAndSend = new MessageHandlingException(this.requestMessage, ex);
|
||||
if (replyMessage != null) {
|
||||
exceptionToLogAndSend = new MessagingException(replyMessage, exceptionToLogAndSend);
|
||||
}
|
||||
public void accept(Object result, Throwable exception) {
|
||||
if (exception == null) {
|
||||
Message<?> replyMessage = null;
|
||||
try {
|
||||
replyMessage = createOutputMessage(result, this.requestMessage.getHeaders());
|
||||
sendOutput(replyMessage, this.replyChannel, false);
|
||||
}
|
||||
logger.error(exceptionToLogAndSend, () -> "Failed to send async reply: " + result.toString());
|
||||
onFailure(exceptionToLogAndSend);
|
||||
catch (Exception ex) {
|
||||
Exception exceptionToLogAndSend = ex;
|
||||
if (!(ex instanceof MessagingException)) { // NOSONAR
|
||||
exceptionToLogAndSend = new MessageHandlingException(this.requestMessage, ex);
|
||||
if (replyMessage != null) {
|
||||
exceptionToLogAndSend = new MessagingException(replyMessage, exceptionToLogAndSend);
|
||||
}
|
||||
}
|
||||
logger.error(exceptionToLogAndSend, () -> "Failed to send async reply: " + result.toString());
|
||||
onFailure(exceptionToLogAndSend);
|
||||
}
|
||||
}
|
||||
else {
|
||||
onFailure(exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable ex) {
|
||||
private void onFailure(Throwable ex) {
|
||||
sendErrorMessage(this.requestMessage, ex);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,13 +23,13 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.apache.kafka.clients.consumer.ConsumerRecord;
|
||||
import org.apache.kafka.clients.producer.ProducerConfig;
|
||||
import org.apache.kafka.clients.producer.ProducerRecord;
|
||||
import org.apache.kafka.common.TopicPartition;
|
||||
@@ -69,9 +69,6 @@ import org.springframework.messaging.MessageHandlingException;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
import org.springframework.util.concurrent.ListenableFutureCallback;
|
||||
import org.springframework.util.concurrent.SettableListenableFuture;
|
||||
|
||||
/**
|
||||
* A Message Handler for Apache Kafka; when supplied with a {@link ReplyingKafkaTemplate} it is used as
|
||||
@@ -174,7 +171,7 @@ public class KafkaProducerMessageHandler<K, V> extends AbstractReplyProducingMes
|
||||
if (this.isGateway) {
|
||||
setAsync(true);
|
||||
updateNotPropagatedHeaders(
|
||||
new String[]{KafkaHeaders.TOPIC, KafkaHeaders.PARTITION, KafkaHeaders.KEY}, false);
|
||||
new String[]{ KafkaHeaders.TOPIC, KafkaHeaders.PARTITION, KafkaHeaders.KEY }, false);
|
||||
}
|
||||
if (JacksonPresent.isJackson2Present()) {
|
||||
this.headerMapper = new DefaultKafkaHeaderMapper();
|
||||
@@ -498,7 +495,7 @@ public class KafkaProducerMessageHandler<K, V> extends AbstractReplyProducingMes
|
||||
if (futureToken != null) {
|
||||
producerRecord.headers().remove(KafkaIntegrationHeaders.FUTURE_TOKEN);
|
||||
}
|
||||
ListenableFuture<SendResult<K, V>> sendFuture;
|
||||
CompletableFuture<SendResult<K, V>> sendFuture;
|
||||
RequestReplyFuture<K, V, Object> gatewayFuture = null;
|
||||
if (this.isGateway && (!preBuilt || producerRecord.headers().lastHeader(KafkaHeaders.REPLY_TOPIC) == null)) {
|
||||
producerRecord.headers().add(new RecordHeader(KafkaHeaders.REPLY_TOPIC, getReplyTopic(message)));
|
||||
@@ -530,7 +527,7 @@ public class KafkaProducerMessageHandler<K, V> extends AbstractReplyProducingMes
|
||||
return processReplyFuture(gatewayFuture);
|
||||
}
|
||||
|
||||
private void sendFutureIfRequested(ListenableFuture<SendResult<K, V>> sendFuture, Object futureToken) {
|
||||
private void sendFutureIfRequested(CompletableFuture<SendResult<K, V>> sendFuture, Object futureToken) {
|
||||
|
||||
if (futureToken != null) {
|
||||
MessageChannel futures = getFuturesChannel();
|
||||
@@ -662,31 +659,26 @@ public class KafkaProducerMessageHandler<K, V> extends AbstractReplyProducingMes
|
||||
}
|
||||
|
||||
public void processSendResult(final Message<?> message, final ProducerRecord<K, V> producerRecord,
|
||||
ListenableFuture<SendResult<K, V>> future, MessageChannel metadataChannel)
|
||||
CompletableFuture<SendResult<K, V>> future, MessageChannel metadataChannel)
|
||||
throws InterruptedException, ExecutionException {
|
||||
|
||||
final MessageChannel failureChannel = getSendFailureChannel();
|
||||
if (failureChannel != null || metadataChannel != null) {
|
||||
future.addCallback(new ListenableFutureCallback<SendResult<K, V>>() { // NOSONAR
|
||||
|
||||
@Override
|
||||
public void onSuccess(SendResult<K, V> result) {
|
||||
future.whenComplete((sendResult, exception) -> {
|
||||
if (exception == null) {
|
||||
if (metadataChannel != null) {
|
||||
KafkaProducerMessageHandler.this.messagingTemplate.send(metadataChannel,
|
||||
getMessageBuilderFactory().fromMessage(message)
|
||||
.setHeader(KafkaHeaders.RECORD_METADATA, result.getRecordMetadata()).build());
|
||||
getMessageBuilderFactory()
|
||||
.fromMessage(message)
|
||||
.setHeader(KafkaHeaders.RECORD_METADATA, sendResult.getRecordMetadata())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable ex) {
|
||||
if (failureChannel != null) {
|
||||
KafkaProducerMessageHandler.this.messagingTemplate.send(failureChannel,
|
||||
KafkaProducerMessageHandler.this.errorMessageStrategy.buildErrorMessage(
|
||||
new KafkaSendFailureException(message, producerRecord, ex), null));
|
||||
}
|
||||
else if (failureChannel != null) {
|
||||
KafkaProducerMessageHandler.this.messagingTemplate.send(failureChannel,
|
||||
KafkaProducerMessageHandler.this.errorMessageStrategy.buildErrorMessage(
|
||||
new KafkaSendFailureException(message, producerRecord, exception), null));
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -720,51 +712,47 @@ public class KafkaProducerMessageHandler<K, V> extends AbstractReplyProducingMes
|
||||
return new ConvertingReplyFuture(future);
|
||||
}
|
||||
|
||||
private final class ConvertingReplyFuture extends SettableListenableFuture<Object> {
|
||||
private final class ConvertingReplyFuture extends CompletableFuture<Object> {
|
||||
|
||||
ConvertingReplyFuture(RequestReplyFuture<?, ?, Object> future) {
|
||||
addCallback(future);
|
||||
}
|
||||
|
||||
private void addCallback(final RequestReplyFuture<?, ?, Object> future) {
|
||||
future.addCallback(new ListenableFutureCallback<ConsumerRecord<?, Object>>() { // NOSONAR
|
||||
|
||||
@Override
|
||||
public void onSuccess(ConsumerRecord<?, Object> result) {
|
||||
future.whenComplete((result, exception) -> {
|
||||
if (exception == null) {
|
||||
try {
|
||||
set(dontLeakHeaders(KafkaProducerMessageHandler.this.replyMessageConverter.toMessage(result,
|
||||
null, null, KafkaProducerMessageHandler.this.replyPayloadType)));
|
||||
complete(dontLeakHeaders(
|
||||
KafkaProducerMessageHandler.this.replyMessageConverter.toMessage(result, null, null,
|
||||
KafkaProducerMessageHandler.this.replyPayloadType)));
|
||||
}
|
||||
catch (Exception e) {
|
||||
setException(e);
|
||||
catch (Exception ex) {
|
||||
completeExceptionally(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Message<?> dontLeakHeaders(Message<?> message) {
|
||||
if (message.getHeaders() instanceof KafkaMessageHeaders) {
|
||||
Map<String, Object> headers = ((KafkaMessageHeaders) message.getHeaders()).getRawHeaders();
|
||||
headers.remove(KafkaHeaders.CORRELATION_ID);
|
||||
headers.remove(KafkaHeaders.REPLY_TOPIC);
|
||||
headers.remove(KafkaHeaders.REPLY_PARTITION);
|
||||
return message;
|
||||
}
|
||||
else {
|
||||
return getMessageBuilderFactory().fromMessage(message)
|
||||
.removeHeader(KafkaHeaders.CORRELATION_ID)
|
||||
.removeHeader(KafkaHeaders.REPLY_TOPIC)
|
||||
.removeHeader(KafkaHeaders.REPLY_PARTITION)
|
||||
.build();
|
||||
}
|
||||
else {
|
||||
completeExceptionally(exception);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable ex) {
|
||||
setException(ex);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private Message<?> dontLeakHeaders(Message<?> message) {
|
||||
if (message.getHeaders() instanceof KafkaMessageHeaders) {
|
||||
Map<String, Object> headers = ((KafkaMessageHeaders) message.getHeaders()).getRawHeaders();
|
||||
headers.remove(KafkaHeaders.CORRELATION_ID);
|
||||
headers.remove(KafkaHeaders.REPLY_TOPIC);
|
||||
headers.remove(KafkaHeaders.REPLY_PARTITION);
|
||||
return message;
|
||||
}
|
||||
else {
|
||||
return getMessageBuilderFactory().fromMessage(message)
|
||||
.removeHeader(KafkaHeaders.CORRELATION_ID)
|
||||
.removeHeader(KafkaHeaders.REPLY_TOPIC)
|
||||
.removeHeader(KafkaHeaders.REPLY_PARTITION)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,6 +39,7 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -309,13 +310,14 @@ class KafkaProducerMessageHandlerTests {
|
||||
|
||||
final RuntimeException fooException = new RuntimeException("foo");
|
||||
|
||||
handler = new KafkaProducerMessageHandler<>(new KafkaTemplate<Integer, String>(producerFactory) {
|
||||
handler = new KafkaProducerMessageHandler<>(new KafkaTemplate<>(producerFactory) {
|
||||
|
||||
@Override
|
||||
protected ListenableFuture<SendResult<Integer, String>> doSend(
|
||||
protected CompletableFuture<SendResult<Integer, String>> doSend(
|
||||
ProducerRecord<Integer, String> producerRecord) {
|
||||
SettableListenableFuture<SendResult<Integer, String>> future = new SettableListenableFuture<>();
|
||||
future.setException(fooException);
|
||||
|
||||
CompletableFuture<SendResult<Integer, String>> future = new CompletableFuture<>();
|
||||
future.completeExceptionally(fooException);
|
||||
return future;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user