Package net.tomp2p.relay

Source Code of net.tomp2p.relay.DistributedRelay

package net.tomp2p.relay;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReferenceArray;

import net.tomp2p.connection.ConnectionConfiguration;
import net.tomp2p.connection.PeerConnection;
import net.tomp2p.futures.BaseFutureAdapter;
import net.tomp2p.futures.FutureDone;
import net.tomp2p.futures.FutureForkJoin;
import net.tomp2p.futures.FuturePeerConnection;
import net.tomp2p.futures.FutureResponse;
import net.tomp2p.message.Message;
import net.tomp2p.message.Message.Type;
import net.tomp2p.p2p.Peer;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.peers.PeerSocketAddress;
import net.tomp2p.relay.android.AndroidRelayConnection;
import net.tomp2p.relay.android.GCMServerCredentials;
import net.tomp2p.relay.tcp.OpenTCPRelayConnection;
import net.tomp2p.rpc.RPC;

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

/**
* The relay manager is responsible for setting up and maintaining connections
* to relay peers and contains all information about the relays.
*
* @author Raphael Voellmy
* @author Thomas Bocek
* @author Nico Rutishauser
*
*/
public class DistributedRelay {

  private final static Logger LOG = LoggerFactory.getLogger(DistributedRelay.class);

  private final Peer peer;
  private final RelayRPC relayRPC;
  private final ConnectionConfiguration config;

  final private List<BaseRelayConnection> relays;
  final private Set<PeerAddress> failedRelays;

  private final Collection<RelayListener> relayListeners;
  private final RelayType relayType;
  private final GCMServerCredentials gcmServerCredentials;
  private final int mapUpdateInterval;

  /**
   * @param peer
   *            the unreachable peer
   * @param relayRPC
   *            the relay RPC
   * @param maxRelays
   *            maximum number of relay peers to set up
   * @param relayType
   *            the kind of the relay connection
   */
  public DistributedRelay(final Peer peer, RelayRPC relayRPC, int failedRelayWaitTime, RelayType relayType,
      GCMServerCredentials gcmServerCredentials, int mapUpdateInterval, ConnectionConfiguration config) {
    this.peer = peer;
    this.relayRPC = relayRPC;
    this.relayType = relayType;
    this.gcmServerCredentials = gcmServerCredentials;
    this.mapUpdateInterval = mapUpdateInterval;
    this.config = config;

    relays = Collections.synchronizedList(new ArrayList<BaseRelayConnection>());
    failedRelays = new ConcurrentCacheSet<PeerAddress>(failedRelayWaitTime);
    relayListeners = Collections.synchronizedList(new ArrayList<RelayListener>(1));
  }

  /**
   * Returns connections to current relay peers
   *
   * @return List of PeerAddresses of the relay peers (copy)
   */
  public List<BaseRelayConnection> relays() {
    synchronized (relays) {
      // make a copy
      return Collections.unmodifiableList(new ArrayList<BaseRelayConnection>(relays));
    }
  }

  public void addRelayListener(RelayListener relayListener) {
    synchronized (relayListeners) {
      relayListeners.add(relayListener);
    }
  }

  public FutureForkJoin<FutureDone<Void>> shutdown() {
    final AtomicReferenceArray<FutureDone<Void>> futureDones;
    synchronized (relays) {
      futureDones = new AtomicReferenceArray<FutureDone<Void>>(relays.size());
      for (int i = 0; i < relays.size(); i++) {
        futureDones.set(i, relays.get(i).shutdown());
      }
    }

    synchronized (relayListeners) {
      relayListeners.clear();
    }

    return new FutureForkJoin<FutureDone<Void>>(futureDones);
  }

  /**
   * Sets up relay connections to other peers. The number of relays to set up
   * is determined by {@link PeerAddress#MAX_RELAYS} or passed to the
   * constructor of this class. It is important that we call this after we
   * bootstrapped and have recent information in our peer map.
   *
   * @return RelayFuture containing a {@link DistributedRelay} instance
   */
  public FutureRelay setupRelays(final FutureRelay futureRelay, final Collection<PeerAddress> manualRelays,
      final int maxFail) {
    final List<PeerAddress> relayCandidates;
    if (manualRelays.isEmpty()) {
      // Get the neighbors of this peer that could possibly act as relays. Relay
      // candidates are neighboring peers that are not relayed themselves and have
      // not recently failed as relay or denied acting as relay.
      relayCandidates = peer.distributedRouting().peerMap().all();
      // remove those who we know have failed
      relayCandidates.removeAll(failedRelays);
    } else {
      // if the user sets manual relays, the failed relays are not removed, as this has to be done by
      // the user
      relayCandidates = new ArrayList<PeerAddress>(manualRelays);
    }

    filterRelayCandidates(relayCandidates);
    setupPeerConnections(futureRelay, relayCandidates, maxFail);
    return futureRelay;
  }

  /**
   * Remove recently failed relays, peers that are relayed themselves and
   * peers that are already relays
   */
  private void filterRelayCandidates(Collection<PeerAddress> relayCandidates) {
    for (Iterator<PeerAddress> iterator = relayCandidates.iterator(); iterator.hasNext();) {
      PeerAddress pa = iterator.next();

      // filter peers that are relayed themselves
      if (pa.isRelayed()) {
        iterator.remove();
        continue;
      }

      // filter relays that are already connected
      synchronized (relays) {
        for (BaseRelayConnection relay : relays) {
          if (relay.relayAddress().equals(pa)) {
            iterator.remove();
            break;
          }
        }
      }
    }

    LOG.trace("Found {} addtional relay candidates", relayCandidates.size());
  }

  /**
   * Sets up N peer connections to relay candidates, where N is maxRelays
   * minus the current relay count.
   *
   * @param cc
   * @return FutureDone
   */
  private void setupPeerConnections(final FutureRelay futureRelay, List<PeerAddress> relayCandidates, final int maxFail) {
    final int nrOfRelays = Math.min(relayType.maxRelayCount() - relays.size(), relayCandidates.size());
    if (nrOfRelays > 0) {
      LOG.debug("Setting up {} relays", nrOfRelays);

      @SuppressWarnings("unchecked")
      FutureDone<PeerConnection>[] futureDones = new FutureDone[nrOfRelays];
      AtomicReferenceArray<FutureDone<PeerConnection>> relayConnectionFutures = new AtomicReferenceArray<FutureDone<PeerConnection>>(
          futureDones);
      setupPeerConnectionsRecursive(relayConnectionFutures, relayCandidates, nrOfRelays, futureRelay, 0, maxFail,
          new StringBuilder());
    } else {
      if (relayCandidates.isEmpty()) {
        // no candidates
        futureRelay.failed("done");
      } else {
        // nothing to do
        futureRelay.done(Collections.<BaseRelayConnection> emptyList());
      }
    }
  }

  /**
   * Sets up connections to relay peers recursively. If the maximum number of
   * relays is already reached, this method will do nothing.
   *
   * @param futureRelayConnections
   * @param relayCandidates
   *            List of peers that could act as relays
   * @param numberOfRelays
   *            The number of relays to establish.
   * @param futureDone
   * @return
   * @throws InterruptedException
   */
  private void setupPeerConnectionsRecursive(final AtomicReferenceArray<FutureDone<PeerConnection>> futures,
      final List<PeerAddress> relayCandidates, final int numberOfRelays, final FutureRelay futureRelay,
      final int fail, final int maxFail, final StringBuilder status) {
    int active = 0;
    for (int i = 0; i < numberOfRelays; i++) {
      if (futures.get(i) == null) {
        PeerAddress candidate = null;
        synchronized (relayCandidates) {
          if (!relayCandidates.isEmpty()) {
            candidate = relayCandidates.remove(0);
          }
        }
        if (candidate != null) {
          // contact the candiate and ask for being my relay
          FutureDone<PeerConnection> futureDone = sendMessage(candidate);
          futures.set(i, futureDone);
          active++;
        }
      } else {
        active++;
      }
    }
    if (active == 0) {
      updatePeerAddress();
      futureRelay.failed("No candidates: " + status.toString());
      return;
    } else if (fail > maxFail) {
      updatePeerAddress();
      futureRelay.failed("Maxfail: " + status.toString());
      return;
    }

    FutureForkJoin<FutureDone<PeerConnection>> ffj = new FutureForkJoin<FutureDone<PeerConnection>>(active, false,
        futures);
    ffj.addListener(new BaseFutureAdapter<FutureForkJoin<FutureDone<PeerConnection>>>() {
      public void operationComplete(FutureForkJoin<FutureDone<PeerConnection>> futureForkJoin) throws Exception {
        if (futureForkJoin.isSuccess()) {
          updatePeerAddress();
          futureRelay.done(relays());
        } else if (!peer.isShutdown()) {
          setupPeerConnectionsRecursive(futures, relayCandidates, numberOfRelays, futureRelay, fail + 1, maxFail,
              status.append(futureForkJoin.failedReason()).append(" "));
        } else {
          futureRelay.failed(futureForkJoin);
        }
      }
    });
  }

  /**
   * Send the setup-message to the relay peer. If the peer that is asked to act as
   * relay is relayed itself, the request will be denied.
   *
   * @param candidate the relay's peer address
   * @return FutureDone with a peer connection to the newly set up relay peer
   */
  private FutureDone<PeerConnection> sendMessage(final PeerAddress candidate) {
    final FutureDone<PeerConnection> futureDone = new FutureDone<PeerConnection>();

    final Message message = relayRPC.createMessage(candidate, RPC.Commands.RELAY.getNr(), Type.REQUEST_1);

    // depend on the relay type whether to keep the connection open or close it after the setup.
    message.keepAlive(relayType.keepConnectionOpen());

    // encode the relay type in the message such that the relay node knows how to handle
    message.intValue(relayType.ordinal());

    // server credentials only used by Android peers
    if (relayType == RelayType.ANDROID) {
      if (gcmServerCredentials == null) {
        LOG.error("No available GCM server found. Seems that they are all occupied. Configure more during peer setup!");
        return futureDone.failed("No GCM server available");
      } else if(!gcmServerCredentials.valid()) {
        LOG.error("GCM Server Configuration is not valid. Please provide a valid configuration");
        return futureDone.failed("Invalid GCM configuration");
      } else {
        // add the registration ID, the GCM authentication key and the map update interval
        message.buffer(RelayUtils.encodeString(gcmServerCredentials.registrationId()));
        message.buffer(RelayUtils.encodeString(gcmServerCredentials.senderAuthenticationKey()));
        message.intValue(mapUpdateInterval);
      }
    }

    LOG.debug("Setting up relay connection to peer {}, message {}", candidate, message);
    final FuturePeerConnection fpc = peer.createPeerConnection(candidate);
    fpc.addListener(new BaseFutureAdapter<FuturePeerConnection>() {
      public void operationComplete(final FuturePeerConnection futurePeerConnection) throws Exception {
        if (futurePeerConnection.isSuccess()) {
          // successfully created a connection to the relay peer
          final PeerConnection peerConnection = futurePeerConnection.object();

          // send the message
          FutureResponse response = RelayUtils.send(peerConnection, peer.peerBean(), peer.connectionBean(),
              config, message);
          response.addListener(new BaseFutureAdapter<FutureResponse>() {
            public void operationComplete(FutureResponse future) throws Exception {
              if (future.isSuccess()) {
                // finialize the relay setup
                setupAddRelays(candidate, peerConnection, gcmServerCredentials);
                futureDone.done(peerConnection);
              } else {
                LOG.debug("Peer {} denied relay request", candidate);
                failedRelays.add(candidate);
                futureDone.failed(future);
              }
            }
          });
        } else {
          LOG.debug("Unable to setup a connection to relay peer {}", candidate);
          failedRelays.add(candidate);
          futureDone.failed(futurePeerConnection);
        }
      }
    });

    return futureDone;
  }

  /**
   * Is called when the setup with the relay worked. Adds the relay to the list.
   */
  private void setupAddRelays(PeerAddress relayAddress, PeerConnection peerConnection, GCMServerCredentials credentials) {
    synchronized (relays) {
      if (relays.size() >= relayType.maxRelayCount()) {
        LOG.warn("The maximum number ({}) of relays is reached", relayType.maxRelayCount());
        return;
      }
    }

    BaseRelayConnection connection = null;
    switch (relayType) {
      case OPENTCP:
        connection = new OpenTCPRelayConnection(peerConnection, peer, config);
        break;
      case ANDROID:
        connection = new AndroidRelayConnection(relayAddress, relayRPC, peer, config, credentials);
        break;
      default:
        LOG.error("Unknown relay type {}", relayType);
        return;
    }

    addCloseListener(connection);

    synchronized (relays) {
      LOG.debug("Adding peer {} as a relay", relayAddress);
      relays.add(connection);
    }
  }

  /**
   * Adds a close listener for an open peer connection, so that if the
   * connection to the relay peer drops, a new relay is found and a new relay
   * connection is established
   *
   * @param connection
   *            the relay connection on which to add a close listener
   */
  private void addCloseListener(final BaseRelayConnection connection) {
    connection.addCloseListener(new RelayListener() {
      @Override
      public void relayFailed(PeerAddress relayAddress) {
        // used to remove a relay peer from the unreachable peers
        // peer address. It will <strong>not</strong> cut the
        // connection to an existing peer, but only update the
        // unreachable peer's PeerAddress if a relay peer failed.
        // It will also cancel the {@link PeerMapUpdateTask}
        // maintenance task if the last relay is removed.
        relays.remove(connection);
        failedRelays.add(relayAddress);
        updatePeerAddress();

        synchronized (relayListeners) {
          for (RelayListener relayListener : relayListeners) {
            relayListener.relayFailed(relayAddress);
          }
        }
      }
    });
  }

  /**
   * Updates the peer's PeerAddress: Adds the relay addresses to the peer
   * address, updates the firewalled flags, and bootstraps to announce its new
   * relay peers.
   */
  private void updatePeerAddress() {
    // add relay addresses to peer address
    boolean hasRelays = !relays.isEmpty();

    Collection<PeerSocketAddress> socketAddresses = new ArrayList<PeerSocketAddress>(relays.size());
    synchronized (relays) {
      for (BaseRelayConnection relay : relays) {
        PeerAddress pa = relay.relayAddress();
        socketAddresses.add(new PeerSocketAddress(pa.inetAddress(), pa.tcpPort(), pa.udpPort()));
      }
    }

    // update firewalled and isRelayed flags
    PeerAddress newAddress = peer.peerAddress().changeFirewalledTCP(!hasRelays).changeFirewalledUDP(!hasRelays)
        .changeRelayed(hasRelays).changePeerSocketAddresses(socketAddresses).changeSlow(hasRelays && relayType.isSlow());
    peer.peerBean().serverPeerAddress(newAddress);
    LOG.debug("Updated peer address {}, isrelay = {}", newAddress, hasRelays);
  }
}
TOP

Related Classes of net.tomp2p.relay.DistributedRelay

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.