Java tutorial
/* * 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(); }); } } }