All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.reactivecommons.async.rabbit.communications.ReactiveMessageSender Maven / Gradle / Ivy

The newest version!
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