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

org.redisson.spring.data.connection.RedissonSubscription Maven / Gradle / Ivy

There is a newer version: 3.39.0
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.spring.data.connection;

import org.redisson.client.BaseRedisPubSubListener;
import org.redisson.client.ChannelName;
import org.redisson.client.codec.ByteArrayCodec;
import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.pubsub.PubSubType;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.pubsub.PubSubConnectionEntry;
import org.redisson.pubsub.PublishSubscribeService;
import org.springframework.data.redis.connection.DefaultMessage;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.SubscriptionListener;
import org.springframework.data.redis.connection.util.AbstractSubscription;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;

/**
 * 
 * @author Nikita Koksharov
 *
 */
public class RedissonSubscription extends AbstractSubscription {

    private final CommandAsyncExecutor commandExecutor;
    private final PublishSubscribeService subscribeService;
    
    public RedissonSubscription(CommandAsyncExecutor commandExecutor, PublishSubscribeService subscribeService, MessageListener listener) {
        super(listener, null, null);
        this.commandExecutor = commandExecutor;
        this.subscribeService = subscribeService;
    }

    @Override
    protected void doSubscribe(byte[]... channels) {
        List> list = new ArrayList<>();
        Queue subscribed = new ConcurrentLinkedQueue<>();
        CountDownLatch latch = new CountDownLatch(1);
        for (byte[] channel : channels) {
            if (subscribeService.hasEntry(new ChannelName(channel))) {
                continue;
            }

            CompletableFuture> f = subscribeService.subscribe(ByteArrayCodec.INSTANCE, new ChannelName(channel), new BaseRedisPubSubListener() {
                @Override
                public void onMessage(CharSequence ch, Object message) {
                    if (!Arrays.equals(((ChannelName) ch).getName(), channel)) {
                        return;
                    }

                    byte[] m = toBytes(message);
                    DefaultMessage msg = new DefaultMessage(((ChannelName) ch).getName(), m);
                    getListener().onMessage(msg, null);
                }

                @Override
                public void onStatus(PubSubType type, CharSequence ch) {
                    if (!Arrays.equals(((ChannelName) ch).getName(), channel)) {
                        return;
                    }

                    if (getListener() instanceof SubscriptionListener) {
                        subscribed.add(channel);
                    }
                    super.onStatus(type, ch);

                    if (type == PubSubType.UNSUBSCRIBE) {
                        latch.countDown();
                    }
                }

            });
            list.add(f);
        }
        for (CompletableFuture future : list) {
            commandExecutor.get(future);
        }
        for (byte[] channel : subscribed) {
            ((SubscriptionListener) getListener()).onChannelSubscribed(channel, 1);
        }

        // hack for RedisMessageListenerContainer
        if (getListener().getClass().getName().equals("org.springframework.data.redis.listener.SynchronizingMessageListener")) {
            StringWriter sw = new StringWriter();
            new Exception().printStackTrace(new PrintWriter(sw));
            String[] r = sw.toString().split("\n");
            if (r.length != 7) {
                return;
            }

            try {
                latch.await();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    @Override
    protected void doUnsubscribe(boolean all, byte[]... channels) {
        for (byte[] channel : channels) {
            CompletableFuture f = subscribeService.unsubscribe(new ChannelName(channel), PubSubType.UNSUBSCRIBE);
            if (getListener() instanceof SubscriptionListener) {
                f.whenComplete((r, e) -> {
                    if (r != null) {
                        ((SubscriptionListener) getListener()).onChannelUnsubscribed(channel, 1);
                    }
                });
            }
        }
    }

    @Override
    protected void doPsubscribe(byte[]... patterns) {
        List> list = new ArrayList<>();
        Queue subscribed = new ConcurrentLinkedQueue<>();
        CountDownLatch latch = new CountDownLatch(1);
        for (byte[] channel : patterns) {
            if (subscribeService.hasEntry(new ChannelName(channel))) {
                continue;
            }

            CompletableFuture> f = subscribeService.psubscribe(new ChannelName(channel), ByteArrayCodec.INSTANCE, new BaseRedisPubSubListener() {
                @Override
                public void onPatternMessage(CharSequence pattern, CharSequence ch, Object message) {
                    if (!Arrays.equals(((ChannelName) pattern).getName(), channel)) {
                        return;
                    }

                    byte[] m = toBytes(message);
                    DefaultMessage msg = new DefaultMessage(((ChannelName)ch).getName(), m);
                    getListener().onMessage(msg, ((ChannelName)pattern).getName());
                }

                @Override
                public void onStatus(PubSubType type, CharSequence pattern) {
                    if (!Arrays.equals(((ChannelName) pattern).getName(), channel)) {
                        return;
                    }

                    if (getListener() instanceof SubscriptionListener) {
                        subscribed.add(channel);
                    }
                    super.onStatus(type, pattern);
                    if (type == PubSubType.PUNSUBSCRIBE) {
                        latch.countDown();
                    }
                }
            });
            list.add(f);
        }
        for (CompletableFuture future : list) {
            commandExecutor.get(future);
        }
        for (byte[] channel : subscribed) {
            ((SubscriptionListener) getListener()).onPatternSubscribed(channel, 1);
        }

        // hack for RedisMessageListenerContainer
        if (getListener().getClass().getName().equals("org.springframework.data.redis.listener.SynchronizingMessageListener")) {
            StringWriter sw = new StringWriter();
            new Exception().printStackTrace(new PrintWriter(sw));
            String[] r = sw.toString().split("\n");
            if (r.length != 7) {
                return;
            }

            try {
                latch.await();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    private byte[] toBytes(Object message) {
        if (message instanceof String) {
            return  ((String) message).getBytes();
        }
        return (byte[]) message;
    }

    @Override
    protected void doPUnsubscribe(boolean all, byte[]... patterns) {
        for (byte[] pattern : patterns) {
            CompletableFuture f = subscribeService.unsubscribe(new ChannelName(pattern), PubSubType.PUNSUBSCRIBE);
            if (getListener() instanceof SubscriptionListener) {
                f.whenComplete((r, e) -> {
                    if (r != null) {
                        ((SubscriptionListener) getListener()).onPatternUnsubscribed(pattern, 1);
                    }
                });
            }
        }
    }

    @Override
    protected void doClose() {
        doUnsubscribe(false, getChannels().toArray(new byte[getChannels().size()][]));
        doPUnsubscribe(false, getPatterns().toArray(new byte[getPatterns().size()][]));
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy