/*
* Copyright 2012 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.replication;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.SortedSet;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.peers.PeerMap;
import net.tomp2p.peers.PeerMapChangeListener;
import net.tomp2p.peers.PeerStatatistic;
import net.tomp2p.storage.ReplicationStorage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class has 3 methods that are called from outside eventes: check, peerInsert, peerRemoved.
*/
public class Replication implements PeerMapChangeListener {
private static final Logger LOG = LoggerFactory.getLogger(Replication.class);
private final List<ResponsibilityListener> listeners = new ArrayList<ResponsibilityListener>();
private final PeerMap peerMap;
private final PeerAddress selfAddress;
private final ReplicationStorage replicationStorage;
private int replicationFactor;
/**
* Constructor.
*
* @param replicationStorage
* The interface where we shore the replication responsibilities
* @param selfAddress
* My address to know for what my peer is responsible
* @param peerMap
* The map of my neighbors
* @param replicationFactor
* The replication factor
*/
public Replication(final ReplicationStorage replicationStorage, final PeerAddress selfAddress,
final PeerMap peerMap, final int replicationFactor) {
this.replicationStorage = replicationStorage;
this.selfAddress = selfAddress;
this.peerMap = peerMap;
this.replicationFactor = replicationFactor;
peerMap.addPeerMapChangeListener(this);
}
/**
*
* @param replicationFactor
* Set the replication factor
*/
public void setReplicationFactor(final int replicationFactor) {
this.replicationFactor = replicationFactor;
}
/**
*
* @return The replication factor.
*/
public int getReplicationFactor() {
return replicationFactor;
}
/**
* Checks if the user enabled replication.
*
* @return True if replication is enabled.
*/
public boolean isReplicationEnabled() {
return listeners.size() > 0;
}
/**
* Add responsibility listener. If the first listener is added, replication is considered enabled.
*
* @param responsibilityListener
* The responsibility listener.
*/
public void addResponsibilityListener(final ResponsibilityListener responsibilityListener) {
listeners.add(responsibilityListener);
}
/**
* Remove a responsibility listener. If all responsibility listeners are removed, replication is considered
* disabled.
*
* @param responsibilityListener
* The responsibility listener.
*/
public void removeResponsibilityListener(final ResponsibilityListener responsibilityListener) {
listeners.remove(responsibilityListener);
}
/**
* Notify if I'm responsible and something needs to change, i.e., to make sure that there are enough replicas.
*
* @param locationKey
* The location key.
*/
private void notifyMeResponsible(final Number160 locationKey) {
for (ResponsibilityListener responsibilityListener : listeners) {
responsibilityListener.meResponsible(locationKey);
}
}
/**
* Notify if an other peer is responsible and we should transfer data to this peer.
*
* @param locationKey
* The location key.
* @param other
* The other peer.
*/
private void notifyOtherResponsible(final Number160 locationKey, final PeerAddress other) {
for (ResponsibilityListener responsibilityListener : listeners) {
responsibilityListener.otherResponsible(locationKey, other);
}
}
/**
* Update responsibilities. This happens for a put / add.
*
* @param locationKey
* The location key.
*/
public void updateAndNotifyResponsibilities(final Number160 locationKey) {
if (!isReplicationEnabled()) {
return;
}
PeerAddress closest = closest(locationKey);
if (closest.getPeerId().equals(selfAddress.getPeerId())) {
if (replicationStorage.updateResponsibilities(locationKey, closest.getPeerId())) {
// I am responsible for this content
notifyMeResponsible(locationKey);
}
} else {
if (replicationStorage.updateResponsibilities(locationKey, closest.getPeerId())) {
// notify that someone else is now responsible for the
// content with key responsibleLocations
notifyOtherResponsible(locationKey, closest);
}
}
}
@Override
public void peerInserted(final PeerAddress peerAddress, final boolean verified) {
if (!isReplicationEnabled() || !verified) {
return;
}
LOG.debug("The peer {} was inserted in my map. I'm {}", peerAddress, selfAddress);
// check if we should change responsibility.
Collection<Number160> myResponsibleLocations = replicationStorage.findContentForResponsiblePeerID(selfAddress
.getPeerId());
LOG.debug("I ({}) am currently responsibel for {}", selfAddress, myResponsibleLocations);
for (Number160 myResponsibleLocation : myResponsibleLocations) {
PeerAddress closest = closest(myResponsibleLocation);
if (!closest.getPeerId().equals(selfAddress.getPeerId())) {
if (replicationStorage.updateResponsibilities(myResponsibleLocation, closest.getPeerId())) {
// notify that someone else is now responsible for the
// content with key responsibleLocations
notifyOtherResponsible(myResponsibleLocation, closest);
}
} else if (isInReplicationRange(myResponsibleLocation, peerAddress, replicationFactor)) {
// we are still responsible, but a new peer joined and if it is within the x close peers, we need to
// replicate
if (replicationStorage.updateResponsibilities(myResponsibleLocation, peerAddress.getPeerId())) {
notifyOtherResponsible(myResponsibleLocation, peerAddress);
}
}
}
}
@Override
public void peerRemoved(final PeerAddress peerAddress, final PeerStatatistic peerStatatistic) {
if (!isReplicationEnabled()) {
return;
}
// check if we should change responsibility.
Collection<Number160> otherResponsibleLocations = replicationStorage
.findContentForResponsiblePeerID(peerAddress.getPeerId());
Collection<Number160> myResponsibleLocations = replicationStorage.findContentForResponsiblePeerID(selfAddress
.getPeerId());
// check if we are now responsible for content where the other peer was responsible
for (Number160 otherResponsibleLocation : otherResponsibleLocations) {
PeerAddress closest = closest(otherResponsibleLocation);
if (closest.getPeerId().equals(selfAddress.getPeerId())) {
if (replicationStorage.updateResponsibilities(otherResponsibleLocation, closest.getPeerId())) {
// notify that someone I'm now responsible for the
// content
// with key responsibleLocations
notifyMeResponsible(otherResponsibleLocation);
// we don't need to check this again, so remove it from the list if present
myResponsibleLocations.remove(otherResponsibleLocation);
}
} else {
replicationStorage.updateResponsibilities(otherResponsibleLocation, closest.getPeerId());
}
}
// now check for our responsibilities. If a peer is gone and it was in the replication range, we need make sure
// we have enough copies
for (Number160 myResponsibleLocation : myResponsibleLocations) {
if (isInReplicationRange(myResponsibleLocation, peerAddress, replicationFactor)) {
notifyMeResponsible(myResponsibleLocation);
}
}
}
@Override
public void peerUpdated(final PeerAddress peerAddress, final PeerStatatistic peerStatatistic) {
// do nothing
}
/**
* Returns the closest peer to a number (including myself).
*
* @param locationKey
* The locationKey
* @return The peer that is responsible for the location key, including myself.
*/
private PeerAddress closest(final Number160 locationKey) {
SortedSet<PeerAddress> tmp = peerMap.closePeers(locationKey, 1);
tmp.add(selfAddress);
return tmp.iterator().next();
}
/**
* Checks if a peeraddress is within the replication range. This means that the peer should also receive a replica.
*
* @param locationKey
* The locationKey
* @param peerAddress
* The peeraddress to check if its within the replication range
* @param replicationFactor
* The replication factor
*
* @return True if the peer is within replication range, otherwise false.
*/
private boolean isInReplicationRange(final Number160 locationKey, final PeerAddress peerAddress,
final int replicationFactor) {
SortedSet<PeerAddress> tmp = peerMap.closePeers(locationKey, replicationFactor);
return tmp.headSet(peerAddress).size() < replicationFactor;
}
}