/***************************************************************************
* *
* References.java *
* ------------------- *
* date : 16.08.2004 *
* copyright : (C) 2004-2008 Distributed and *
* Mobile Systems Group *
* Lehrstuhl fuer Praktische Informatik *
* Universitaet Bamberg *
* http://www.uni-bamberg.de/pi/ *
* email : sven.kaffille@uni-bamberg.de *
* karsten.loesing@uni-bamberg.de *
* *
* *
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* A copy of the license can be found in the license.txt file supplied *
* with this software or at: http://www.gnu.org/copyleft/gpl.html *
* *
***************************************************************************/
package de.uniba.wiai.lspi.chord.service.impl;
import static de.uniba.wiai.lspi.util.logging.Logger.LogLevel.DEBUG;
import static de.uniba.wiai.lspi.util.logging.Logger.LogLevel.INFO;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import de.uniba.wiai.lspi.chord.com.CommunicationException;
import de.uniba.wiai.lspi.chord.com.Entry;
import de.uniba.wiai.lspi.chord.com.Node;
import de.uniba.wiai.lspi.chord.com.Proxy;
import de.uniba.wiai.lspi.chord.data.ID;
import de.uniba.wiai.lspi.chord.data.URL;
import de.uniba.wiai.lspi.util.logging.Logger;
/**
* Stores all remote references of nodes the local node is connected to and
* provides methods for querying and manipulating these references. Makes use of
* one finger table, one successor list, and one predecessor reference.
*
* @author Karsten Loesing
* @version 1.0.5
*/
final class References {
/**
* Object logger.
*/
private Logger logger;
/**
* This node's finger table.
*/
private FingerTable fingerTable = null;
/**
* This node's successor list
*/
private SuccessorList successorList = null;
/**
* This node's predecessor.
*/
private Node predecessor = null;
/**
* This node's local ID.
*/
private ID localID = null;
private URL localURL = null;
private Entries entries;
/**
* Creates an References object which contains no references.
*
* @param locID
* ID of local node. Must not be <code>null</code>.
* @param numberOfEntriesInSuccessorList
* Length of successor list to be created. Must be greater or
* equal 1!
* @param entries
* Reference on this nodes' entries which is passed to creation
* of the successor list. Must not be <code>null</code>.
* @throws IllegalArgumentException
* If any parameters is <code>null</code> or
* if number of entries in successor list is less than 1.
*/
References(ID locID, URL locURL, int numberOfEntriesInSuccessorList, Entries entries) {
if (locURL == null || locID == null || entries == null) {
throw new IllegalArgumentException(
"No parameter of constructor may be null!");
}
if (numberOfEntriesInSuccessorList < 1)
throw new IllegalArgumentException(
"Number of entries in successor list cannot be less than 1! "
+ numberOfEntriesInSuccessorList
+ " is not a valid value!");
this.logger = Logger.getLogger(References.class.getName() + "."
+ locID);
this.logger.debug("Logger initialized.");
// store node id
this.localID = locID;
this.localURL = locURL;
this.entries = entries;
// create empty finger table and successor list
this.fingerTable = new FingerTable(locID, this);
this.successorList = new SuccessorList(locID,
numberOfEntriesInSuccessorList, this, entries);
}
/**
* Determines the closest preceding node for the given ID based on finger
* table, successor list, and predecessor, but without testing the node's
* liveliness.
*
* @param key
* ID to find closest preceding node for.
* @throws NullPointerException
* If ID is <code>null</code>.
* @return Reference on closest preceding node.
*/
final synchronized Node getClosestPrecedingNode(ID key) {
if (key == null) {
NullPointerException e = new NullPointerException(
"ID may not be null!");
this.logger.error("Null pointer", e);
throw e;
}
Map<ID, Node> foundNodes = new HashMap<ID, Node>();
// determine closest preceding reference of finger table
Node closestNodeFT = this.fingerTable.getClosestPrecedingNode(key);
if (closestNodeFT != null) {
foundNodes.put(closestNodeFT.getNodeID(), closestNodeFT);
}
// determine closest preceding reference of successor list
Node closestNodeSL = this.successorList.getClosestPrecedingNode(key);
if (closestNodeSL != null) {
foundNodes.put(closestNodeSL.getNodeID(), closestNodeSL);
}
// predecessor is appropriate only if it precedes the given id
Node predecessorIfAppropriate = null;
if (this.predecessor != null
&& key.isInInterval(this.predecessor.getNodeID(), this.localID)) {
predecessorIfAppropriate = this.predecessor;
foundNodes.put(this.predecessor.getNodeID(), predecessor);
}
// with three references which may be null, there are eight (8) cases we
// have to enumerate...
Node closestNode = null;
List<ID> orderedIDList = new ArrayList<ID>(foundNodes.keySet());
orderedIDList.add(key);
int sizeOfList = orderedIDList.size();
// size of list must be greater than one to not only contain the key.
// if (sizeOfList > 1) {
/*
* Sort list in ascending order
*/
Collections.sort(orderedIDList);
/*
* The list item with one index lower than that of the key must be the
* id of the closest predecessor or the key.
*/
int keyIndex = orderedIDList.indexOf(key);
/*
* As all ids are located on a ring if the key is the first item in the
* list we have to select the last item as predecessor with help of this
* calculation.
*/
int index = (sizeOfList + (keyIndex - 1)) % sizeOfList;
/*
* Get the references to the node from the map of collected nodes.
*/
ID idOfclosestNode = orderedIDList.get(index);
closestNode = foundNodes.get(idOfclosestNode);
if (closestNode == null) {
throw new NullPointerException("closestNode must not be null!");
}
/*
* Following code is too complicated.
*/
// if (closestNodeFT == null) {
// if (closestNodeSL == null) {
// if (predecessorIfAppropriate == null) {
// // no reference is appropriate
// closestNode = null;
// } else {
// // only predecessor is appropriate (case should not occur,
// // but anyway...
// closestNode = predecessorIfAppropriate;
// }
// } else {
// if (predecessorIfAppropriate == null) {
// // only reference of successor list is appropriate
// closestNode = closestNodeSL;
// } else {
// // either predecessor or reference of successor list is
// // appropriate; determine one of both
// if (predecessorIfAppropriate.nodeID.isInInterval(
// closestNodeSL.nodeID, key)) {
// closestNode = predecessorIfAppropriate;
// } else {
// closestNode = closestNodeSL;
// }
// }
// }
// } else {
// if (closestNodeSL == null) {
// if (predecessorIfAppropriate == null) {
// // only reference of finger table is appropriate
// closestNode = closestNodeFT;
// } else {
// // either predecessor or reference of finger table is
// // appropriate; determine one of both
// if (predecessorIfAppropriate.nodeID.isInInterval(
// closestNodeFT.nodeID, key)) {
// closestNode = predecessorIfAppropriate;
// } else {
// closestNode = closestNodeFT;
// }
// }
// } else {
// if (predecessorIfAppropriate == null) {
// // either reference of successor list or reference of finger
// // table is appropriate; determine one of both
// if (closestNodeSL.nodeID.isInInterval(closestNodeFT.nodeID,
// key)) {
// closestNode = closestNodeSL;
// } else {
// closestNode = closestNodeFT;
// }
// } else {
// // either of the three reference is appropriate; determine
// // first one of the references of successor list and finger
// // table is more appropriate and afterwards compare with
// // predecessor
// if (closestNodeSL.nodeID.isInInterval(closestNodeFT.nodeID,
// key)) {
// if (predecessorIfAppropriate.nodeID.isInInterval(
// closestNodeSL.nodeID, key)) {
// closestNode = predecessorIfAppropriate;
// } else {
// closestNode = closestNodeSL;
// }
// } else {
// if (predecessorIfAppropriate.nodeID.isInInterval(
// closestNodeFT.nodeID, key)) {
// closestNode = predecessorIfAppropriate;
// } else {
// closestNode = closestNodeFT;
// }
// }
// }
// }
// }
if (this.logger.isEnabledFor(DEBUG)) {
this.logger.debug("Closest preceding node of ID "
+ key
+ " at node "
+ this.localID.toString()
+ " is "
+ closestNode.getNodeID()
+ " with closestNodeFT="
+ (closestNodeFT == null ? "null" : ""
+ closestNodeFT.getNodeID())
+ " and closestNodeSL="
+ (closestNodeSL == null ? "null" : ""
+ closestNodeSL.getNodeID())
+ " and predecessor (only if it precedes given ID)="
+ (predecessorIfAppropriate == null ? "null" : ""
+ predecessorIfAppropriate.getNodeID()));
}
return closestNode;
}
/**
* Adds the given node reference to the finger table and successor list, if
* appropriate. The reference is NOT set as predecessor, even if is closer
* to this node. Therefore use {@link #addReferenceAsPredecessor(Node)}.
*
* @param newReference
* Reference to be added to the local data structures.
* @throws NullPointerException
* If the given reference is null.
*/
final synchronized void addReference(Node newReference) {
if (newReference == null) {
NullPointerException e = new NullPointerException(
"Node reference to be added must not be null!");
this.logger.error("Null pointer", e);
throw e;
}
boolean debug = this.logger.isEnabledFor(DEBUG);
// June 21, 2006. Moved here by sven to avoid failing of checkIfProxy()
if (newReference.getNodeID().equals(this.localID)) {
if (debug) {
this.logger.debug("Reference on myself was not added");
}
return;
}
// check parameters
this.checkIfProxy(newReference);
this.fingerTable.addReference(newReference);
this.successorList.addSuccessor(newReference);
if (debug) {
this.logger.debug("Attempted to add reference "
+ newReference.getNodeID().toString()
+ " to finger table and successor list. Whether it fit "
+ "or not depends on those data structures.");
}
}
/**
* Removes the given node reference from the finger table and the successor
* list. If the given reference is the current predecessor, the predecessor
* reference will be <code>null</code> afterwards.
*
* @param oldReference
* Reference to remove from ALL data structures.
* @throws NullPointerException
* If reference to remove is <code>null</code>.
*/
final synchronized void removeReference(Node oldReference) {
if (oldReference == null) {
NullPointerException e = new NullPointerException(
"Reference to remove must not be null!");
this.logger.error("Null pointer", e);
throw e;
}
this.fingerTable.removeReference(oldReference);
this.successorList.removeReference(oldReference);
if (oldReference.equals(this.getPredecessor())) {
this.predecessor = null;
}
disconnectIfUnreferenced(oldReference);
if (this.logger.isEnabledFor(DEBUG)) {
this.logger
.debug("Attempted to remove reference "
+ oldReference
+ " from all data structures including predecessor reference.");
}
}
/**
* Closes the connection to the given reference, if it is not kept in any
* data structure (ie. finger table, successor list, predecessor) any more.
*
* @param removedReference
* Node to which the connection shall be closed, if there exists
* no reference any more.
* @throws NullPointerException
* If given reference is null.
*/
void disconnectIfUnreferenced(Node removedReference) {
if (removedReference == null) {
NullPointerException e = new NullPointerException(
"Reference may not be null!");
this.logger.error("Null pointer", e);
throw e;
}
if (!this.containsReference(removedReference)) {
if (!(removedReference instanceof Proxy)) {
this.logger
.error("Attempt to disconnect unused reference failed");
throw new RuntimeException("Reference should be of type Proxy");
}
this.logger.debug("Disconnecting unused reference on node "
+ removedReference);
removedReference.disconnect();
}
}
/**
* Determines this node's direct successor and returns it. If no successor
* is known, <code>null</code> is returned.
*
* @return The local node's direct successor, or <code>null</code> if no
* successor is known.
*/
final synchronized Node getSuccessor() {
// direct successor is the first entry in my successor list
return this.successorList.getDirectSuccessor();
}
/**
* Returns a formatted string of the IDs of all references stored on this
* node. This includes references in the finger table and successor list as
* well as the predecessor.
*
* @return Formatted string of references.
*/
public synchronized String toString() {
StringBuilder result = new StringBuilder("Node: "
+ this.localID.toString() + ", " + this.localURL + "\n");
result.append(this.fingerTable.toString());
result.append(this.successorList.toString());
result.append("Predecessor: "
+ (this.predecessor == null ? "null" : ""
+ this.predecessor.getNodeID() + ", "
+ this.predecessor.getNodeURL()));
return result.toString();
}
/**
* Returns the reference on this node's predecessor, if available. If no
* predecessor exists for this node, <code>null</code> is returned.
*
* @return Reference on this node's predecessor, if available. If no
* predecessor exists for this node, <code>null</code> is
* returned.
*/
final synchronized Node getPredecessor() {
return this.predecessor;
}
/**
* Sets the given reference as this node's predecessor. If the former value
* of this predecessor's node was <code>null</code> and if this reference
* is not used any more (eg. in finger table or successor list), the
* connection to it is closed.
*
* @param potentialPredecessor
* Reference on the node to be set as new predecessor; may not be
* null
* @throws NullPointerException
* If potential predecessor is null.
*/
final synchronized void setPredecessor(Node potentialPredecessor) {
if (potentialPredecessor == null) {
NullPointerException e = new NullPointerException(
"Potential predecessor of method setPredecessor may not be "
+ "null!");
this.logger.error("Null pointer", e);
throw e;
}
this.checkIfProxy(potentialPredecessor);
boolean info = this.logger.isEnabledFor(INFO);
if (!(potentialPredecessor.equals(this.predecessor))) {
Node formerPredecessor = this.predecessor;
this.predecessor = potentialPredecessor;
if (formerPredecessor != null) {
this.disconnectIfUnreferenced(formerPredecessor);
/*
* The replicas, which are in the range between the old and the
* new predecessor, on the last successor of this node have to
* be removed if the successor list is full. => capacity of sl ==
* length of sl.
*/
int sLSize = this.successorList.getSize();
if (this.successorList.getCapacity() == sLSize) {
Node lastSuccessor = this.successorList.getReferences()
.get(sLSize - 1);
try {
lastSuccessor.removeReplicas(this.predecessor
.getNodeID(), new HashSet<Entry>());
} catch (CommunicationException e) {
logger
.warn(
"Could not remove replicas on last predecessor",
e);
}
}
if (this.logger.isEnabledFor(DEBUG)) {
this.logger.debug("Old predecessor " + formerPredecessor
+ " was replaced by " + potentialPredecessor);
}
} else {
if (info) {
this.logger.info("Predecessor reference set to "
+ potentialPredecessor + "; was null before.");
}
Set<Entry> entriesToRep = this.entries.getEntriesInInterval(
this.predecessor.getNodeID(), this.localID);
List<Node> successors = this.successorList.getReferences();
for (Node successor : successors) {
try {
successor.insertReplicas(entriesToRep);
} catch (CommunicationException e) {
this.logger.warn(
"Damn. Could not replicate to successor "
+ successor.getNodeID(), e);
}
}
}
}
}
/**
* Returns an unmodifiable list of this node's successors.
*
* @return Unmodifiable successor list.
*/
final synchronized List<Node> getSuccessors() {
return this.successorList.getReferences();
}
/**
* Determines if the given reference is contained in any one data structure,
* ie. finger table, successor list, or predecessor reference.
*
* @param newReference
* Reference to look up.
* @throws NullPointerException
* If given reference is <code>null</code>.
* @return <code>true</code> if the reference is contained and
* <code>false</code> if not.
*/
final synchronized boolean containsReference(Node newReference) {
if (newReference == null) {
NullPointerException e = new NullPointerException(
"Reference to look up must not be null!");
this.logger.error("Null pointer", e);
throw e;
}
return (this.fingerTable.containsReference(newReference)
|| this.successorList.containsReference(newReference) || newReference
.equals(this.predecessor));
}
/**
* Returns a formatted string of this node's finger table.
*
* @return String representation of finger table.
*/
final String printFingerTable() {
return this.fingerTable.toString();
}
/**
* Returns a formatted string of this node's successor list.
*
* @return String representation of successor list.
*/
final String printSuccessorList() {
return this.successorList.toString();
}
/**
* Adds the given node reference to the finger table and successor list AND
* sets it as new predecessor reference, if appropriate. Appropriateness is
* given if either the former predecessor reference was <code>null</code>
* or the new reference's ID is located between the old predecessor ID and
* this node's ID. Even if not appropriate as predecessor, the reference is
* added to finger table and successor list.
*
* @param potentialPredecessor
* Reference which should be this node's predecessor.
* @throws NullPointerException
* If the given reference is <code>null</code>.
*/
void addReferenceAsPredecessor(Node potentialPredecessor) {
this.checkIfProxy(potentialPredecessor);
if (potentialPredecessor == null) {
NullPointerException e = new NullPointerException(
"Reference to potential predecessor may not be null!");
this.logger.error("Null pointer", e);
throw e;
}
// if the local node did not have a predecessor reference before
// or if the potential predecessor is closer to this local node,
// replace the predecessor reference
if (this.predecessor == null
|| potentialPredecessor.getNodeID().isInInterval(
this.predecessor.getNodeID(), this.localID)) {
if (this.logger.isEnabledFor(INFO)) {
this.logger
.info("Setting a new predecessor reference: New reference is "
+ potentialPredecessor
+ ", old reference was "
+ (this.predecessor == null ? "null"
: this.predecessor));
}
// replace predecessor reference
this.setPredecessor(potentialPredecessor);
/*
* If a new predecessor, better than the old one has arrived, we
* have to copy the entries, that are relevant for this new
* predecessor. This has to be done by the predecessor itself, NOT
* here. 18.06.2007. sven
*/
// final Set<Entry> entries = this.entries.getEntriesInInterval(
// this.predecessor.nodeID, this.localID);
// this.localChord.getAsyncExecutor().execute(new Runnable() {
// public void run() {
// try {
// for (Entry entryToInsert : entries) {
// getPredecessor().insertEntry(entryToInsert);
// }
// } catch (CommunicationException e) {
// }
// }
// });
}
// add reference
this.addReference(potentialPredecessor);
}
/**
* Determines the first i entries in the finger table.
*
* @param i
* @return The first (i+1) entries of finger table. If there are fewer then
* i+1 entries only these are returned.
*/
public List<Node> getFirstFingerTableEntries(int i) {
return this.fingerTable.getFirstFingerTableEntries(i);
}
/**
* Added by sven on 21.03.2006. This data strucutre is supposed to work with
* remote references therefore it must be instances of Proxy. This method is
* used in every method that adds a new reference to this to check that it
* is an instance of Proxy. -> TODO: Consider to change type of all Nodes
* within this data structure to type Proxy!!!!
*
* @param node
* @throws RuntimeException
*/
private void checkIfProxy(Node node) {
if (!(node instanceof Proxy)) {
String msg = "Trying to use local node " + node
+ " with references. This is not possible."
+ "If you see this Exception contact the developers!";
RuntimeException e = new RuntimeException(msg);
this.logger.fatal(msg, e);
throw e;
}
}
}