io.quarkus.redis.runtime.datasource.ReactivePubSubCommandsImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of quarkus-redis-client Show documentation
Show all versions of quarkus-redis-client Show documentation
Connect to Redis in either imperative or reactive style
package io.quarkus.redis.runtime.datasource;
import static io.quarkus.redis.runtime.datasource.Validation.notNullOrEmpty;
import static io.smallrye.mutiny.helpers.ParameterValidation.doesNotContainNull;
import static io.smallrye.mutiny.helpers.ParameterValidation.nonNull;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import io.quarkus.redis.datasource.ReactiveRedisDataSource;
import io.quarkus.redis.datasource.pubsub.ReactivePubSubCommands;
import io.quarkus.redis.datasource.pubsub.RedisPubSubMessage;
import io.quarkus.vertx.core.runtime.context.VertxContextSafetyToggle;
import io.smallrye.common.vertx.VertxContext;
import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.subscription.UniEmitter;
import io.vertx.core.Context;
import io.vertx.core.Vertx;
import io.vertx.mutiny.redis.client.Command;
import io.vertx.mutiny.redis.client.Redis;
import io.vertx.mutiny.redis.client.RedisAPI;
import io.vertx.mutiny.redis.client.RedisConnection;
import io.vertx.mutiny.redis.client.Response;
public class ReactivePubSubCommandsImpl extends AbstractRedisCommands implements ReactivePubSubCommands {
private final Type classOfMessage;
private final Redis client;
private final ReactiveRedisDataSourceImpl datasource;
public ReactivePubSubCommandsImpl(ReactiveRedisDataSourceImpl ds, Type classOfMessage) {
super(ds, new Marshaller(classOfMessage));
this.client = ds.redis;
this.datasource = ds;
this.classOfMessage = classOfMessage;
}
@Override
public ReactiveRedisDataSource getDataSource() {
return datasource;
}
@Override
public Uni publish(String channel, V message) {
nonNull(channel, "channel");
nonNull(message, "message");
RedisCommand cmd = RedisCommand.of(Command.PUBLISH)
.put(channel)
.put(marshaller.encode(message));
return execute(cmd)
.replaceWithVoid();
}
@Override
public Uni subscribe(String channel, Consumer onMessage) {
return subscribe(channel, onMessage, null, null);
}
@Override
public Uni subscribeToPattern(String pattern, Consumer onMessage) {
return subscribeToPattern(pattern, onMessage, null, null);
}
@Override
public Uni subscribeToPattern(String pattern, BiConsumer onMessage) {
return subscribeToPattern(pattern, onMessage, null, null);
}
@Override
public Uni subscribeToPatterns(List patterns, Consumer onMessage) {
return subscribeToPatterns(patterns, onMessage, null, null);
}
@Override
public Uni subscribeToPatterns(List patterns, BiConsumer onMessage) {
return subscribeToPatterns(patterns, onMessage, null, null);
}
@Override
public Uni subscribe(List channels, Consumer onMessage) {
return subscribe(channels, onMessage, null, null);
}
@Override
public Uni subscribe(List channels, BiConsumer onMessage) {
return subscribe(channels, onMessage, null, null);
}
@Override
public Uni subscribe(String channel, Consumer onMessage, Runnable onEnd,
Consumer onException) {
return subscribe(List.of(channel), onMessage, onEnd, onException);
}
@Override
public Uni subscribeToPattern(String pattern, Consumer onMessage, Runnable onEnd,
Consumer onException) {
return subscribeToPatterns(List.of(pattern), onMessage, onEnd, onException);
}
@Override
public Uni subscribeToPattern(String pattern, BiConsumer onMessage, Runnable onEnd,
Consumer onException) {
return subscribeToPatterns(List.of(pattern), onMessage, onEnd, onException);
}
private void validatePatterns(List patterns) {
notNullOrEmpty(patterns, "patterns");
for (String pattern : patterns) {
if (pattern == null) {
throw new IllegalArgumentException("Pattern must not be null");
}
if (pattern.isBlank()) {
throw new IllegalArgumentException("Pattern cannot be blank");
}
}
}
@Override
public Uni subscribeToPatterns(List patterns, Consumer onMessage, Runnable onEnd,
Consumer onException) {
nonNull(onMessage, "onMessage");
validatePatterns(patterns);
return client.connect()
.chain(conn -> {
RedisAPI api = RedisAPI.api(conn);
ReactiveRedisPatternSubscriberImpl subscriber = new ReactiveRedisPatternSubscriberImpl(conn, api, patterns,
(channel, value) -> onMessage.accept(value), onEnd, onException);
return subscriber.subscribe()
.replaceWith(subscriber);
});
}
@Override
public Uni subscribeToPatterns(List patterns, BiConsumer onMessage,
Runnable onEnd,
Consumer onException) {
validatePatterns(patterns);
nonNull(onMessage, "onMessage");
return client.connect()
.chain(conn -> {
RedisAPI api = RedisAPI.api(conn);
ReactiveRedisPatternSubscriberImpl subscriber = new ReactiveRedisPatternSubscriberImpl(conn, api, patterns,
onMessage, onEnd, onException);
return subscriber.subscribe()
.replaceWith(subscriber);
});
}
private void validateChannels(List channels) {
notNullOrEmpty(channels, "channels");
for (String pattern : channels) {
if (pattern == null) {
throw new IllegalArgumentException("Channel must not be null");
}
if (pattern.isBlank()) {
throw new IllegalArgumentException("Channel cannot be blank");
}
}
}
@Override
public Uni subscribe(List channels, Consumer onMessage, Runnable onEnd,
Consumer onException) {
nonNull(onMessage, "onMessage");
validateChannels(channels);
return client.connect()
.chain(conn -> {
RedisAPI api = RedisAPI.api(conn);
ReactiveAbstractRedisSubscriberImpl subscriber = new ReactiveAbstractRedisSubscriberImpl(conn, api,
channels, (channel, value) -> onMessage.accept(value), onEnd, onException);
return subscriber.subscribe()
.replaceWith(subscriber);
});
}
@Override
public Uni subscribe(List channels, BiConsumer onMessage, Runnable onEnd,
Consumer onException) {
notNullOrEmpty(channels, "channels");
nonNull(onMessage, "onMessage");
for (String channel : channels) {
if (channel == null) {
throw new IllegalArgumentException("Channels must not be null");
}
if (channel.isBlank()) {
throw new IllegalArgumentException("Channels cannot be blank");
}
}
return client.connect()
.chain(conn -> {
RedisAPI api = RedisAPI.api(conn);
ReactiveAbstractRedisSubscriberImpl subscriber = new ReactiveAbstractRedisSubscriberImpl(conn, api,
channels, onMessage, onEnd, onException);
return subscriber.subscribe()
.replaceWith(subscriber);
});
}
@Override
public Multi subscribeToPatterns(String... patterns) {
notNullOrEmpty(patterns, "patterns");
doesNotContainNull(patterns, "patterns");
return Multi.createFrom().emitter(emitter -> {
subscribeToPatterns(List.of(patterns), emitter::emit, emitter::complete, emitter::fail)
.subscribe().with(x -> {
emitter.onTermination(() -> x.unsubscribe(patterns).subscribe().asCompletionStage());
}, emitter::fail);
});
}
@Override
public Multi> subscribeAsMessagesToPatterns(String... patterns) {
notNullOrEmpty(patterns, "patterns");
doesNotContainNull(patterns, "patterns");
return Multi.createFrom().emitter(emitter -> {
subscribeToPatterns(List.of(patterns),
(channel, value) -> emitter.emit(new DefaultRedisPubSubMessage<>(value, channel)), emitter::complete,
emitter::fail)
.subscribe().with(x -> {
emitter.onTermination(() -> x.unsubscribe(patterns).subscribe().asCompletionStage());
}, emitter::fail);
});
}
@Override
public Multi subscribe(String... channels) {
notNullOrEmpty(channels, "channels");
doesNotContainNull(channels, "channels");
return Multi.createFrom().emitter(emitter -> {
subscribe(List.of(channels), emitter::emit, emitter::complete, emitter::fail)
.subscribe().with(x -> {
emitter.onTermination(() -> {
x.unsubscribe(channels).subscribe().asCompletionStage();
});
}, emitter::fail);
});
}
@Override
public Multi> subscribeAsMessages(String... channels) {
notNullOrEmpty(channels, "channels");
doesNotContainNull(channels, "channels");
List list = List.of(channels);
return Multi.createFrom().emitter(emitter -> {
subscribe(list, (channel, value) -> new DefaultRedisPubSubMessage<>(value, channel), emitter::complete,
emitter::fail)
.subscribe().with(subscriber -> emitter
.onTermination(() -> subscriber.unsubscribe(channels).subscribe().asCompletionStage()));
});
}
private abstract class AbstractRedisSubscriber implements ReactiveRedisSubscriber {
final RedisConnection connection;
final RedisAPI api;
final String id;
final BiConsumer onMessage;
final Runnable onEnd;
final Consumer onException;
private AbstractRedisSubscriber(RedisConnection connection, RedisAPI api, BiConsumer onMessage,
Runnable onEnd, Consumer onException) {
this.connection = connection;
this.api = api;
this.id = UUID.randomUUID().toString();
this.onMessage = onMessage;
this.onEnd = onEnd;
this.onException = onException;
}
abstract Uni subscribeToRedis();
public Uni subscribe() {
Uni handled = Uni.createFrom().emitter(emitter -> {
connection.handler(r -> runOnDuplicatedContext(() -> handleRedisEvent(emitter, r)));
if (onEnd != null) {
connection.endHandler(() -> runOnDuplicatedContext(onEnd));
}
if (onException != null) {
connection.exceptionHandler(t -> runOnDuplicatedContext(() -> onException.accept(t)));
}
});
Uni subscribed = subscribeToRedis();
return subscribed.chain(() -> handled)
.replaceWith(id);
}
private void runOnDuplicatedContext(Runnable runnable) {
Consumer contextConsumer = c -> {
Context context = VertxContext.getOrCreateDuplicatedContext(c);
VertxContextSafetyToggle.setContextSafe(context, true);
context.runOnContext(ignored -> runnable.run());
};
Optional.ofNullable(Vertx.currentContext()).ifPresentOrElse(contextConsumer,
() -> datasource.getVertx().runOnContext(() -> contextConsumer.accept(Vertx.currentContext())));
}
protected void handleRedisEvent(UniEmitter super Void> emitter, Response r) {
if (r != null && r.size() > 0) {
String command = r.get(0).toString();
if ("subscribe".equalsIgnoreCase(command) || "psubscribe".equalsIgnoreCase(command)) {
emitter.complete(null); // Subscribed
} else if ("message".equalsIgnoreCase(command)) {
onMessage.accept(r.get(1).toString(), marshaller.decode(classOfMessage, r.get(2)));
} else if ("pmessage".equalsIgnoreCase(command)) {
onMessage.accept(r.get(2).toString(), marshaller.decode(classOfMessage, r.get(3)));
}
}
}
public Uni closeAndUnregister(Collection> collection) {
if (collection.isEmpty()) {
return connection.close();
}
return Uni.createFrom().voidItem();
}
}
private class ReactiveAbstractRedisSubscriberImpl extends AbstractRedisSubscriber implements ReactiveRedisSubscriber {
private final List channels;
public ReactiveAbstractRedisSubscriberImpl(RedisConnection connection, RedisAPI api, List channels,
BiConsumer onMessage, Runnable onEnd,
Consumer onException) {
super(connection, api, onMessage, onEnd, onException);
this.channels = new ArrayList<>(channels);
}
@Override
Uni subscribeToRedis() {
return api.subscribe(channels).replaceWithVoid();
}
@Override
public Uni unsubscribe(String... channels) {
notNullOrEmpty(channels, "channels");
doesNotContainNull(channels, "channels");
List list = List.of(channels);
return api.unsubscribe(list)
.chain(() -> {
this.channels.removeAll(list);
return closeAndUnregister(this.channels);
});
}
@Override
public Uni unsubscribe() {
return api.unsubscribe(channels)
.chain(() -> {
this.channels.clear();
return closeAndUnregister(channels);
});
}
}
private class ReactiveRedisPatternSubscriberImpl extends AbstractRedisSubscriber implements ReactiveRedisSubscriber {
private final List patterns;
public ReactiveRedisPatternSubscriberImpl(RedisConnection connection, RedisAPI api, List patterns,
BiConsumer onMessage, Runnable onEnd,
Consumer onException) {
super(connection, api, onMessage, onEnd, onException);
this.patterns = new ArrayList<>(patterns);
}
@Override
Uni subscribeToRedis() {
return api.psubscribe(patterns).replaceWithVoid();
}
@Override
public Uni unsubscribe(String... patterns) {
notNullOrEmpty(patterns, "patterns");
doesNotContainNull(patterns, "patterns");
List list = List.of(patterns);
return api.punsubscribe(list)
.chain(() -> {
this.patterns.removeAll(list);
return closeAndUnregister(this.patterns);
});
}
@Override
public Uni unsubscribe() {
return api.punsubscribe(patterns)
.chain(() -> {
this.patterns.clear();
return closeAndUnregister(patterns);
});
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy