package io.eguan.net; /* * #%L * Project eguan * %% * Copyright (C) 2012 - 2017 Oodrive * %% * 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. * #L% */ import io.eguan.net.MsgClientStartpoint.PeerReconnection; import io.eguan.proto.net.MsgWrapper; import io.eguan.proto.net.MsgWrapper.MsgReply; import java.net.InetSocketAddress; import java.util.Collection; import java.util.Map; import java.util.Queue; import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReadWriteLock; import javax.annotation.concurrent.GuardedBy; import org.jboss.netty.bootstrap.ClientBootstrap; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.SimpleChannelHandler; import org.jboss.netty.channel.group.ChannelGroup; import org.jboss.netty.util.Timeout; import org.jboss.netty.util.Timer; import org.jboss.netty.util.TimerTask; /** * The Netty client handler which receive all remote events. * * @author oodrive * @author llambert * */ final class MsgClientHandler extends SimpleChannelHandler { /** <code>true</code> if the handler is the main broadcast channel */ private final boolean mainChannel; /** Map used to wait for acknowledgments from remote peers through the use of a count down latch. */ private final Map<Long, CountDownLatch> mapQueue; private final ClientBootstrap clientBootstrap; /** A ReadWrite lock to protect the variable channelGroup. */ private final ReadWriteLock lockClientStarted; /** A set which contains all remote channels. */ @GuardedBy(value = "lockClientStarted") private final ChannelGroup channelGroup; @GuardedBy(value = "peersConnect") private final Map<InetSocketAddress, PeerReconnection> peersConnect; @GuardedBy(value = "peersConnect") private final Map<UUID, InetSocketAddress> peerNodes; private final Map<Long, Queue<MsgServerRemoteStatus>> mapExceptions; /** Timer used to generated reconnection to remote peers. */ private final AtomicReference<Timer> timerRef; /** Message client id. */ private final UUID msgClientId; /** Message peer id. Set when the handler is connected. */ private UUID msgPeerId = null; /** Tells whether the client is started. */ @GuardedBy(value = "lockClientStarted") private final AtomicBoolean clientStarted; MsgClientHandler(final boolean mainChannel, final UUID msgClientId, final Map<Long, CountDownLatch> mapQueue, final Map<Long, Queue<MsgServerRemoteStatus>> mapExceptions, final ClientBootstrap clientBootstrap, final ChannelGroup channelGroup, final ReadWriteLock lockClientStarted, final AtomicBoolean clientStarted, final Map<InetSocketAddress, PeerReconnection> peersConnect, final Map<UUID, InetSocketAddress> peerNodes, final AtomicReference<Timer> timerRef) { this.mainChannel = mainChannel; this.msgClientId = msgClientId; this.mapQueue = mapQueue; this.mapExceptions = mapExceptions; this.clientBootstrap = clientBootstrap; this.channelGroup = channelGroup; this.lockClientStarted = lockClientStarted; this.clientStarted = clientStarted; this.peersConnect = peersConnect; this.peerNodes = peerNodes; this.timerRef = timerRef; } @Override public final void messageReceived(final ChannelHandlerContext ctx, final MessageEvent e) { final MsgWrapper.MsgReply msgReply = (MsgReply) e.getMessage(); final Long msgMsgId = Long.valueOf(msgReply.getMsgId()); // If an error occurs into the remote peer then add the associated exception to the mapExceptions if (!msgReply.getStatus()) { final Collection<MsgServerRemoteStatus> msgServerRemoteStatus = mapExceptions.get(msgMsgId); if (msgServerRemoteStatus != null) { msgServerRemoteStatus.add(new MsgServerRemoteStatus(msgPeerId, msgReply.getException(), e .getRemoteAddress())); } MsgClientStartpoint.LOGGER.warn("Msg client [{}] receive exception '{}' from '{}/{}'", new Object[] { msgClientId, msgReply.getException(), msgPeerId, e.getRemoteAddress() }); } else if (msgReply.hasRepData()) { final Collection<MsgServerRemoteStatus> msgServerRemoteStatus = mapExceptions.get(msgMsgId); if (msgServerRemoteStatus != null) { msgServerRemoteStatus.add(new MsgServerRemoteStatus(msgPeerId, msgReply.getRepData(), e .getRemoteAddress())); } if (MsgClientStartpoint.LOGGER.isDebugEnabled()) { MsgClientStartpoint.LOGGER.debug("Msg client [{}] received reply from '{}/{}'", new Object[] { msgClientId, msgPeerId, e.getRemoteAddress() }); } } // ACK received then increment the counter of the count down latch associated to the message final CountDownLatch countDownLatch = mapQueue.get(msgMsgId); if (countDownLatch != null) { countDownLatch.countDown(); } } @Override public final void channelConnected(final ChannelHandlerContext ctx, final ChannelStateEvent e) throws Exception { // If the client is stopped then close all new connected channels in order to be able to release allocated // resources when the client stop. lockClientStarted.readLock().lock(); try { if (!clientStarted.get()) { ctx.getChannel().close(); return; } } finally { lockClientStarted.readLock().unlock(); } final InetSocketAddress peer; synchronized (peersConnect) { assert msgPeerId == null; peer = (InetSocketAddress) ctx.getChannel().getRemoteAddress(); // Connection completed if (mainChannel) { final PeerReconnection peerReconnection = peersConnect.get(peer); if (peerReconnection == null) { ctx.getChannel().close(); return; } // Reset the exponential backoff algorithm for that remote peer peerReconnection.connected(); } // Look for the UUID of the peer for (final Map.Entry<UUID, InetSocketAddress> peerNode : peerNodes.entrySet()) { if (peerNode.getValue().equals(peer)) { msgPeerId = peerNode.getKey(); break; } } // Peer UUID should be found... if (msgPeerId == null) { ctx.getChannel().close(); MsgClientStartpoint.LOGGER.error("Failed to find the node ID of '{}'", peer); return; } } // Add the new channel to the channel group if (mainChannel) { lockClientStarted.writeLock().lock(); try { channelGroup.add(ctx.getChannel()); } finally { lockClientStarted.writeLock().unlock(); } } MsgClientStartpoint.LOGGER.info("Msg client [{}] connected to '{}@{}'", msgClientId, msgPeerId, peer); } @Override public final void channelClosed(final ChannelHandlerContext ctx, final ChannelStateEvent e) { // Reset the UUID of the peer msgPeerId = null; // If the client is stopped then close all new connected channels in order to be able to release allocated // resources when the client stop. lockClientStarted.readLock().lock(); try { if (!clientStarted.get()) { ctx.getChannel().close(); return; } } finally { lockClientStarted.readLock().unlock(); } if (mainChannel) { final InetSocketAddress peer = (InetSocketAddress) clientBootstrap.getOption("remoteAddress"); final PeerReconnection peerReconnection; synchronized (peersConnect) { peerReconnection = peersConnect.get(peer); } // The peer was removed from the map peers if (peerReconnection == null) { return; } final long delay = peerReconnection.getDelay(); try { final Timer timer = timerRef.get(); if (timer != null) { timer.newTimeout(new TimerTask() { @Override public final void run(final Timeout timeout) throws Exception { clientBootstrap.connect(); } }, delay, TimeUnit.SECONDS); } } catch (final Throwable t) { MsgClientStartpoint.LOGGER.error("Unable to run new task", t); } } if (MsgClientStartpoint.LOGGER.isTraceEnabled()) { MsgClientStartpoint.LOGGER.trace("Msg client [{}] Will try a reconnection to '{}'", msgClientId, clientBootstrap.getOption("remoteAddress")); } } }