org.anhonesteffort.chnlbrkr.BrkrList.java Source code

Java tutorial

Introduction

Here is the source code for org.anhonesteffort.chnlbrkr.BrkrList.java

Source

/*
 * Copyright (C) 2015 An Honest Effort LLC, coping.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.anhonesteffort.chnlbrkr;

import com.google.common.collect.Lists;
import com.lambdaworks.redis.RedisClient;
import com.lambdaworks.redis.api.async.RedisAsyncCommands;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.EventLoopGroup;
import org.anhonesteffort.chnlzr.CapnpUtil;
import org.capnproto.MessageBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static org.anhonesteffort.chnlzr.Proto.BaseMessage;
import static org.anhonesteffort.chnlzr.Proto.Error;
import static org.anhonesteffort.chnlzr.Proto.HostId;

@ChannelHandler.Sharable
public class BrkrList extends ChannelHandlerAdapter {

    private final HostId.Reader hostId;
    private final Optional<ExpiringRedisSet> expiringSet;

    public BrkrList(ChnlBrkrConfig config, HostId.Reader hostId, EventLoopGroup workerGroup,
            Optional<RedisClient> redisClient) {
        this.hostId = hostId;

        if (redisClient.isPresent()) {
            expiringSet = Optional.of(new ExpiringRedisSet(config, redisClient.get().connect().async()));

            workerGroup.scheduleAtFixedRate(() -> expiringSet.get().removeExpiredMembers(), 0l,
                    config.brokerCacheExpireIntervalMs(), TimeUnit.MILLISECONDS);

            workerGroup.scheduleAtFixedRate(() -> expiringSet.get().addBrkr(hostId), 0l,
                    config.brokerCacheUpdateIntervalMs(), TimeUnit.MILLISECONDS);
        } else {
            expiringSet = Optional.empty();
        }
    }

    @Override
    public void channelRead(ChannelHandlerContext context, Object msg) throws Exception {
        BaseMessage.Reader message = (BaseMessage.Reader) msg;

        switch (message.getType()) {
        case GET_BRKR_LIST:
            if (expiringSet.isPresent()) {
                expiringSet.get().getBrkrList().thenAccept(context::writeAndFlush);
            } else {
                context.writeAndFlush(CapnpUtil.brkrList(Lists.newArrayList(hostId)));
            }
            break;

        default:
            super.channelRead(context, msg);
        }
    }

    // todo: should we shutdown in response to every redis error?
    private static class ExpiringRedisSet {

        private static final Logger log = LoggerFactory.getLogger(ExpiringRedisSet.class);

        private final RedisAsyncCommands<String, String> redisCommands;
        private final String key;
        private final long memberExpireTimeMs;

        public ExpiringRedisSet(ChnlBrkrConfig config, RedisAsyncCommands<String, String> redisCommands) {
            this.redisCommands = redisCommands;
            this.key = config.brokerHostsSetKey();
            this.memberExpireTimeMs = config.brokerCacheExpireIntervalMs();
        }

        private String serialize(HostId.Reader hostId) {
            return hostId.getHostname().toString() + ":" + hostId.getPort();
        }

        private HostId.Reader deserialize(String hostId) {
            return CapnpUtil.hostId(hostId.split(":")[0], Integer.parseInt(hostId.split(":")[1]));
        }

        public void addBrkr(HostId.Reader hostId) {
            redisCommands.zadd(key, (System.currentTimeMillis() + memberExpireTimeMs), serialize(hostId))
                    .handleAsync((Long count, Throwable error) -> {
                        if (error != null)
                            log.error("unable to write host id to redis", error);
                        return Optional.empty();
                    });
        }

        public CompletionStage<MessageBuilder> getBrkrList() {
            return redisCommands.zrangebyscore(key, System.currentTimeMillis(), Double.POSITIVE_INFINITY)
                    .handleAsync((List<String> values, Throwable error) -> {
                        if (error != null) {
                            log.error("unable to retrieve brkr list from redis", error);
                            return CapnpUtil.error(Error.ERROR_UNKNOWN);
                        } else {
                            return CapnpUtil
                                    .brkrList(values.stream().map(this::deserialize).collect(Collectors.toList()));
                        }
                    });
        }

        public void removeExpiredMembers() {
            redisCommands.zremrangebyscore(key, Double.NEGATIVE_INFINITY, System.currentTimeMillis())
                    .handleAsync((Long count, Throwable error) -> {
                        if (error != null)
                            log.error("unable to remove expired members from redis set", error);
                        return Optional.empty();
                    });
        }
    }
}