io.quarkiverse.reactive.messaging.nats.jetstream.client.ReaderSubscribtion Maven / Gradle / Ivy
package io.quarkiverse.reactive.messaging.nats.jetstream.client;
import java.time.Duration;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.microprofile.reactive.messaging.Message;
import org.jboss.logging.Logger;
import io.nats.client.JetStreamReader;
import io.nats.client.JetStreamStatusException;
import io.nats.client.JetStreamSubscription;
import io.quarkiverse.reactive.messaging.nats.jetstream.ExponentialBackoff;
import io.quarkiverse.reactive.messaging.nats.jetstream.client.configuration.ReaderConsumerConfiguration;
import io.quarkiverse.reactive.messaging.nats.jetstream.mapper.MessageMapper;
import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import io.vertx.mutiny.core.Context;
class ReaderSubscribtion implements Subscription
{
private final static Logger logger = Logger.getLogger(ReaderSubscribtion.class);
private final Connection connection;
private final ReaderConsumerConfiguration
consumerConfiguration;
private final JetStreamReader reader;
private final JetStreamSubscription subscription;
private final MessageMapper messageMapper;
private final Context context;
private final AtomicBoolean closed;
ReaderSubscribtion(Connection connection,
ReaderConsumerConfiguration
consumerConfiguration,
JetStreamSubscription subscription,
JetStreamReader reader,
MessageMapper messageMapper,
Context context) {
this.connection = connection;
this.consumerConfiguration = consumerConfiguration;
this.subscription = subscription;
this.reader = reader;
this.messageMapper = messageMapper;
this.context = context;
this.closed = new AtomicBoolean(false);
}
@Override
public Multi> subscribe() {
boolean traceEnabled = consumerConfiguration.consumerConfiguration().traceEnabled();
Class payloadType = consumerConfiguration.consumerConfiguration().payloadType().orElse(null);
ExecutorService pullExecutor = Executors.newSingleThreadExecutor(JetstreamWorkerThread::new);
return Multi.createBy().repeating()
.uni(this::readNextMessage)
.whilst(message -> !closed.get())
.runSubscriptionOn(pullExecutor)
.emitOn(context::runOnContext)
.flatMap(message -> createMulti(message.orElse(null), traceEnabled, payloadType, context));
}
@Override
public void onEvent(ConnectionEvent event, String message) {
}
@Override
public void close() {
this.closed.set(true);
try {
reader.stop();
} catch (Throwable e) {
logger.warnf("Failed to stop reader with message %s", e.getMessage());
}
try {
if (subscription.isActive()) {
subscription.drain(Duration.ofMillis(1000));
}
} catch (Throwable e) {
logger.warnf("Interrupted while draining subscription");
}
try {
if (subscription.isActive()) {
subscription.unsubscribe();
}
} catch (Throwable e) {
logger.warnf("Failed to unsubscribe subscription with message %s", e.getMessage());
}
connection.removeListener(this);
}
private Uni> readNextMessage() {
return Uni.createFrom().emitter(emitter -> {
try {
if (!connection.isConnected()) {
emitter.fail(new ConnectionException("The connection is not connected"));
} else if (!subscription.isActive()) {
emitter.fail(new ReaderException("The subscription is not active"));
} else {
emitter.complete(Optional
.ofNullable(reader.nextMessage(consumerConfiguration.maxRequestExpires().orElse(Duration.ZERO))));
}
} catch (JetStreamStatusException e) {
emitter.fail(new ReaderException(e));
} catch (IllegalStateException e) {
logger.warnf("The subscription became inactive for stream: %s",
consumerConfiguration.consumerConfiguration().stream());
emitter.complete(Optional.empty());
} catch (InterruptedException e) {
emitter.fail(new ReaderException(String.format("The reader was interrupted for stream: %s",
consumerConfiguration.consumerConfiguration().stream()), e));
} catch (Throwable throwable) {
emitter.fail(new ReaderException(String.format("Error reading next message from stream: %s",
consumerConfiguration.consumerConfiguration().stream()), throwable));
}
});
}
private Multi> createMulti(io.nats.client.Message message,
boolean tracingEnabled, Class payloadType, Context context) {
if (message == null || message.getData() == null) {
return Multi.createFrom().empty();
} else {
return Multi.createFrom()
.item(() -> messageMapper.of(message, tracingEnabled, payloadType, context,
new ExponentialBackoff(
consumerConfiguration.consumerConfiguration().exponentialBackoff(),
consumerConfiguration.consumerConfiguration().exponentialBackoffMaxDuration()),
consumerConfiguration.consumerConfiguration().ackTimeout()));
}
}
}