net.tomp2p.connection.RequestHandler.java Source code

Java tutorial

Introduction

Here is the source code for net.tomp2p.connection.RequestHandler.java

Source

/*
* Copyright 2009 Thomas Bocek
*
* 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 net.tomp2p.connection;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import net.tomp2p.futures.FutureResponse;
import net.tomp2p.message.Message;
import net.tomp2p.message.MessageID;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.peers.PeerStatusListener;
import net.tomp2p.rpc.RPC;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Is able to send UDP messages (as a request) and processes incoming replies. It is important that this class handles
 * close() because if we shutdown the connections, the we need to notify the futures. In case of error set the peer to
 * offline. A similar class is {@link RequestHandlerTCP}, which is used for TCP.
 * 
 * @author Thomas Bocek
 * @param <K>
 *            The type of future to handle
 */
public class RequestHandler<K extends FutureResponse> extends SimpleChannelInboundHandler<Message> {
    private static final Logger LOG = LoggerFactory.getLogger(RequestHandler.class);

    // The future response which is currently be waited for
    private final K futureResponse;

    // The node this request handler is associated with
    private final PeerBean peerBean;
    private final ConnectionBean connectionBean;

    private final Message message;

    private final MessageID sendMessageID;

    // modifiable variables
    private final int idleTCPSeconds; // = ConnectionBean.DEFAULT_TCP_IDLE_SECONDS;
    private final int idleUDPSeconds; // = ConnectionBean.DEFAULT_UDP_IDLE_SECONDS;
    private final int connectionTimeoutTCPMillis; // = ConnectionBean.DEFAULT_CONNECTION_TIMEOUT_TCP;
    private final int slowResponseTimeoutSeconds; // = ConnectionBean.DEFAULT_SLOW_RESPONSE_TIMEOUT_SECONDS;

    /**
     * Create a request handler that can send UDP messages.
     * 
     * @param futureResponse
     *            The future that will be called when we get an answer
     * @param peerBean
     *            The peer bean
     * @param connectionBean
     *            The connection bean
     * @param configuration
     *            the client side connection configuration
     */
    public RequestHandler(final K futureResponse, final PeerBean peerBean, final ConnectionBean connectionBean,
            final ConnectionConfiguration configuration) {
        this.peerBean = peerBean;
        this.connectionBean = connectionBean;
        this.futureResponse = futureResponse;
        this.message = futureResponse.request();
        this.sendMessageID = new MessageID(message);
        this.idleTCPSeconds = configuration.idleTCPSeconds();
        this.idleUDPSeconds = configuration.idleUDPSeconds();
        this.connectionTimeoutTCPMillis = configuration.connectionTimeoutTCPMillis();
        this.slowResponseTimeoutSeconds = configuration.slowResponseTimeoutSeconds();
    }

    /**
     * @return The future response that will be called when we get an answer
     */
    public K futureResponse() {
        return futureResponse;
    }

    /**
     * @return The peer bean
     */
    public PeerBean peerBean() {
        return peerBean;
    }

    /**
     * @return The connection bean
     */
    public ConnectionBean connectionBean() {
        return connectionBean;
    }

    /**
     * @return The time that a TCP connection can be idle
     */
    public int idleTCPSeconds() {
        return idleTCPSeconds;
    }

    /**
     * @return The time that a UDP connection can be idle
     */
    public int idleUDPSeconds() {
        return idleUDPSeconds;
    }

    /**
     * @return The time a TCP connection is allowed to be established
     */
    public int connectionTimeoutTCPMillis() {
        return connectionTimeoutTCPMillis;
    }

    /**
     * @return The time when a slow response time outs
     */
    public int slowResponseTimeoutSeconds() {
        return slowResponseTimeoutSeconds;
    }

    /**
     * Send a UDP message and expect a reply.
     * 
     * @param channelCreator
     *            The channel creator will create a UDP connection
     * @return The future that was added in the constructor
     */
    public K sendUDP(final ChannelCreator channelCreator) {
        connectionBean.sender().sendUDP(this, futureResponse, message, channelCreator, idleUDPSeconds, false);
        return futureResponse;
    }

    /**
     * Send a UDP message and don't expect a reply.
     * 
     * @param channelCreator
     *            The channel creator will create a UDP connection
     * @return The future that was added in the constructor
     */
    public K fireAndForgetUDP(final ChannelCreator channelCreator) {
        connectionBean.sender().sendUDP(null, futureResponse, message, channelCreator, 0, false);
        return futureResponse;
    }

    /**
     * Broadcast a UDP message (layer 2) and expect a reply.
     * 
     * @param channelCreator
     *            The channel creator will create a UDP connection
     * @return The future that was added in the constructor
     */
    public K sendBroadcastUDP(final ChannelCreator channelCreator) {
        connectionBean.sender().sendUDP(this, futureResponse, message, channelCreator, idleUDPSeconds, true);
        return futureResponse;
    }

    /**
     * Broadcast a UDP message (layer 2) and do not expect a reply.
     * 
     * @param channelCreator
     *            The channel creator will create a UDP connection
     * @return The future that was added in the constructor
     */
    public K fireAndForgetBroadcastUDP(final ChannelCreator channelCreator) {
        connectionBean.sender().sendUDP(null, futureResponse, message, channelCreator, 0, true);
        return futureResponse;
    }

    /**
     * Send a TCP message and expect a reply.
     * 
     * @param channelCreator
     *            The channel creator will create a TCP connection
     * @return The future that was added in the constructor
     */
    public K sendTCP(final ChannelCreator channelCreator) {
        connectionBean.sender().sendTCP(this, futureResponse, message, channelCreator, idleTCPSeconds,
                connectionTimeoutTCPMillis, null);
        return futureResponse;
    }

    public K sendTCP(final PeerConnection peerConnection) {
        connectionBean.sender().sendTCP(this, futureResponse, message, null, idleTCPSeconds,
                connectionTimeoutTCPMillis, peerConnection);
        return futureResponse;
    }

    /**
     * Send a TCP message and expect a reply.
     * 
     * @param channelCreator
     *            The channel creator will create a TCP connection
     * @return The future that was added in the constructor
     */
    public K sendTCP(final ChannelCreator channelCreator, final PeerConnection peerConnection) {
        connectionBean.sender().sendTCP(this, futureResponse, message, channelCreator, idleTCPSeconds,
                connectionTimeoutTCPMillis, peerConnection);
        return futureResponse;
    }

    @Override
    public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) throws Exception {
        LOG.debug("Error originating from: {}, cause {}", futureResponse.request(), cause);
        if (futureResponse.isCompleted()) {
            LOG.warn("Got exception, but ignored (future response completed): {}", futureResponse.failedReason());
        } else {
            if (LOG.isDebugEnabled()) {
                LOG.debug("exception caugth, but handled properly: " + cause.toString());
            }
            if (cause instanceof PeerException) {
                PeerException pe = (PeerException) cause;
                if (pe.abortCause() != PeerException.AbortCause.USER_ABORT) {
                    // do not force if we ran into a timeout, the peer may be
                    // busy
                    synchronized (peerBean.peerStatusListeners()) {
                        for (PeerStatusListener peerStatusListener : peerBean.peerStatusListeners()) {
                            peerStatusListener.peerFailed(futureResponse.request().recipient(), pe);
                        }
                    }
                    LOG.debug("removed from map, cause: {} msg: {}", pe.toString(), message);
                } else {
                    LOG.warn("error in request", cause);
                }
            } else {
                synchronized (peerBean.peerStatusListeners()) {
                    for (PeerStatusListener peerStatusListener : peerBean.peerStatusListeners()) {
                        peerStatusListener.peerFailed(futureResponse.request().recipient(),
                                new PeerException(cause));
                    }
                }
            }
        }

        LOG.debug("report failure", cause);
        futureResponse.failedLater(cause);
        ctx.close();
    }

    @Override
    protected void channelRead0(final ChannelHandlerContext ctx, final Message responseMessage) throws Exception {
        MessageID recvMessageID = new MessageID(responseMessage);
        // Error handling
        if (responseMessage.type() == Message.Type.UNKNOWN_ID) {
            String msg = "Message was not delivered successfully, unknow id (peer may be offline or unknown RPC handler): "
                    + this.message;
            exceptionCaught(ctx, new PeerException(PeerException.AbortCause.PEER_ABORT, msg));
            return;
        } else if (responseMessage.type() == Message.Type.EXCEPTION) {
            String msg = "Message caused an exception on the other side, handle as peer_abort: " + this.message;
            exceptionCaught(ctx, new PeerException(PeerException.AbortCause.PEER_ABORT, msg));
            return;
        } else if (responseMessage.isRequest()) {
            ctx.fireChannelRead(responseMessage);
            return;
        } else if (!sendMessageID.equals(recvMessageID)) {
            String msg = "Message [" + responseMessage
                    + "] sent to the node is not the same as we expect. We sent [" + this.message + "]";
            exceptionCaught(ctx, new PeerException(PeerException.AbortCause.PEER_ABORT, msg));
            return;
        } else if (responseMessage.command() != RPC.Commands.RCON.getNr()
                && message.recipient().isRelayed() != responseMessage.sender().isRelayed()) {
            String msg = "Message [" + responseMessage + "] sent has a different relay flag than we sent ["
                    + this.message + "]. Recipient (" + message.recipient().isRelayed() + ") / Sender ("
                    + responseMessage.sender().isRelayed() + ")";
            exceptionCaught(ctx, new PeerException(PeerException.AbortCause.PEER_ABORT, msg));
            return;
        }

        //NAT reflection, reverse lookup
        PeerAddress realAddress = peerBean.localMap().translateReverse(responseMessage.sender());
        if (realAddress != null) {
            responseMessage.sender(realAddress);
        }

        // Stop time measurement of RTT
        futureResponse.stopRTTMeasurement();

        // We got a good answer, let's mark the sender as alive
        //if its an announce, the peer status will be handled in the RPC
        if (responseMessage.command() != RPC.Commands.LOCAL_ANNOUNCE.getNr()
                && (responseMessage.isOk() || responseMessage.isNotOk())) {
            peerBean.notifyPeerFound(responseMessage.sender(), null, null, futureResponse.getRoundTripTime());
        }

        // call this for streaming support
        if (!responseMessage.isDone()) {
            LOG.debug("good message is streaming {}", responseMessage);
            return;
        }

        // support slow, unreachable devices which cannot respond instantly
        if (this.message.recipient().isRelayed() && this.message.recipient().isSlow()
                && responseMessage.type() == Message.Type.PARTIALLY_OK) {
            LOG.debug("Received partially ok by the relay peer. Wait for answer of the unreachable peer.");
            // wait for the (real) answer of the unreachable peer.
            connectionBean.dispatcher().addPendingRequest(message.messageId(), futureResponse,
                    slowResponseTimeoutSeconds, connectionBean.timer());
            // close the channel to the relay peer
            ctx.close();
            return;
        }

        if (!message.isKeepAlive()) {
            LOG.debug("good message, we can close {}, {}", responseMessage, ctx.channel());
            //set the success now, but trigger the notify when we closed the channel.
            futureResponse.responseLater(responseMessage);
            //the channel creater adds a listener that sets futureResponse.setResponseNow, when the channel is closed
            ctx.close();
        } else {
            LOG.debug("good message, leave open {}", responseMessage);
            futureResponse.response(responseMessage);
        }
    }
}