/*
* 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.p2p;
import io.netty.util.ResourceLeakDetector;
import io.netty.util.Timer;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import net.tomp2p.connection2.Bindings;
import net.tomp2p.connection2.ChannelCreator;
import net.tomp2p.connection2.ConnectionBean;
import net.tomp2p.connection2.PeerBean;
import net.tomp2p.connection2.PeerConnection;
import net.tomp2p.connection2.PeerCreator;
import net.tomp2p.futures.BaseFuture;
import net.tomp2p.futures.BaseFutureAdapter;
import net.tomp2p.futures.FutureChannelCreator;
import net.tomp2p.futures.FutureDone;
import net.tomp2p.futures.FuturePeerConnection;
import net.tomp2p.natpmp.NatPmpException;
import net.tomp2p.p2p.builder.AddBuilder;
import net.tomp2p.p2p.builder.AddTrackerBuilder;
import net.tomp2p.p2p.builder.BootstrapBuilder;
import net.tomp2p.p2p.builder.BroadcastBuilder;
import net.tomp2p.p2p.builder.DiscoverBuilder;
import net.tomp2p.p2p.builder.GetBuilder;
import net.tomp2p.p2p.builder.GetTrackerBuilder;
import net.tomp2p.p2p.builder.ParallelRequestBuilder;
import net.tomp2p.p2p.builder.PingBuilder;
import net.tomp2p.p2p.builder.PutBuilder;
import net.tomp2p.p2p.builder.RemoveBuilder;
import net.tomp2p.p2p.builder.SendBuilder;
import net.tomp2p.p2p.builder.SendDirectBuilder;
import net.tomp2p.p2p.builder.ShutdownBuilder;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.rpc.BroadcastRPC;
import net.tomp2p.rpc.DirectDataRPC;
import net.tomp2p.rpc.PingRPC;
import net.tomp2p.rpc.NeighborRPC;
import net.tomp2p.rpc.ObjectDataReply;
import net.tomp2p.rpc.PeerExchangeRPC;
import net.tomp2p.rpc.QuitRPC;
import net.tomp2p.rpc.RawDataReply;
import net.tomp2p.rpc.StorageRPC;
//import net.tomp2p.rpc.TaskRPC;
import net.tomp2p.rpc.TrackerRPC;
//import net.tomp2p.task.AsyncTask;
//import net.tomp2p.task.Worker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This is the main class to start DHT operations. This class makes use of the build pattern and for each DHT operation,
* a builder class is returned. The main operations can be initiated with {@link #put(Number160)},
* {@link #get(Number160)}, {@link #add(Number160)}, {@link #addTracker(Number160)}, {@link #getTracker(Number160)},
* {@link #remove(Number160)}, {@link #submit(Number160, Worker)}, {@link #send(Number160)}, {@link #sendDirect()},
* {@link #broadcast(Number160)}. Each of those operations return a builder that offers more options. One of the main
* difference to a "regular" DHT is that TomP2P can store a map (key-values) instead of just values. To distinguish
* those, the keys are termed location key (for finding the right peer in the network), and content key (to store more
* than one value on a peer). For the put builder e.g. the following options can be set:
*
* <ul>
* <li>{@link PutBuilder#setData(Number160, net.tomp2p.storage.Data)} - puts a content key with a value</li>
* <li>{@link PutBuilder#setDataMap(Map) - puts multiple content key / values at once</li>
* <li>{@link PutBuilder#setPutIfAbsent() - only puts data if its not already on the peers</li>
* <li>...</li>
* </ul>
*
* Furthermore, TomP2P also provides to store keys in different domains to avoid key collisions
* ({@link PutBuilder#setDomainKey(Number160)).
*
* @author Thomas Bocek
*/
public class Peer {
// domain used if no domain provided
private static final Logger LOG = LoggerFactory.getLogger(Peer.class);
// As soon as the user calls listen, this connection handler is set
private final PeerCreator peerCreator;
// the id of this node
private final Number160 peerId;
// the p2p network identifier, two different networks can have the same
// ports
private final int p2pID;
// Distributed
private DistributedHashTable distributedHashMap;
private DistributedTracker distributedTracker;
private DistributedRouting distributedRouting;
//private DistributedTask distributedTask;
// private AsyncTask asyncTask;
// RPC
private PingRPC handshakeRCP;
private StorageRPC storageRPC;
private NeighborRPC neighborRPC;
private QuitRPC quitRCP;
private PeerExchangeRPC peerExchangeRPC;
private DirectDataRPC directDataRPC;
private TrackerRPC trackerRPC;
// private TaskRPC taskRPC;
private BroadcastRPC broadcastRPC;
//
private boolean shutdown = false;
private List<AutomaticFuture> automaticFutures = null;
//
// final private ConnectionConfiguration configuration;
// final private Map<BaseFuture, Long> pendingFutures = Collections.synchronizedMap(new CacheMap<BaseFuture, Long>(
// 1000, true));
// private boolean masterFlag = true;
// private List<ScheduledFuture<?>> scheduledFutures = Collections
// .synchronizedList(new ArrayList<ScheduledFuture<?>>());
// final private List<PeerListener> listeners = new ArrayList<PeerListener>();
// private Timer timer;
// final public static int BLOOMFILTER_SIZE = 1024;
//
// final private int maintenanceThreads;
// final private int replicationThreads;
// final private int replicationRefreshMillis;
// final private PeerMap peerMap;
// final private int maxMessageSize;
// private volatile boolean shutdown = false;
/**
* Create a peer. Please use {@link PeerMaker} to create a class
*
* @param p2pID
* The P2P ID
* @param peerId
* The Id of the peer
* @param peerCreator
* The peer creator that holds the peer bean and connection bean
*/
Peer(final int p2pID, final Number160 peerId, final PeerCreator peerCreator) {
this.p2pID = p2pID;
this.peerId = peerId;
this.peerCreator = peerCreator;
ResourceLeakDetector.setEnabled(false);
}
PeerCreator peerCreator() {
return peerCreator;
}
public PingRPC getHandshakeRPC() {
if (handshakeRCP == null) {
throw new RuntimeException("Not enabled, please enable this RPC in PeerMaker");
}
return handshakeRCP;
}
public void setHandshakeRPC(PingRPC handshakeRPC) {
this.handshakeRCP = handshakeRPC;
}
public StorageRPC getStoreRPC() {
if (storageRPC == null) {
throw new RuntimeException("Not enabled, please enable this RPC in PeerMaker");
}
return storageRPC;
}
public void setStorageRPC(StorageRPC storageRPC) {
this.storageRPC = storageRPC;
}
public NeighborRPC getNeighborRPC() {
if (neighborRPC == null) {
throw new RuntimeException("Not enabled, please enable this RPC in PeerMaker");
}
return neighborRPC;
}
public void setNeighborRPC(NeighborRPC neighborRPC) {
this.neighborRPC = neighborRPC;
}
public QuitRPC getQuitRPC() {
if (quitRCP == null) {
throw new RuntimeException("Not enabled, please enable this RPC in PeerMaker");
}
return quitRCP;
}
public void setQuitRPC(QuitRPC quitRCP) {
this.quitRCP = quitRCP;
}
public PeerExchangeRPC getPeerExchangeRPC() {
if (peerExchangeRPC == null) {
throw new RuntimeException("Not enabled, please enable this RPC in PeerMaker");
}
return peerExchangeRPC;
}
public void setPeerExchangeRPC(PeerExchangeRPC peerExchangeRPC) {
this.peerExchangeRPC = peerExchangeRPC;
}
public DirectDataRPC getDirectDataRPC() {
if (directDataRPC == null) {
throw new RuntimeException("Not enabled, please enable this RPC in PeerMaker");
}
return directDataRPC;
}
public void setDirectDataRPC(DirectDataRPC directDataRPC) {
this.directDataRPC = directDataRPC;
}
public TrackerRPC getTrackerRPC() {
if (trackerRPC == null) {
throw new RuntimeException("Not enabled, please enable this RPC in PeerMaker");
}
return trackerRPC;
}
public void setTrackerRPC(TrackerRPC trackerRPC) {
this.trackerRPC = trackerRPC;
}
/*
* public TaskRPC getTaskRPC() { if (taskRPC == null) { throw new
* RuntimeException("Not enabled, please enable this RPC in PeerMaker"); } return taskRPC; }
*
* public void setTaskRPC(TaskRPC taskRPC) { this.taskRPC = taskRPC; }
*/
public void setBroadcastRPC(BroadcastRPC broadcastRPC) {
this.broadcastRPC = broadcastRPC;
}
public BroadcastRPC getBroadcastRPC() {
if (broadcastRPC == null) {
throw new RuntimeException("Not enabled, please enable this RPC in PeerMaker");
}
return broadcastRPC;
}
public DistributedRouting getDistributedRouting() {
if (distributedRouting == null) {
throw new RuntimeException("Not enabled, please enable this P2P function in PeerMaker");
}
return distributedRouting;
}
public void setDistributedRouting(DistributedRouting distributedRouting) {
this.distributedRouting = distributedRouting;
}
public DistributedHashTable getDistributedHashMap() {
if (distributedHashMap == null) {
throw new RuntimeException("Not enabled, please enable this P2P function in PeerMaker");
}
return distributedHashMap;
}
public void setDistributedHashMap(DistributedHashTable distributedHashMap) {
this.distributedHashMap = distributedHashMap;
}
public DistributedTracker getDistributedTracker() {
if (distributedTracker == null) {
throw new RuntimeException("Not enabled, please enable this P2P function in PeerMaker");
}
return distributedTracker;
}
public void setDistributedTracker(DistributedTracker distributedTracker) {
this.distributedTracker = distributedTracker;
}
/*
* public AsyncTask getAsyncTask() { if (asyncTask == null) { throw new
* RuntimeException("Not enabled, please enable this RPC in PeerMaker"); } return asyncTask; }
*
* public void setAsyncTask(AsyncTask asyncTask) { this.asyncTask = asyncTask; }
*/
/*public DistributedTask getDistributedTask() {
if (distributedTask == null) {
throw new RuntimeException("Not enabled, please enable this P2P function in PeerMaker");
}
return distributedTask;
}
public void setDistributedTask(DistributedTask task) {
this.distributedTask = task;
}*/
public PeerBean getPeerBean() {
return peerCreator.peerBean();
}
public ConnectionBean getConnectionBean() {
return peerCreator.connectionBean();
}
public Number160 getPeerID() {
return peerId;
}
public int getP2PID() {
return p2pID;
}
public PeerAddress getPeerAddress() {
return getPeerBean().serverPeerAddress();
}
Peer setAutomaticFutures(List<AutomaticFuture> automaticFutures) {
this.automaticFutures = Collections.unmodifiableList(automaticFutures);
return this;
}
//TODO: expose this
public Peer notifyAutomaticFutures(BaseFuture future) {
if(automaticFutures!=null) {
for(AutomaticFuture automaticFuture:automaticFutures) {
automaticFuture.futureCreated(future);
}
}
return this;
}
// *********************************** DHT / Tracker operations start here
public void setRawDataReply(final RawDataReply rawDataReply) {
getDirectDataRPC().setReply(rawDataReply);
}
public void setObjectDataReply(final ObjectDataReply objectDataReply) {
getDirectDataRPC().setReply(objectDataReply);
}
/**
* Opens a TCP connection and keeps it open. The user can provide the idle timeout, which means that the connection
* gets closed after that time of inactivity. If the other peer goes offline or closes the connection (due to
* inactivity), further requests with this connections reopens the connection. This methods blocks until a
* connection can be reserver.
*
* @param destination
* The end-point to connect to
* @param idleTCPMillis
* time in milliseconds after a connection gets closed if idle, -1 if it should remain always open until
* the user closes the connection manually.
* @return A class that needs to be passed to those methods that should use the already open connection. If the
* connection could not be reserved, maybe due to a shutdown, null is returned.
*/
public FuturePeerConnection createPeerConnection(final PeerAddress destination) {
final FuturePeerConnection futureDone = new FuturePeerConnection(destination);
final FutureChannelCreator fcc = getConnectionBean().reservation().createPermanent(1);
fcc.addListener(new BaseFutureAdapter<FutureChannelCreator>() {
@Override
public void operationComplete(final FutureChannelCreator future) throws Exception {
if (future.isSuccess()) {
final ChannelCreator cc = fcc.getChannelCreator();
final PeerConnection peerConnection = new PeerConnection(destination, cc);
futureDone.setDone(peerConnection);
} else {
futureDone.setFailed(future);
}
}
});
return futureDone;
}
/**
* The Dynamic and/or Private Ports are those from 49152 through 65535
* (http://www.iana.org/assignments/port-numbers).
*
* @param internalHost
* The IP of the internal host
* @return True if port forwarding seemed to be successful
*/
public boolean setupPortForwanding(final String internalHost) {
Bindings bindings = getConnectionBean().channelServer().bindings();
int portUDP = bindings.getOutsideUDPPort();
int portTCP = bindings.getOutsideTCPPort();
boolean success;
try {
success = getConnectionBean().natUtils().mapUPNP(internalHost, getPeerAddress().tcpPort(),
getPeerAddress().udpPort(), portUDP, portTCP);
} catch (IOException e) {
success = false;
}
if (!success) {
if (LOG.isWarnEnabled()) {
LOG.warn("cannot find UPNP devices");
}
try {
success = getConnectionBean().natUtils().mapPMP(getPeerAddress().tcpPort(),
getPeerAddress().udpPort(), portUDP, portTCP);
if (!success) {
if (LOG.isWarnEnabled()) {
LOG.warn("cannot find NAT-PMP devices");
}
}
} catch (NatPmpException e1) {
if (LOG.isWarnEnabled()) {
LOG.warn("cannot find NAT-PMP devices ", e1);
}
}
}
return success;
}
// New API - builder pattern
// --------------------------------------------------
/*
* public SubmitBuilder submit(Number160 locationKey, Worker worker) { return new SubmitBuilder(this, locationKey,
* worker); }
*/
public AddBuilder add(Number160 locationKey) {
return new AddBuilder(this, locationKey);
}
public PutBuilder put(Number160 locationKey) {
return new PutBuilder(this, locationKey);
}
public GetBuilder get(Number160 locationKey) {
return new GetBuilder(this, locationKey);
}
public RemoveBuilder remove(Number160 locationKey) {
return new RemoveBuilder(this, locationKey);
}
/**
* The send method works as follows:
*
* <pre>
* 1. routing: find close peers to the content hash.
* You can control the routing behavior with
* setRoutingConfiguration()
* 2. sending: send the data to the n closest peers.
* N is set via setRequestP2PConfiguration().
* If you want to send it to the closest one, use
* setRequestP2PConfiguration(1, 5, 0)
* </pre>
*
* @param locationKey
* The target hash to search for during the routing process
* @return The send builder that allows to set options
*/
public SendBuilder send(Number160 locationKey) {
return new SendBuilder(this, locationKey);
}
public SendDirectBuilder sendDirect(PeerAddress recipientAddress) {
return new SendDirectBuilder(this, recipientAddress);
}
public SendDirectBuilder sendDirect(FuturePeerConnection recipientConnection) {
return new SendDirectBuilder(this, recipientConnection);
}
public BootstrapBuilder bootstrap() {
return new BootstrapBuilder(this);
}
public PingBuilder ping() {
return new PingBuilder(this);
}
public DiscoverBuilder discover() {
return new DiscoverBuilder(this);
}
public AddTrackerBuilder addTracker(Number160 locationKey) {
return new AddTrackerBuilder(this, locationKey);
}
public GetTrackerBuilder getTracker(Number160 locationKey) {
return new GetTrackerBuilder(this, locationKey);
}
public ParallelRequestBuilder parallelRequest(Number160 locationKey) {
return new ParallelRequestBuilder(this, locationKey);
}
public BroadcastBuilder broadcast(Number160 messageKey) {
return new BroadcastBuilder(this, messageKey);
}
/**
* Sends a friendly shutdown message to my close neighbors in the DHT.
*
* @return A builder for shutdown that runs asynchronous.
*/
public ShutdownBuilder announceShutdown() {
return new ShutdownBuilder(this);
}
/**
* Shuts down everything.
*
* @return The future, when shutdown is completed
*/
public FutureDone<Void> shutdown() {
//prevent the shutdown from being called twice
if (!shutdown) {
shutdown = true;
getConnectionBean().timer().cancel();
return peerCreator.shutdown();
} else {
return new FutureDone<Void>().setFailed("already shutting / shut down");
}
}
/**
* @return True if the peer is about or already has shutdown
*/
public boolean isShutdown() {
return shutdown;
}
}