io.quarkiverse.reactive.messaging.nats.jetstream.client.DefaultConnection Maven / Gradle / Ivy
package io.quarkiverse.reactive.messaging.nats.jetstream.client;
import static io.nats.client.Connection.Status.CONNECTED;
import static io.quarkiverse.reactive.messaging.nats.jetstream.mapper.DefaultMessageMapper.MESSAGE_TYPE_HEADER;
import static io.smallrye.reactive.messaging.tracing.TracingUtils.traceOutgoing;
import java.io.IOException;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.concurrent.TimeoutException;
import org.eclipse.microprofile.reactive.messaging.Message;
import org.jboss.logging.Logger;
import io.nats.client.*;
import io.nats.client.api.ConsumerInfo;
import io.nats.client.api.KeyValueEntry;
import io.nats.client.api.StreamInfo;
import io.nats.client.api.StreamInfoOptions;
import io.nats.client.impl.Headers;
import io.quarkiverse.reactive.messaging.nats.jetstream.ExponentialBackoff;
import io.quarkiverse.reactive.messaging.nats.jetstream.JetStreamOutgoingMessageMetadata;
import io.quarkiverse.reactive.messaging.nats.jetstream.client.api.*;
import io.quarkiverse.reactive.messaging.nats.jetstream.client.api.Consumer;
import io.quarkiverse.reactive.messaging.nats.jetstream.client.configuration.*;
import io.quarkiverse.reactive.messaging.nats.jetstream.client.configuration.ConsumerConfiguration;
import io.quarkiverse.reactive.messaging.nats.jetstream.mapper.ConsumerMapper;
import io.quarkiverse.reactive.messaging.nats.jetstream.mapper.MessageMapper;
import io.quarkiverse.reactive.messaging.nats.jetstream.mapper.PayloadMapper;
import io.quarkiverse.reactive.messaging.nats.jetstream.mapper.StreamStateMapper;
import io.quarkiverse.reactive.messaging.nats.jetstream.tracing.JetStreamInstrument;
import io.quarkiverse.reactive.messaging.nats.jetstream.tracing.JetStreamTrace;
import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.tuples.Tuple2;
import io.smallrye.mutiny.tuples.Tuple5;
import io.smallrye.mutiny.unchecked.Unchecked;
import io.vertx.mutiny.core.Context;
public class DefaultConnection implements Connection {
private final static Logger logger = Logger.getLogger(DefaultConnection.class);
private final io.nats.client.Connection connection;
private final List listeners;
private final Context context;
private final StreamStateMapper streamStateMapper;
private final ConsumerMapper consumerMapper;
private final MessageMapper messageMapper;
private final PayloadMapper payloadMapper;
private final JetStreamInstrument instrument;
DefaultConnection(final ConnectionConfiguration configuration,
final ConnectionListener connectionListener,
final Context context,
final MessageMapper messageMapper,
final PayloadMapper payloadMapper,
final ConsumerMapper consumerMapper,
final StreamStateMapper streamStateMapper,
final JetStreamInstrument instrumenter) throws ConnectionException {
this.connection = connect(configuration);
this.listeners = new ArrayList<>(List.of(connectionListener));
this.context = context;
this.streamStateMapper = streamStateMapper;
this.consumerMapper = consumerMapper;
this.messageMapper = messageMapper;
this.payloadMapper = payloadMapper;
this.instrument = instrumenter;
fireEvent(ConnectionEvent.Connected, "Connection established");
}
@Override
public boolean isConnected() {
return CONNECTED.equals(connection.getStatus());
}
@Override
public Uni flush(Duration duration) {
return Uni.createFrom(). item(Unchecked.supplier(() -> {
try {
connection.flush(duration);
return null;
} catch (TimeoutException | InterruptedException e) {
throw new ConnectionException(e);
}
}))
.emitOn(context::runOnContext);
}
@Override
public List listeners() {
return listeners;
}
@Override
public void addListener(ConnectionListener listener) {
listeners.add(listener);
}
@Override
public void removeListener(ConnectionListener listener) {
this.listeners.remove(listener);
}
@Override
public void close() {
new ArrayList<>(listeners).forEach(listener -> {
try {
listener.close();
} catch (Throwable failure) {
logger.warnf(failure, "Error closing listener: %s", failure.getMessage());
}
});
try {
connection.close();
} catch (Throwable throwable) {
logger.warnf(throwable, "Error closing connection: %s", throwable.getMessage());
}
}
@Override
public Uni getConsumer(String stream, String consumerName) {
return getJetStreamManagement()
.onItem().transformToUni(jsm -> Uni.createFrom(). emitter(emitter -> {
try {
emitter.complete(jsm.getConsumerInfo(stream, consumerName));
} catch (IOException | JetStreamApiException e) {
emitter.fail(new SystemException(e));
}
}))
.onItem().transform(consumerMapper::of)
.emitOn(context::runOnContext);
}
@Override
public Uni deleteConsumer(String streamName, String consumerName) {
return getJetStreamManagement()
.onItem().transformToUni(jsm -> Uni.createFrom(). emitter(emitter -> {
try {
jsm.deleteConsumer(streamName, consumerName);
emitter.complete(null);
} catch (Throwable failure) {
emitter.fail(new SystemException(failure));
}
}))
.emitOn(context::runOnContext);
}
@Override
public Uni pauseConsumer(String streamName, String consumerName, ZonedDateTime pauseUntil) {
return getJetStreamManagement()
.onItem().transformToUni(jetStreamManagement -> Uni.createFrom(). emitter(emitter -> {
try {
final var response = jetStreamManagement.pauseConsumer(streamName, consumerName, pauseUntil);
if (!response.isPaused()) {
emitter.fail(new SystemException(
String.format("Unable to pause consumer %s in stream %s", consumerName, streamName)));
}
emitter.complete(null);
} catch (Throwable failure) {
emitter.fail(new SystemException(failure));
}
}))
.emitOn(context::runOnContext);
}
@Override
public Uni resumeConsumer(String streamName, String consumerName) {
return getJetStreamManagement()
.onItem().transformToUni(jetStreamManagement -> Uni.createFrom(). emitter(emitter -> {
try {
final var response = jetStreamManagement.resumeConsumer(streamName, consumerName);
if (!response) {
emitter.fail(new SystemException(
String.format("Unable to resume consumer %s in stream %s", consumerName, streamName)));
}
emitter.complete(null);
} catch (Throwable failure) {
emitter.fail(new SystemException(failure));
}
}))
.emitOn(context::runOnContext);
}
@Override
public Uni getFirstSequence(String streamName) {
return getStreamState(streamName)
.onItem().transform(StreamState::firstSequence);
}
@Override
public Uni> getStreams() {
return getJetStreamManagement()
.onItem().transformToUni(jsm -> Uni.createFrom().item(Unchecked.supplier((jsm::getStreamNames))))
.emitOn(context::runOnContext);
}
@Override
public Uni> getSubjects(String streamName) {
return getStreamInfo(streamName)
.onItem().transform(stream -> stream.getConfiguration().getSubjects())
.emitOn(context::runOnContext);
}
@Override
public Uni> getConsumerNames(String streamName) {
return getJetStreamManagement()
.onItem().transformToUni(jsm -> Uni.createFrom().> emitter(emitter -> {
try {
emitter.complete(jsm.getConsumerNames(streamName));
} catch (Throwable failure) {
emitter.fail(new SystemException(failure));
}
}))
.emitOn(context::runOnContext);
}
@Override
public Uni purgeStream(String streamName) {
return getJetStreamManagement()
.onItem().transformToUni(jsm -> Uni.createFrom(). emitter(emitter -> {
try {
final var response = jsm.purgeStream(streamName);
emitter.complete(new PurgeResult(streamName, response.isSuccess(), response.getPurged()));
} catch (Throwable failure) {
emitter.fail(new SystemException(failure));
}
}))
.emitOn(context::runOnContext);
}
@Override
public Uni deleteMessage(String stream, long sequence, boolean erase) {
return getJetStreamManagement()
.onItem().transformToUni(jsm -> Uni.createFrom(). emitter(emitter -> {
try {
if (!jsm.deleteMessage(stream, sequence, erase)) {
emitter.fail(new DeleteException(
String.format("Unable to delete message in stream %s with sequence %d", stream, sequence)));
}
emitter.complete(null);
} catch (Throwable failure) {
emitter.fail(new DeleteException(
String.format("Unable to delete message in stream %s with sequence %d: %s", stream,
sequence, failure.getMessage()),
failure));
}
}))
.emitOn(context::runOnContext);
}
@Override
public Uni getStreamState(String streamName) {
return getStreamInfo(streamName)
.onItem().transform(streamInfo -> streamStateMapper.of(streamInfo.getStreamState()))
.emitOn(context::runOnContext);
}
@Override
public Uni getStreamConfiguration(String streamName) {
return getStreamInfo(streamName)
.onItem().transform(streamInfo -> StreamConfiguration.of(streamInfo.getConfiguration()))
.emitOn(context::runOnContext);
}
@Override
public Uni> purgeAllStreams() {
return getStreams()
.onItem().transformToUni(this::purgeAllStreams)
.emitOn(context::runOnContext);
}
@Override
public Uni> publish(final Message message, final PublishConfiguration configuration) {
return Uni.createFrom().> emitter(emitter -> {
try {
final var metadata = message.getMetadata(JetStreamOutgoingMessageMetadata.class);
final var messageId = metadata.map(JetStreamOutgoingMessageMetadata::messageId)
.orElseGet(() -> UUID.randomUUID().toString());
final var payload = payloadMapper.of(message.getPayload());
final var subject = metadata.flatMap(JetStreamOutgoingMessageMetadata::subtopic)
.map(subtopic -> configuration.subject() + "." + subtopic).orElseGet(configuration::subject);
final var headers = new HashMap>();
metadata.ifPresent(m -> headers.putAll(m.headers()));
if (message.getPayload() != null) {
headers.putIfAbsent(MESSAGE_TYPE_HEADER, List.of(message.getPayload().getClass().getTypeName()));
}
if (configuration.traceEnabled()) {
// Create a new span for the outbound message and record updated tracing information in
// the headers; this has to be done before we build the properties below
traceOutgoing(instrument.publisher(), message,
new JetStreamTrace(configuration.stream(), subject, messageId, headers,
new String(payload)));
}
final var jetStream = connection.jetStream();
final var options = createPublishOptions(messageId, configuration.stream());
emitter.complete(Tuple5.of(jetStream, subject, toJetStreamHeaders(headers), payload, options));
} catch (Throwable failure) {
emitter.fail(
new PublishException(String.format("Failed to publish message: %s", failure.getMessage()), failure));
}
})
.onItem()
.transformToUni(tuple -> Uni.createFrom().completionStage(
tuple.getItem1().publishAsync(tuple.getItem2(), tuple.getItem3(), tuple.getItem4(), tuple.getItem5())))
.onItem()
.invoke(ack -> logger.debugf("Message published to stream: %s with sequence number: %d", ack.getStream(),
ack.getSeqno()))
.onItem().transformToUni(ignore -> acknowledge(message))
.onFailure().recoverWithUni(failure -> notAcknowledge(message, failure))
.onFailure().transform(failure -> new PublishException(failure.getMessage(), failure))
.emitOn(context::runOnContext);
}
@Override
public Uni> publish(Message message,
PublishConfiguration publishConfiguration,
FetchConsumerConfiguration consumerConfiguration) {
return addOrUpdateConsumer(consumerConfiguration)
.onItem().transformToUni(v -> publish(message, publishConfiguration));
}
@Override
public Uni> nextMessage(FetchConsumerConfiguration configuration) {
return addOrUpdateConsumer(configuration)
.onItem()
.transformToUni(consumerContext -> nextMessage(consumerContext, configuration));
}
@Override
public Multi> nextMessages(FetchConsumerConfiguration configuration) {
return addOrUpdateConsumer(configuration)
.onItem().transformToMulti(consumerContext -> nextMessages(consumerContext, configuration))
.emitOn(context::runOnContext);
}
@Override
public Uni getKeyValue(String bucketName, String key, Class valueType) {
return Uni.createFrom(). emitter(emitter -> {
try {
final var keyValue = connection.keyValue(bucketName);
emitter.complete(keyValue.get(key));
} catch (Throwable failure) {
emitter.fail(new KeyValueException(failure));
}
})
.onItem().ifNull().failWith(() -> new KeyValueNotFoundException(bucketName, key))
.onItem().ifNotNull().transform(keyValueEntry -> payloadMapper.of(keyValueEntry.getValue(), valueType))
.emitOn(context::runOnContext);
}
@Override
public Uni putKeyValue(String bucketName, String key, T value) {
return Uni.createFrom(). emitter(emitter -> {
try {
KeyValue keyValue = connection.keyValue(bucketName);
keyValue.put(key, payloadMapper.of(value));
emitter.complete(null);
} catch (Throwable failure) {
emitter.fail(new KeyValueException(failure));
}
})
.emitOn(context::runOnContext);
}
@Override
public Uni deleteKeyValue(String bucketName, String key) {
return Uni.createFrom(). emitter(emitter -> {
try {
KeyValue keyValue = connection.keyValue(bucketName);
keyValue.delete(key);
emitter.complete(null);
} catch (Throwable failure) {
emitter.fail(new KeyValueException(failure));
}
})
.emitOn(context::runOnContext);
}
@Override
public Uni> resolve(String streamName, long sequence) {
return Uni.createFrom().> emitter(emitter -> {
try {
final var jetStream = connection.jetStream();
final var streamContext = jetStream.getStreamContext(streamName);
final var messageInfo = streamContext.getMessage(sequence);
emitter.complete(new JetStreamMessage<>(messageInfo, payloadMapper. of(messageInfo).orElse(null)));
} catch (IOException | JetStreamApiException e) {
emitter.fail(e);
}
})
.emitOn(context::runOnContext);
}
@Override
public Uni> subscribtion(PushConsumerConfiguration configuration) {
return Uni.createFrom().item(() -> new PushSubscription<>(this, configuration, connection, messageMapper, context));
}
@Override
public Uni> subscribtion(ReaderConsumerConfiguration configuration) {
return createSubscription(configuration)
.onItem().transformToUni(subscription -> createReader(configuration, subscription))
.onItem()
.transformToUni(pair -> Uni.createFrom()
.> item(Unchecked.supplier(() -> new ReaderSubscribtion<>(this, configuration,
pair.getItem1(), pair.getItem2(), messageMapper, context))))
.onItem().invoke(this::addListener);
}
@Override
public void close(Subscription subscription) {
try {
subscription.close();
} catch (Throwable failure) {
logger.warnf(failure, "Failed to close subscription: %s", failure.getMessage());
}
removeListener(subscription);
}
@Override
public Uni addOrUpdateKeyValueStores(List keyValueConfigurations) {
return Multi.createFrom().items(keyValueConfigurations.stream())
.onItem().transformToUniAndMerge(this::addOrUpdateKeyValueStore)
.collect().last()
.emitOn(context::runOnContext);
}
@Override
public Uni> addStreams(List streamConfigurations) {
return getJetStreamManagement()
.onItem()
.transformToMulti(jetStreamManagement -> Multi.createFrom()
.items(streamConfigurations.stream()
.map(streamConfiguration -> Tuple2.of(jetStreamManagement, streamConfiguration))))
.onItem().transformToUniAndMerge(tuple -> addOrUpdateStream(tuple.getItem1(), tuple.getItem2()))
.collect().asList()
.emitOn(context::runOnContext);
}
private Uni> createReader(ReaderConsumerConfiguration configuration,
JetStreamSubscription subscription) {
return Uni.createFrom()
.item(Unchecked.supplier(() -> subscription.reader(configuration.maxRequestBatch(), configuration.rePullAt())))
.onItem().transform(reader -> Tuple2.of(subscription, reader));
}
/**
* Creates a subscription.
* If an IllegalArgumentException is thrown the consumer configuration is modified.
*/
private Uni createSubscription(ReaderConsumerConfiguration configuration) {
return Uni.createFrom().item(Unchecked.supplier(connection::jetStream))
.onItem().transformToUni(jetStream -> createSubscription(jetStream, configuration));
}
private Uni createSubscription(JetStream jetStream,
ReaderConsumerConfiguration configuration) {
return subscribe(jetStream, configuration)
.onFailure().recoverWithUni(failure -> {
if (failure instanceof IllegalArgumentException) {
return deleteConsumer(configuration.consumerConfiguration().stream(),
configuration.consumerConfiguration().name())
.onItem().transformToUni(v -> subscribe(jetStream, configuration));
} else {
return Uni.createFrom().failure(failure);
}
});
}
private Uni subscribe(JetStream jetStream, ReaderConsumerConfiguration configuration) {
return Uni.createFrom().emitter(emitter -> {
try {
final var optionsFactory = new PullSubscribeOptionsFactory();
emitter.complete(jetStream.subscribe(configuration.subject(), optionsFactory.create(configuration)));
} catch (Throwable failure) {
emitter.fail(failure);
}
});
}
private Uni getStreamInfo(String streamName) {
return getJetStreamManagement()
.onItem().transformToUni(jsm -> getStreamInfo(jsm, streamName));
}
private Uni getStreamInfo(JetStreamManagement jsm, String streamName) {
return Uni.createFrom().emitter(emitter -> {
try {
emitter.complete(jsm.getStreamInfo(streamName, StreamInfoOptions.allSubjects()));
} catch (Throwable failure) {
emitter.fail(new SystemException(
String.format("Unable to read stream %s with message: %s", streamName, failure.getMessage()), failure));
}
});
}
private Optional purgeStream(JetStreamManagement jetStreamManagement, String streamName) {
try {
final var response = jetStreamManagement.purgeStream(streamName);
return Optional.of(PurgeResult.builder()
.streamName(streamName)
.success(response.isSuccess())
.purgeCount(response.getPurged()).build());
} catch (IOException | JetStreamApiException e) {
logger.warnf(e, "Unable to purge stream %s with message: %s", streamName, e.getMessage());
return Optional.empty();
}
}
private Uni> purgeAllStreams(List streams) {
return getJetStreamManagement()
.onItem().transformToUni(jsm -> Uni.createFrom().item(
Unchecked.supplier(
() -> streams.stream().flatMap(streamName -> purgeStream(jsm, streamName).stream()).toList())));
}
private Uni getJetStreamManagement() {
return Uni.createFrom().emitter(emitter -> {
try {
emitter.complete(connection.jetStreamManagement());
} catch (Throwable failure) {
emitter.fail(
new SystemException(String.format("Unable to manage JetStream: %s", failure.getMessage()), failure));
}
});
}
private PublishOptions createPublishOptions(final String messageId, final String streamName) {
return PublishOptions.builder()
.messageId(messageId)
.stream(streamName)
.build();
}
private FetchConsumer fetchConsumer(final ConsumerContext consumerContext, final Duration timeout)
throws IOException, JetStreamApiException {
if (timeout == null) {
return consumerContext.fetch(FetchConsumeOptions.builder().maxMessages(1).noWait().build());
} else {
return consumerContext.fetch(FetchConsumeOptions.builder().maxMessages(1).expiresIn(timeout.toMillis()).build());
}
}
private Uni> acknowledge(final Message message) {
return Uni.createFrom().completionStage(message.ack())
.onItem().transform(v -> message);
}
private Uni> notAcknowledge(final Message message, final Throwable throwable) {
return Uni.createFrom().completionStage(message.nack(throwable))
.onItem().invoke(() -> logger.warnf(throwable, "Message not acknowledged: %s", throwable.getMessage()))
.onItem().transformToUni(v -> Uni.createFrom().item(message));
}
private Uni nextMessage(final ConsumerContext consumerContext,
final Duration timeout) {
return Uni.createFrom(). emitter(emitter -> {
try {
final var message = consumerContext.next(timeout);
if (message != null) {
emitter.complete(message);
} else {
emitter.fail(new MessageNotFoundException());
}
} catch (Throwable failure) {
logger.errorf(failure, "Failed to fetch message: %s", failure.getMessage());
emitter.fail(new FetchException(failure));
}
})
.emitOn(context::runOnContext);
}
private Uni> nextMessage(final ConsumerContext consumerContext,
final FetchConsumerConfiguration configuration) {
return nextMessage(consumerContext, configuration.fetchTimeout().orElse(null))
.map(message -> messageMapper.of(
message,
configuration.traceEnabled(),
configuration.payloadType().orElse(null),
context,
new ExponentialBackoff(false, Duration.ZERO),
configuration.ackTimeout()));
}
private Headers toJetStreamHeaders(Map> headers) {
final var result = new Headers();
headers.forEach(result::add);
return result;
}
private Uni addOrUpdateConsumer(ConsumerConfiguration configuration) {
return Uni.createFrom().item(Unchecked.supplier(() -> {
try {
final var factory = new ConsumerConfigurtationFactory();
final var consumerConfiguration = factory.create(configuration);
final var streamContext = connection.getStreamContext(configuration.stream());
final var consumerContext = streamContext.createOrUpdateConsumer(consumerConfiguration);
connection.flush(Duration.ZERO);
return consumerContext;
} catch (Throwable failure) {
throw new FetchException(failure);
}
}))
.emitOn(context::runOnContext);
}
private Multi> nextMessages(final ConsumerContext consumerContext,
FetchConsumerConfiguration configuration) {
return Multi.createFrom().> emitter(emitter -> {
try {
try (final var fetchConsumer = fetchConsumer(consumerContext, configuration.fetchTimeout().orElse(null))) {
var message = fetchConsumer.nextMessage();
while (message != null) {
emitter.emit(messageMapper.of(
message,
configuration.traceEnabled(),
configuration.payloadType().orElse(null),
context,
new ExponentialBackoff(false, Duration.ZERO),
configuration.ackTimeout()));
message = fetchConsumer.nextMessage();
}
emitter.complete();
}
} catch (Throwable failure) {
emitter.fail(new FetchException(failure));
}
})
.emitOn(context::runOnContext);
}
private io.nats.client.Connection connect(ConnectionConfiguration configuration) throws ConnectionException {
try {
ConnectionOptionsFactory optionsFactory = new ConnectionOptionsFactory();
final var options = optionsFactory.create(configuration, new InternalConnectionListener(this));
return Nats.connect(options);
} catch (Throwable failure) {
throw new ConnectionException(failure);
}
}
private Uni addOrUpdateKeyValueStore(final KeyValueSetupConfiguration keyValueSetupConfiguration) {
return Uni.createFrom().emitter(emitter -> {
try {
final var kvm = connection.keyValueManagement();
final var factory = new KeyValueConfigurationFactory();
if (kvm.getBucketNames().contains(keyValueSetupConfiguration.bucketName())) {
kvm.update(factory.create(keyValueSetupConfiguration));
} else {
kvm.create(factory.create(keyValueSetupConfiguration));
}
emitter.complete(null);
} catch (Throwable failure) {
emitter.fail(new SetupException(String.format("Unable to manage Key Value Store: %s", failure.getMessage()),
failure));
}
});
}
private Uni addOrUpdateStream(final JetStreamManagement jsm,
final StreamSetupConfiguration setupConfiguration) {
return getStreamInfo(jsm, setupConfiguration.configuration().name())
.onItem().transformToUni(streamInfo -> updateStream(jsm, streamInfo, setupConfiguration))
.onFailure().recoverWithUni(failure -> createStream(jsm, setupConfiguration.configuration()));
}
private Uni createStream(final JetStreamManagement jsm,
final StreamConfiguration streamConfiguration) {
return Uni.createFrom().emitter(emitter -> {
try {
final var factory = new StreamConfigurationFactory();
final var streamConfig = factory.create(streamConfiguration);
jsm.addStream(streamConfig);
emitter.complete(
StreamResult.builder().configuration(streamConfiguration).status(StreamStatus.Created).build());
} catch (Throwable failure) {
emitter.fail(new SetupException(String.format("Unable to create stream: %s with message: %s",
streamConfiguration.name(), failure.getMessage()), failure));
}
});
}
private Uni updateStream(final JetStreamManagement jsm,
final StreamInfo streamInfo,
final StreamSetupConfiguration setupConfiguration) {
return Uni.createFrom(). emitter(emitter -> {
try {
final var currentConfiguration = streamInfo.getConfiguration();
final var factory = new StreamConfigurationFactory();
final var configuration = factory.create(currentConfiguration, setupConfiguration.configuration());
if (configuration.isPresent()) {
logger.debugf("Updating stream %s", setupConfiguration.configuration().name());
jsm.updateStream(configuration.get());
emitter.complete(StreamResult.builder().configuration(setupConfiguration.configuration())
.status(StreamStatus.Updated).build());
} else {
emitter.complete(StreamResult.builder().configuration(setupConfiguration.configuration())
.status(StreamStatus.NotModified).build());
}
} catch (Throwable failure) {
logger.errorf(failure, "message: %s", failure.getMessage());
emitter.fail(new SetupException(String.format("Unable to update stream: %s with message: %s",
setupConfiguration.configuration().name(), failure.getMessage()), failure));
}
})
.onFailure().recoverWithUni(failure -> {
if (failure.getCause() instanceof JetStreamApiException && setupConfiguration.overwrite()) {
return deleteStream(jsm, setupConfiguration.configuration().name())
.onItem().transformToUni(v -> createStream(jsm, setupConfiguration.configuration()));
}
return Uni.createFrom().failure(failure);
});
}
private Uni deleteStream(final JetStreamManagement jsm, String streamName) {
return Uni.createFrom().emitter(emitter -> {
try {
jsm.deleteStream(streamName);
emitter.complete(null);
} catch (Throwable failure) {
emitter.fail(new SetupException(String.format("Unable to delete stream: %s with message: %s", streamName,
failure.getMessage()), failure));
}
});
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy