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

org.redisson.client.RedisPubSubConnection Maven / Gradle / Ivy

Go to download

Easy Redis Java client and Real-Time Data Platform. Valkey compatible. Sync/Async/RxJava3/Reactive API. Client side caching. Over 50 Redis based Java objects and services: JCache API, Apache Tomcat, Hibernate, Spring, Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Scheduler, RPC

There is a newer version: 3.40.2
Show newest version
/**
 * Copyright (c) 2013-2024 Nikita Koksharov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.redisson.client;

import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.util.concurrent.FutureListener;
import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.CommandData;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.decoder.MultiDecoder;
import org.redisson.client.protocol.pubsub.*;

import java.util.Collections;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * 
 * @author Nikita Koksharov
 *
 */
public class RedisPubSubConnection extends RedisConnection {

    final Queue> listeners = new ConcurrentLinkedQueue<>();
    final Map channels = new ConcurrentHashMap<>();
    final Map shardedChannels = new ConcurrentHashMap<>();
    final Map patternChannels = new ConcurrentHashMap<>();
    final Map unsubscribedChannels = new ConcurrentHashMap<>();

    public RedisPubSubConnection(RedisClient redisClient, Channel channel, CompletableFuture connectionPromise) {
        super(redisClient, channel, connectionPromise);
    }

    public void addListener(RedisPubSubListener listener) {
        listeners.add((RedisPubSubListener) listener);
    }

    public void removeListener(RedisPubSubListener listener) {
        listeners.remove(listener);
    }

    public void onMessage(PubSubStatusMessage message) {
        for (RedisPubSubListener redisPubSubListener : listeners) {
            redisPubSubListener.onStatus(message.getType(), message.getChannel());
        }
    }

    public void onMessage(PubSubMessage message) {
        for (RedisPubSubListener redisPubSubListener : listeners) {
            redisPubSubListener.onMessage(message.getChannel(), message.getValue());
        }
    }

    public void onMessage(PubSubPatternMessage message) {
        for (RedisPubSubListener redisPubSubListener : listeners) {
            redisPubSubListener.onPatternMessage(message.getPattern(), message.getChannel(), message.getValue());
        }
    }

    public ChannelFuture subscribe(CompletableFuture promise, Codec codec, ChannelName... channels) {
        for (ChannelName ch : channels) {
            this.channels.put(ch, codec);
        }
        return async(promise, new PubSubMessageDecoder(codec.getValueDecoder()), RedisCommands.SUBSCRIBE, channels);
    }

    public ChannelFuture ssubscribe(CompletableFuture promise, Codec codec, ChannelName... channels) {
        for (ChannelName ch : channels) {
            this.shardedChannels.put(ch, codec);
        }
        return async(promise, new PubSubMessageDecoder(codec.getValueDecoder()), RedisCommands.SSUBSCRIBE, channels);
    }

    public ChannelFuture psubscribe(CompletableFuture promise, Codec codec, ChannelName... channels) {
        for (ChannelName ch : channels) {
            patternChannels.put(ch, codec);
        }
        return async(promise, new PubSubPatternMessageDecoder(codec.getValueDecoder()), RedisCommands.PSUBSCRIBE, channels);
    }

    public ChannelFuture subscribe(Codec codec, ChannelName... channels) {
        for (ChannelName ch : channels) {
            this.channels.put(ch, codec);
        }
        return async(new PubSubMessageDecoder(codec.getValueDecoder()), RedisCommands.SUBSCRIBE, channels);
    }

    public ChannelFuture ssubscribe(Codec codec, ChannelName... channels) {
        for (ChannelName ch : channels) {
            this.shardedChannels.put(ch, codec);
        }
        return async(new PubSubMessageDecoder(codec.getValueDecoder()), RedisCommands.SSUBSCRIBE, channels);
    }

    public ChannelFuture psubscribe(Codec codec, ChannelName... channels) {
        for (ChannelName ch : channels) {
            patternChannels.put(ch, codec);
        }
        return async(new PubSubPatternMessageDecoder(codec.getValueDecoder()), RedisCommands.PSUBSCRIBE, channels);
    }

    public ChannelFuture unsubscribe(PubSubType type, ChannelName... channels) {
        RedisCommand command;
        if (type == PubSubType.UNSUBSCRIBE) {
            command = RedisCommands.UNSUBSCRIBE;
            for (ChannelName ch : channels) {
                this.channels.remove(ch);
                unsubscribedChannels.put(ch, type);
            }
        } else if (type == PubSubType.SUNSUBSCRIBE) {
            command = RedisCommands.SUNSUBSCRIBE;
            for (ChannelName ch : channels) {
                this.shardedChannels.remove(ch);
                unsubscribedChannels.put(ch, type);
            }
        } else {
            command = RedisCommands.PUNSUBSCRIBE;
            for (ChannelName ch : channels) {
                patternChannels.remove(ch);
                unsubscribedChannels.put(ch, type);
            }
        }

        ChannelFuture future = async((MultiDecoder) null, command, channels);
        future.addListener((FutureListener) f -> {
            if (!f.isSuccess()) {
                for (ChannelName channel : channels) {
                    removeDisconnectListener(channel);
                    onMessage(new PubSubStatusMessage(type, channel));
                }
            }
        });
        return future;
    }
    
    public void removeDisconnectListener(ChannelName channel) {
        unsubscribedChannels.remove(channel);
    }
    
    @Override
    public void fireDisconnected() {
        super.fireDisconnected();

        unsubscribedChannels.forEach((key, value) -> onMessage(new PubSubStatusMessage(value, key)));
    }
    
    private  ChannelFuture async(MultiDecoder messageDecoder, RedisCommand command, Object... params) {
        CompletableFuture promise = new CompletableFuture<>();
        return channel.writeAndFlush(new CommandData<>(promise, messageDecoder, null, command, params));
    }

    private  ChannelFuture async(CompletableFuture promise, MultiDecoder messageDecoder, RedisCommand command, Object... params) {
        return channel.writeAndFlush(new CommandData<>(promise, messageDecoder, null, command, params));
    }

    public Map getShardedChannels() {
        return Collections.unmodifiableMap(shardedChannels);
    }

    public Map getChannels() {
        return Collections.unmodifiableMap(channels);
    }

    public Map getPatternChannels() {
        return Collections.unmodifiableMap(patternChannels);
    }

}