Finished DHT content storage and retrieval

Started working on Content lookup and sending content
This commit is contained in:
Joshua Kissoon 2014-02-26 17:07:18 +05:30
parent b5e89c6ddb
commit a2d0be6124
6 changed files with 258 additions and 24 deletions

View File

@ -16,7 +16,7 @@ public class GetParameter
{ {
private NodeId key; private NodeId key;
private String owner = null; private String ownerId = null;
private String type = null; private String type = null;
public GetParameter(NodeId key) public GetParameter(NodeId key)
@ -27,7 +27,7 @@ public class GetParameter
public GetParameter(NodeId key, String owner) public GetParameter(NodeId key, String owner)
{ {
this(key); this(key);
this.owner = owner; this.ownerId = owner;
} }
public GetParameter(NodeId key, String owner, String type) public GetParameter(NodeId key, String owner, String type)
@ -36,4 +36,18 @@ public class GetParameter
this.type = type; this.type = type;
} }
public NodeId getKey()
{
return this.key;
}
public String getOwnerId()
{
return this.ownerId;
}
public String getType()
{
return this.type;
}
} }

View File

@ -2,6 +2,8 @@ package kademlia.core;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import kademlia.dht.DHT; import kademlia.dht.DHT;
@ -24,6 +26,7 @@ import kademlia.operation.StoreOperation;
* @todo When we receive a store message - if we have a newer version of the content, re-send this newer version to that node so as to update their version * @todo When we receive a store message - if we have a newer version of the content, re-send this newer version to that node so as to update their version
* @todo Handle IPv6 Addresses * @todo Handle IPv6 Addresses
* @todo Handle compressing data * @todo Handle compressing data
* @todo Allow optional storing of content locally using the put method
*/ */
public class Kademlia public class Kademlia
{ {
@ -138,13 +141,23 @@ public class Kademlia
* The content returned is a JSON String in byte format; this string is parsed into a class * The content returned is a JSON String in byte format; this string is parsed into a class
* *
* @param param The parameters used to search for the content * @param param The parameters used to search for the content
* @param c The class to cast the returned object to
* *
* @return DHTContent The content * @return DHTContent The content
*
* @throws java.io.IOException
*/ */
public KadContent get(GetParameter param, Class c) public List<KadContent> get(GetParameter param) throws NoSuchElementException, IOException
{ {
return null; if (this.dht.contains(param))
{
/* If the content exist in our own DHT, then return it. */
return this.dht.get(param);
}
else
{
/* Seems like it doesn't exist in our DHT, get it from other Nodes */
return new DataLookupOperation().execute().getContent();
}
} }
/** /**

View File

@ -1,10 +1,18 @@
package kademlia.dht; package kademlia.dht;
import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import kademlia.core.Configuration; import kademlia.core.Configuration;
import kademlia.core.GetParameter;
import kademlia.node.NodeId;
import kademlia.serializer.JsonSerializer; import kademlia.serializer.JsonSerializer;
/** /**
@ -33,10 +41,90 @@ public class DHT
public void store(KadContent content) throws IOException public void store(KadContent content) throws IOException
{ {
/* Keep track of this content in the entries manager */ /* Keep track of this content in the entries manager */
this.entriesManager.put(new StorageEntry(content)); this.entriesManager.put(content);
/* Now we store the content locally in a file */
String contentStorageFolder = this.getContentStorageFolderName(content.getKey());
DataOutputStream dout = new DataOutputStream(new FileOutputStream(contentStorageFolder + File.separator + content.hashCode() + ".kct"));
new JsonSerializer().write(content, dout);
}
/** /**
* Now we store the content locally in a file * Retrieves a Content from local storage
*
* @param key The Key of the content to retrieve
* @param hashCode The hash code of the content to retrieve
*
* @return A KadContent object
*/
private KadContent retrieve(NodeId key, int hashCode) throws FileNotFoundException, IOException, ClassNotFoundException
{
String folder = this.getContentStorageFolderName(key);
DataInputStream in = new DataInputStream(new FileInputStream(folder + File.separator + hashCode + ".kct"));
return new JsonSerializer().read(in);
}
/**
* Check if any content for the given criteria exists in this DHT
*
* @param param The content search criteria
*
* @return boolean Whether any content exist that satisfy the criteria
*/
public boolean contains(GetParameter param)
{
return this.entriesManager.contains(param);
}
/**
* Get the List<StorageEntry> for the content if any exist,
* retrieve the content from the storage system and return it
*
* @param param The parameters used to filter the content needed
*
* @return List<KadContent> A list with the content found on the DHT satisfying the given criteria
*
* @throws java.io.IOException
*/
public List<KadContent> get(GetParameter param) throws NoSuchElementException, IOException
{
/* Load all KadContent for the entries if any exist */
List<KadContent> values = new ArrayList<>();
try
{
for (StorageEntry e : this.entriesManager.get(param))
{
values.add(this.retrieve(e.getKey(), e.getContentHash()));
}
}
catch (FileNotFoundException e)
{
System.err.println("Error while loading file for content.");
}
catch (ClassNotFoundException e)
{
System.err.println("The class for some content was not found.");
}
if (values.isEmpty())
{
throw new NoSuchElementException();
}
return values;
}
/**
* Get the name of the folder for which a content should be stored
*
* @param key The key of the content
*
* @return String The name of the folder
*/
private String getContentStorageFolderName(NodeId key)
{
/**
* Each content is stored in a folder named after the first 10 characters of the NodeId * Each content is stored in a folder named after the first 10 characters of the NodeId
* *
* The name of the file containing the content is the hash of this content * The name of the file containing the content is the hash of this content
@ -44,23 +132,21 @@ public class DHT
String storagePath = System.getProperty("user.home") + File.separator + Configuration.localFolder; String storagePath = System.getProperty("user.home") + File.separator + Configuration.localFolder;
File mainStorageFolder = new File(storagePath); File mainStorageFolder = new File(storagePath);
/* Create the folder if it doesn't exist */ /* Create the main storage folder if it doesn't exist */
if (!mainStorageFolder.isDirectory()) if (!mainStorageFolder.isDirectory())
{ {
mainStorageFolder.mkdir(); mainStorageFolder.mkdir();
} }
/* Check if a folder after the first 10 characters of Hex(nodeId) exist, if not, create it */ String folderName = key.hexRepresentation().substring(0, 10);
String folderName = content.getKey().hexRepresentation().substring(0, 20);
File contentStorageFolder = new File(mainStorageFolder + File.separator + folderName); File contentStorageFolder = new File(mainStorageFolder + File.separator + folderName);
/* Create the content folder if it doesn't exist */
if (!contentStorageFolder.isDirectory()) if (!contentStorageFolder.isDirectory())
{ {
contentStorageFolder.mkdir(); contentStorageFolder.mkdir();
} }
/* Write the content to a file and store it in the folder */ return mainStorageFolder + File.separator + folderName;
File contentFile = new File(String.valueOf(content.hashCode()) + ".kct");
DataOutputStream dout = new DataOutputStream(new FileOutputStream(contentStorageFolder + File.separator + contentFile));
new JsonSerializer().write(content, dout);
} }
} }

View File

@ -1,6 +1,7 @@
package kademlia.dht; package kademlia.dht;
import java.util.Objects; import java.util.Objects;
import kademlia.core.GetParameter;
import kademlia.node.NodeId; import kademlia.node.NodeId;
/** /**
@ -16,12 +17,14 @@ public class StorageEntry
private final NodeId key; private final NodeId key;
private final String ownerId; private final String ownerId;
private final String type; private final String type;
private final int contentHash;
public StorageEntry(KadContent content) public StorageEntry(KadContent content)
{ {
this.key = content.getKey(); this.key = content.getKey();
this.ownerId = content.getOwnerId(); this.ownerId = content.getOwnerId();
this.type = content.getType(); this.type = content.getType();
this.contentHash = content.hashCode();
} }
public NodeId getKey() public NodeId getKey()
@ -29,6 +32,52 @@ public class StorageEntry
return this.key; return this.key;
} }
public String getOwnerId()
{
return this.ownerId;
}
public String getType()
{
return this.type;
}
public int getContentHash()
{
return this.contentHash;
}
/**
* When a node is looking for content, he sends the search criteria in a GetParameter object
* Here we take this GetParameter object and check if this StorageEntry satisfies the given parameters
*
* @param params
*
* @return boolean Whether this content satisfies the parameters
*/
public boolean satisfiesParameters(GetParameter params)
{
/* Check that owner id matches */
if ((params.getOwnerId() != null) && (!params.getOwnerId().equals(this.ownerId)))
{
return false;
}
/* Check that type matches */
if ((params.getType() != null) && (!params.getType().equals(this.type)))
{
return false;
}
/* Check that key matches */
if ((params.getKey() != null) && (!params.getKey().equals(this.key)))
{
return false;
}
return true;
}
@Override @Override
public boolean equals(Object o) public boolean equals(Object o)
{ {

View File

@ -4,6 +4,8 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.NoSuchElementException;
import kademlia.core.GetParameter;
import kademlia.node.NodeId; import kademlia.node.NodeId;
/** /**
@ -27,10 +29,11 @@ public class StorageEntryManager
/** /**
* Add a new entry to our storage * Add a new entry to our storage
* *
* @param entry * @param content The content to store a reference to
*/ */
public void put(StorageEntry entry) public void put(KadContent content)
{ {
StorageEntry entry = new StorageEntry(content);
if (!this.entries.containsKey(entry.getKey())) if (!this.entries.containsKey(entry.getKey()))
{ {
this.entries.put(entry.getKey(), new ArrayList<StorageEntry>()); this.entries.put(entry.getKey(), new ArrayList<StorageEntry>());
@ -44,27 +47,65 @@ public class StorageEntryManager
* *
* @todo Add searching for content by type and ownerID * @todo Add searching for content by type and ownerID
* *
* @param key * @param param The parameters used to search for a content
* *
* @return boolean * @return boolean
*/ */
public boolean contains(NodeId key) public boolean contains(GetParameter param)
{ {
return this.entries.containsKey(key); if (this.entries.containsKey(param.getKey()))
{
/* Content with this key exist, check if any match the rest of the search criteria */
for (StorageEntry e : this.entries.get(param.getKey()))
{
/* If any entry satisfies the given parameters, return true */
if (e.satisfiesParameters(param))
{
return true;
}
}
}
return false;
} }
/** /**
* Checks if our DHT has a Content for the given criteria * Checks if our DHT has a Content for the given criteria
* *
* @todo Add finding for content by type and ownerID * @param param The parameters used to search for a content
* *
* @param key * @todo Add finding for content by type and ownerID
* *
* @return List of content for the specific search parameters * @return List of content for the specific search parameters
*/ */
public List<StorageEntry> get(NodeId key) public List<StorageEntry> get(GetParameter param) throws NoSuchElementException
{ {
return this.entries.get(key); if (this.entries.containsKey(param.getKey()))
{
/* Content with this key exist, check if any match the rest of the search criteria */
List<StorageEntry> results = new ArrayList<>();
for (StorageEntry e : this.entries.get(param.getKey()))
{
/* If any entry satisfies the given parameters, return true */
if (e.satisfiesParameters(param))
{
results.add(e);
}
}
if (results.size() > 0)
{
return results;
}
else
{
throw new NoSuchElementException();
}
}
else
{
throw new NoSuchElementException("No content exist for the given parameters");
}
} }
} }

View File

@ -0,0 +1,31 @@
package kademlia.operation;
import kademlia.core.KadServer;
import kademlia.node.Node;
import kademlia.node.NodeId;
/**
* Looks up a specified identifier and returns the value associated with it
*
* @author Joshua Kissoon
* @since 20140226
*/
public class ContentLookupOperation implements Operation
{
private final KadServer server;
private final Node localNode;
private final NodeId key;
/**
* @param server
* @param localNode
* @param key The key for the content which we need to find
*/
public ContentLookupOperation(KadServer server, Node localNode, NodeId key)
{
this.server = server;
this.localNode = localNode;
this.key = key;
}
}