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

io.soffa.foundation.pubsub.nats.NatsClient Maven / Gradle / Ivy

package io.soffa.foundation.pubsub.nats;

import io.nats.client.*;
import io.nats.client.api.AckPolicy;
import io.nats.client.api.ConsumerConfiguration;
import io.nats.client.api.PublishAck;
import io.nats.client.api.StreamConfiguration;
import io.soffa.foundation.commons.Logger;
import io.soffa.foundation.errors.TechnicalException;
import io.soffa.foundation.model.Message;
import io.soffa.foundation.pubsub.AbstractPubSubClient;
import io.soffa.foundation.pubsub.MessageHandler;
import io.soffa.foundation.pubsub.PubSubClient;
import io.soffa.foundation.pubsub.config.PubSubClientConfig;
import lombok.SneakyThrows;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.jetbrains.annotations.NotNull;

import javax.annotation.PreDestroy;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;


public class NatsClient extends AbstractPubSubClient implements PubSubClient {

    private static final Logger LOG = Logger.get(NatsClient.class);
    private final PubSubClientConfig config;

    private Connection connection;
    private JetStream stream;


    public NatsClient(String applicationName, PubSubClientConfig config, String broadcasting) {
        super(applicationName, config, broadcasting);
        this.config = config;
        configure();
    }


    @SneakyThrows
    @Override
    public void subscribe(@NonNull String subject, boolean broadcast, MessageHandler messageHandler) {
        // String subject, boolean broadcast, io.soffa.foundation.pubsub.MessageHandler handler
        LOG.info("Configuring subscription to %s", subject);

        NatsMessageHandler h = new NatsMessageHandler(connection, messageHandler);
        @SuppressWarnings("PMD")
        Dispatcher dispatcher = connection.createDispatcher();

        if (!broadcast) {
            dispatcher.subscribe(subject, subject + "-group", h);
        } else {
            configureStream(subject);
            ConsumerConfiguration c = ConsumerConfiguration.builder()
                .durable(applicationName)
                .ackWait(Duration.ofSeconds(5))
                .ackPolicy(AckPolicy.Explicit)
                .build();
            PushSubscribeOptions pso = PushSubscribeOptions.builder().configuration(c).build();
            stream.subscribe(subject, dispatcher, h, true, pso);
        }
    }

    private void configure() {
        try {
            String[] addresses = config.getAddresses().split(",");
            Options o = new Options.Builder().servers(addresses)
                .maxReconnects(-1).build();
            connection = Nats.connect(o);
            // connection.flush(Duration.ofSeconds(1));
            JetStreamOptions jso = JetStreamOptions.defaultOptions();
            this.stream = connection.jetStream(jso);
            LOG.info("Connected to NATS servers: %s", config.getAddresses());
        } catch (Exception e) {
            NatsUtil.close(connection);
            throw new TechnicalException(e, "Unable to connect to NATS @ %s", config.getAddresses());
        }
    }

    @SneakyThrows
    private void configureStream(@NonNull String subject) {

        JetStreamManagement jsm = connection.jetStreamManagement();
        try {
            StreamConfiguration.Builder scBuilder = StreamConfiguration.builder()
                .name(subject)
                .addSubjects(subject);
            jsm.addStream(scBuilder.build());
        } catch (JetStreamApiException ignore) {
            LOG.warn("Stream %s already configured", subject);
        }
    }

    @Override
    public CompletableFuture internalRequest(@NonNull String subject, Message message) {
        return connection.request(NatsUtil.createNatsMessage(subject, message)).thenApply(io.nats.client.Message::getData);
    }

    @Override
    public void publish(@NonNull String target, @NotNull Message message) {
        connection.publish(NatsUtil.createNatsMessage(target, message));
    }

    @SneakyThrows
    @Override
    public void broadcast(@NonNull String target, @NotNull Message message) {
        String sub = resolveBroadcast(target);
        PublishAck ack = stream.publish(NatsUtil.createNatsMessage(sub, message));
        if (ack.hasError()) {
            throw new TechnicalException(ack.getError());
        }
    }

    @PreDestroy
    @SuppressWarnings("PMD")
    protected void cleanup() {
        NatsUtil.close(connection);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy