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

org.redisson.client.handler.CommandPubSubDecoder Maven / Gradle / Ivy

There is a newer version: 0.40.13
Show newest version
/**
 * Copyright 2018 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.handler;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;

import org.redisson.client.ChannelName;
import org.redisson.client.RedisPubSubConnection;
import org.redisson.client.codec.ByteArrayCodec;
import org.redisson.client.protocol.CommandData;
import org.redisson.client.protocol.Decoder;
import org.redisson.client.protocol.QueueCommand;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.decoder.ListObjectDecoder;
import org.redisson.client.protocol.decoder.MultiDecoder;
import org.redisson.client.protocol.pubsub.Message;
import org.redisson.client.protocol.pubsub.PubSubMessage;
import org.redisson.client.protocol.pubsub.PubSubPatternMessage;
import org.redisson.client.protocol.pubsub.PubSubStatusMessage;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.CharsetUtil;
import io.netty.util.internal.PlatformDependent;

/**
 * Redis Publish Subscribe protocol decoder
 *
 * @author Nikita Koksharov
 *
 */
public class CommandPubSubDecoder extends CommandDecoder {

    private static final Set MESSAGES = new HashSet(Arrays.asList("subscribe", "psubscribe", "punsubscribe", "unsubscribe"));
    // It is not needed to use concurrent map because responses are coming consecutive
    private final Map entries = new HashMap();
    private final Map> commands = PlatformDependent.newConcurrentHashMap();

    private final ExecutorService executor;
    private final boolean keepOrder;
    
    public CommandPubSubDecoder(ExecutorService executor, boolean keepOrder) {
        this.executor = executor;
        this.keepOrder = keepOrder;
    }

    public void addPubSubCommand(ChannelName channel, CommandData data) {
        String operation = data.getCommand().getName().toLowerCase();
        commands.put(new PubSubKey(channel, operation), data);
    }

    @Override
    protected void decodeCommand(ChannelHandlerContext ctx, ByteBuf in, QueueCommand data) throws Exception {
        if (data == null) {
            try {
                while (in.writerIndex() > in.readerIndex()) {
                    decode(in, null, null, ctx, false);
                }
                sendNext(ctx);
            } catch (Exception e) {
                log.error("Unable to decode data. channel: {} message: {}", ctx.channel(), in.toString(0, in.writerIndex(), CharsetUtil.UTF_8), e);
                sendNext(ctx);
                throw e;
            }
        } else if (data instanceof CommandData) {
            CommandData cmd = (CommandData)data;
            try {
                if (state().isMakeCheckpoint()) {
                    decodeFromCheckpoint(ctx, in, data, cmd);
                } else {
                    while (in.writerIndex() > in.readerIndex()) {
                        decode(in, cmd, null, ctx, false);
                    }
                }
                sendNext(ctx, data);
            } catch (Exception e) {
                log.error("Unable to decode data. channel: {} message: {}", ctx.channel(), in.toString(0, in.writerIndex(), CharsetUtil.UTF_8), e);
                cmd.tryFailure(e);
                sendNext(ctx);
                throw e;
            }
        }
    }
    
    @Override
    protected void decodeResult(CommandData data, List parts, ChannelHandlerContext ctx,
            final Object result) throws IOException {
        if (executor.isShutdown()) {
            return;
        }

        if (result instanceof Message) {
            checkpoint();

            final RedisPubSubConnection pubSubConnection = RedisPubSubConnection.getFrom(ctx.channel());
            ChannelName channelName = ((Message) result).getChannel();
            if (result instanceof PubSubStatusMessage) {
                String operation = ((PubSubStatusMessage) result).getType().name().toLowerCase();
                PubSubKey key = new PubSubKey(channelName, operation);
                CommandData d = commands.get(key);
                if (Arrays.asList(RedisCommands.PSUBSCRIBE.getName(), RedisCommands.SUBSCRIBE.getName()).contains(d.getCommand().getName())) {
                    commands.remove(key);
                    entries.put(channelName, new PubSubEntry(d.getMessageDecoder()));
                }
                
                if (Arrays.asList(RedisCommands.PUNSUBSCRIBE.getName(), RedisCommands.UNSUBSCRIBE.getName()).contains(d.getCommand().getName())) {
                    commands.remove(key);
                    if (result instanceof PubSubPatternMessage) {
                        channelName = ((PubSubPatternMessage)result).getPattern();
                    }
                    PubSubEntry entry = entries.remove(channelName);
                    if (keepOrder) {
                        enqueueMessage(result, pubSubConnection, entry);
                    }
                }
            }
            
            
            if (keepOrder) {
                if (result instanceof PubSubPatternMessage) {
                    channelName = ((PubSubPatternMessage)result).getPattern();
                }
                PubSubEntry entry = entries.get(channelName);
                if (entry != null) {
                    enqueueMessage(result, pubSubConnection, entry);
                }
            } else {
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        if (result instanceof PubSubStatusMessage) {
                            pubSubConnection.onMessage((PubSubStatusMessage) result);
                        } else if (result instanceof PubSubMessage) {
                            pubSubConnection.onMessage((PubSubMessage) result);
                        } else {
                            pubSubConnection.onMessage((PubSubPatternMessage) result);
                        }
                    }
                });
            }
        } else {
            if (data != null && data.getCommand().getName().equals("PING")) {
                super.decodeResult(data, parts, ctx, result);
            }
        }
    }

    private void enqueueMessage(Object result, final RedisPubSubConnection pubSubConnection, final PubSubEntry entry) {
        if (result != null) {
            entry.getQueue().add((Message)result);
        }
        
        if (entry.getSent().compareAndSet(false, true)) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        while (true) {
                            Message result = entry.getQueue().poll();
                            if (result != null) {
                                if (result instanceof PubSubStatusMessage) {
                                    pubSubConnection.onMessage((PubSubStatusMessage) result);
                                } else if (result instanceof PubSubMessage) {
                                    pubSubConnection.onMessage((PubSubMessage) result);
                                } else {
                                    pubSubConnection.onMessage((PubSubPatternMessage) result);
                                }
                            } else {
                                break;
                            }
                        }
                    } finally {
                        entry.getSent().set(false);
                        if (!entry.getQueue().isEmpty()) {
                            enqueueMessage(null, pubSubConnection, entry);
                        }
                    }
                }
            });
        }
    }
    
    @Override
    protected MultiDecoder messageDecoder(CommandData data, List parts) {
        if (parts.isEmpty()) {
            return null;
        }
        String command = parts.get(0).toString();
        if (MESSAGES.contains(command)) {
            ChannelName channelName = new ChannelName((byte[]) parts.get(1));
            PubSubKey key = new PubSubKey(channelName, command);
            CommandData commandData = commands.get(key);
            if (commandData == null) {
                return null;
            }
            return commandData.getCommand().getReplayMultiDecoder();
        } else if (command.equals("message")) {
            byte[] channelName = (byte[]) parts.get(1);
            return entries.get(new ChannelName(channelName)).getDecoder();
        } else if (command.equals("pmessage")) {
            byte[] patternName = (byte[]) parts.get(1);
            return entries.get(new ChannelName(patternName)).getDecoder();
        } else if (command.equals("pong")) {
            return new ListObjectDecoder(0);
        }

        return data.getCommand().getReplayMultiDecoder();
    }

    @Override
    protected Decoder selectDecoder(CommandData data, List parts) {
        if (parts != null) {
            if (data != null && parts.size() == 1 && "pong".equals(parts.get(0))) {
                return data.getCodec().getValueDecoder();
            }
            if (parts.size() == 1) {
                return ByteArrayCodec.INSTANCE.getValueDecoder();
            }
            if (parts.size() == 2 && "pmessage".equals(parts.get(0))) {
                return ByteArrayCodec.INSTANCE.getValueDecoder();
            }
            
            if (parts.size() == 2 && "message".equals(parts.get(0))) {
                byte[] channelName = (byte[]) parts.get(1);
                return entries.get(new ChannelName(channelName)).getDecoder().getDecoder(parts.size(), state());
            }
            if (parts.size() == 3 && "pmessage".equals(parts.get(0))) {
                byte[] patternName = (byte[]) parts.get(1);
                return entries.get(new ChannelName(patternName)).getDecoder().getDecoder(parts.size(), state());
            }
        }
        
        if (data != null && data.getCommand().getName().equals(RedisCommands.PING.getName())) {
            return data.getCodec().getValueDecoder();
        }
        
        return super.selectDecoder(data, parts);
    }

}