
org.reactivecommons.async.rabbit.communications.ReactiveMessageSender Maven / Gradle / Ivy
package org.reactivecommons.async.rabbit.communications;
import com.rabbitmq.client.AMQP;
import org.reactivecommons.async.commons.communications.Message;
import org.reactivecommons.async.commons.converters.MessageConverter;
import org.reactivecommons.async.commons.exceptions.SendFailureNoAckException;
import reactor.core.publisher.Flux;
import reactor.core.publisher.FluxSink;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoSink;
import reactor.rabbitmq.OutboundMessage;
import reactor.rabbitmq.OutboundMessageResult;
import reactor.rabbitmq.Sender;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import static org.reactivecommons.async.api.DirectAsyncGateway.DELAYED;
import static org.reactivecommons.async.commons.Headers.SOURCE_APPLICATION;
public class ReactiveMessageSender {
private final Sender sender;
private final String sourceApplication;
private final MessageConverter messageConverter;
private final TopologyCreator topologyCreator;
private final int numberOfSenderSubscriptions = 4;
private final CopyOnWriteArrayList> fluxSinkConfirm = new CopyOnWriteArrayList<>();
private volatile FluxSink fluxSinkNoConfirm;
private final AtomicLong counter = new AtomicLong();
private final ExecutorService executorService = Executors.newFixedThreadPool(13, r -> new Thread(r, "RMessageSender1-" + counter.getAndIncrement()));
private final ExecutorService executorService2 = Executors.newFixedThreadPool(13, r -> new Thread(r, "RMessageSender2-" + counter.getAndIncrement()));
public ReactiveMessageSender(Sender sender, String sourceApplication, MessageConverter messageConverter, TopologyCreator topologyCreator) {
this.sender = sender;
this.sourceApplication = sourceApplication;
this.messageConverter = messageConverter;
this.topologyCreator = topologyCreator;
for (int i = 0; i < numberOfSenderSubscriptions; ++i) {
final Flux messageSource = Flux.create(fluxSinkConfirm::add);
sender.sendWithTypedPublishConfirms(messageSource)
.doOnNext((OutboundMessageResult outboundMessageResult) -> {
final Consumer ackNotifier = outboundMessageResult.getOutboundMessage().getAckNotifier();
executorService.submit(() -> ackNotifier.accept(outboundMessageResult.isAck()));
}).subscribe();
}
final Flux messageSourceNoConfirm = Flux.create(fluxSink -> {
this.fluxSinkNoConfirm = fluxSink;
});
sender.send(messageSourceNoConfirm).subscribe();
}
public Mono sendWithConfirm(T message, String exchange, String routingKey, Map headers, boolean persistent) {
return Mono.create(monoSink -> {
Consumer notifier = new AckNotifier(monoSink);
final MyOutboundMessage outboundMessage = toOutboundMessage(message, exchange, routingKey, headers, notifier, persistent);
executorService2.submit(() -> fluxSinkConfirm.get((int) (System.currentTimeMillis() % numberOfSenderSubscriptions)).next(outboundMessage));
});
}
public Mono sendNoConfirm(T message, String exchange, String routingKey, Map headers, boolean persistent) {
fluxSinkNoConfirm.next(toOutboundMessage(message, exchange, routingKey, headers, persistent));
return Mono.empty();
}
public Flux sendWithConfirmBatch(Flux messages, String exchange, String routingKey, Map headers, boolean persistent) {
return messages.map(message -> toOutboundMessage(message, exchange, routingKey, headers, persistent))
.as(sender::sendWithPublishConfirms)
.flatMap(result -> result.isAck() ?
Mono.empty() :
Mono.error(new SendFailureNoAckException("Event no ACK in communications"))
);
}
private static class AckNotifier implements Consumer {
private final MonoSink monoSink;
public AckNotifier(MonoSink monoSink) {
this.monoSink = monoSink;
}
@Override
public void accept(Boolean ack) {
if (ack) {
monoSink.success();
} else {
monoSink.error(new SendFailureNoAckException("No ACK when sending message"));
}
}
}
static class MyOutboundMessage extends OutboundMessage {
private final Consumer ackNotifier;
public MyOutboundMessage(String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body, Consumer ackNotifier) {
super(exchange, routingKey, properties, body);
this.ackNotifier = ackNotifier;
}
public Consumer getAckNotifier() {
return ackNotifier;
}
}
private MyOutboundMessage toOutboundMessage(T object, String exchange, String routingKey, Map headers, Consumer ackNotifier, boolean persistent) {
final Message message = messageConverter.toMessage(object);
final AMQP.BasicProperties props = buildMessageProperties(message, headers, persistent);
return new MyOutboundMessage(exchange, routingKey, props, message.getBody(), ackNotifier);
}
private OutboundMessage toOutboundMessage(T object, String exchange, String routingKey, Map headers, boolean persistent) {
final Message message = messageConverter.toMessage(object);
final AMQP.BasicProperties props = buildMessageProperties(message, headers, persistent);
return new OutboundMessage(exchange, routingKey, props, message.getBody());
}
private AMQP.BasicProperties buildMessageProperties(Message message, Map headers, boolean persistent) {
final Message.Properties properties = message.getProperties();
final Map baseHeaders = new HashMap<>(properties.getHeaders());
baseHeaders.putAll(headers);
baseHeaders.put(SOURCE_APPLICATION, sourceApplication);
AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder()
.contentType(properties.getContentType())
.appId(sourceApplication)
.contentEncoding(properties.getContentEncoding())
.deliveryMode(persistent ? 2 : 1)
.timestamp(new Date())
.messageId(UUID.randomUUID().toString())
.headers(baseHeaders);
if (headers.containsKey(DELAYED)) {
builder.expiration((String) headers.get(DELAYED));
}
return builder.build();
}
public reactor.rabbitmq.Sender getSender() {
return sender;
}
public TopologyCreator getTopologyCreator() {
return topologyCreator;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy