package kademlia.dht; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.List; import java.util.NoSuchElementException; import kademlia.KadConfiguration; import kademlia.exceptions.ContentExistException; import kademlia.exceptions.ContentNotFoundException; import kademlia.node.KademliaId; import kademlia.util.serializer.JsonSerializer; import kademlia.util.serializer.KadSerializer; /** * The main Distributed Hash Table class that manages the entire DHT * * @author Joshua Kissoon * @since 20140226 */ public class DHT implements KademliaDHT { private transient StoredContentManager contentManager; private transient KadSerializer<JKademliaStorageEntry> serializer = null; private transient KadConfiguration config; private final String ownerId; public DHT(String ownerId, KadConfiguration config) { this.ownerId = ownerId; this.config = config; this.initialize(); } @Override public final void initialize() { contentManager = new StoredContentManager(); } @Override public void setConfiguration(KadConfiguration con) { this.config = con; } @Override public KadSerializer<JKademliaStorageEntry> getSerializer() { if (null == serializer) { serializer = new JsonSerializer<>(); } return serializer; } @Override public boolean store(JKademliaStorageEntry content) throws IOException { /* Lets check if we have this content and it's the updated version */ if (this.contentManager.contains(content.getContentMetadata())) { KademliaStorageEntryMetadata current = this.contentManager.get(content.getContentMetadata()); /* update the last republished time */ current.updateLastRepublished(); if (current.getLastUpdatedTimestamp() >= content.getContentMetadata().getLastUpdatedTimestamp()) { /* We have the current content, no need to update it! just leave this method now */ return false; } else { /* We have this content, but not the latest version, lets delete it so the new version will be added below */ try { //System.out.println("Removing older content to update it"); this.remove(content.getContentMetadata()); } catch (ContentNotFoundException ex) { /* This won't ever happen at this point since we only get here if the content is found, lets ignore it */ } } } /** * If we got here means we don't have this content, or we need to update the content * If we need to update the content, the code above would've already deleted it, so we just need to re-add it */ try { //System.out.println("Adding new content."); /* Keep track of this content in the entries manager */ KademliaStorageEntryMetadata sEntry = this.contentManager.put(content.getContentMetadata()); /* Now we store the content locally in a file */ String contentStorageFolder = this.getContentStorageFolderName(content.getContentMetadata().getKey()); try (FileOutputStream fout = new FileOutputStream(contentStorageFolder + File.separator + sEntry.hashCode() + ".kct"); DataOutputStream dout = new DataOutputStream(fout)) { this.getSerializer().write(content, dout); } return true; } catch (ContentExistException e) { /** * Content already exist on the DHT * This won't happen because above takes care of removing the content if it's older and needs to be updated, * or returning if we already have the current content version. */ return false; } } @Override public boolean store(KadContent content) throws IOException { return this.store(new JKademliaStorageEntry(content)); } @Override public JKademliaStorageEntry retrieve(KademliaId key, int hashCode) throws FileNotFoundException, IOException, ClassNotFoundException { String folder = this.getContentStorageFolderName(key); DataInputStream din = new DataInputStream(new FileInputStream(folder + File.separator + hashCode + ".kct")); return this.getSerializer().read(din); } @Override public boolean contains(GetParameter param) { return this.contentManager.contains(param); } @Override public JKademliaStorageEntry get(KademliaStorageEntryMetadata entry) throws IOException, NoSuchElementException { try { return this.retrieve(entry.getKey(), entry.hashCode()); } catch (FileNotFoundException e) { System.err.println("Error while loading file for content. Message: " + e.getMessage()); } catch (ClassNotFoundException e) { System.err.println("The class for some content was not found. Message: " + e.getMessage()); } /* If we got here, means we got no entries */ throw new NoSuchElementException(); } @Override public JKademliaStorageEntry get(GetParameter param) throws NoSuchElementException, IOException { /* Load a KadContent if any exist for the given criteria */ try { KademliaStorageEntryMetadata e = this.contentManager.get(param); return this.retrieve(e.getKey(), e.hashCode()); } catch (FileNotFoundException e) { System.err.println("Error while loading file for content. Message: " + e.getMessage()); } catch (ClassNotFoundException e) { System.err.println("The class for some content was not found. Message: " + e.getMessage()); } /* If we got here, means we got no entries */ throw new NoSuchElementException(); } @Override public void remove(KadContent content) throws ContentNotFoundException { this.remove(new StorageEntryMetadata(content)); } @Override public void remove(KademliaStorageEntryMetadata entry) throws ContentNotFoundException { String folder = this.getContentStorageFolderName(entry.getKey()); File file = new File(folder + File.separator + entry.hashCode() + ".kct"); contentManager.remove(entry); if (file.exists()) { file.delete(); } else { throw new ContentNotFoundException(); } } /** * 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(KademliaId key) { /** * Each content is stored in a folder named after the first 2 characters of the NodeId * * The name of the file containing the content is the hash of this content */ String folderName = key.hexRepresentation().substring(0, 2); File contentStorageFolder = new File(this.config.getNodeDataFolder(ownerId) + File.separator + folderName); /* Create the content folder if it doesn't exist */ if (!contentStorageFolder.isDirectory()) { contentStorageFolder.mkdir(); } return contentStorageFolder.toString(); } @Override public List<KademliaStorageEntryMetadata> getStorageEntries() { return contentManager.getAllEntries(); } @Override public void putStorageEntries(List<KademliaStorageEntryMetadata> ientries) { for (KademliaStorageEntryMetadata e : ientries) { try { this.contentManager.put(e); } catch (ContentExistException ex) { /* Entry already exist, no need to store it again */ } } } @Override public synchronized String toString() { return this.contentManager.toString(); } }