Added raw implementation for subscriptions persistence

This commit is contained in:
selva.andre
2012-08-10 23:24:59 +02:00
parent 943a962bb9
commit 389c906d8f
4 changed files with 171 additions and 87 deletions
+15 -2
View File
@@ -1,6 +1,6 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
@@ -25,7 +25,7 @@
<version>2.0.4</version> <version>2.0.4</version>
</dependency> </dependency>
<!-- <dependency> <!-- <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId> <artifactId>slf4j-jdk14</artifactId>
<version>1.6.4</version> <version>1.6.4</version>
@@ -37,6 +37,12 @@
<artifactId>slf4j-log4j12</artifactId> <artifactId>slf4j-log4j12</artifactId>
<version>1.6.4</version> <version>1.6.4</version>
</dependency> </dependency>
<dependency>
<groupId>org.fusesource.hawtdb</groupId>
<artifactId>hawtdb</artifactId>
<version>1.6</version>
</dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
@@ -77,4 +83,11 @@
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
<!-- <repositories>
<repository>
<id>Fusesource Releases</id>
<url>http://repo.fusesource.com/nexus/content/repositories/snapshots</url>
</repository>
</repositories>-->
</project> </project>
@@ -18,6 +18,11 @@ public class SimpleMessaging implements IMessaging {
private INotifier m_notifier; private INotifier m_notifier;
public SimpleMessaging() {
subscriptions.init();
}
public void setNotifier(INotifier notifier) { public void setNotifier(INotifier notifier) {
m_notifier = notifier; m_notifier = notifier;
} }
@@ -1,13 +1,23 @@
package org.dna.mqtt.moquette.messaging.spi.impl; package org.dna.mqtt.moquette.messaging.spi.impl;
import java.io.File;
import java.io.IOException;
import java.text.ParseException; import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level; import org.dna.mqtt.moquette.MQTTException;
import java.util.logging.Logger; import org.dna.mqtt.moquette.server.MQTTHandler;
import org.fusesource.hawtbuf.codec.StringCodec;
import org.fusesource.hawtdb.api.BTreeIndexFactory;
import org.fusesource.hawtdb.api.PageFile;
import org.fusesource.hawtdb.api.PageFileFactory;
import org.fusesource.hawtdb.api.SortedIndex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* Represents a tree of published topics * Represents a tree of published topics
@@ -17,15 +27,16 @@ import java.util.logging.Logger;
public class SubscriptionsStore { public class SubscriptionsStore {
private class TreeNode { private class TreeNode {
TreeNode m_parent; TreeNode m_parent;
Token m_token; Token m_token;
List<TreeNode> m_children = new ArrayList<TreeNode>(); List<TreeNode> m_children = new ArrayList<TreeNode>();
List<Subscription> m_subscriptions = new ArrayList<Subscription>(); List<Subscription> m_subscriptions = new ArrayList<Subscription>();
TreeNode(TreeNode parent) { TreeNode(TreeNode parent) {
this.m_parent = parent; this.m_parent = parent;
} }
Token getToken() { Token getToken() {
return m_token; return m_token;
} }
@@ -33,7 +44,7 @@ public class SubscriptionsStore {
void setToken(Token topic) { void setToken(Token topic) {
this.m_token = topic; this.m_token = topic;
} }
void addSubcription(Subscription s) { void addSubcription(Subscription s) {
//avoid double registering //avoid double registering
if (m_subscriptions.contains(s)) { if (m_subscriptions.contains(s)) {
@@ -41,16 +52,15 @@ public class SubscriptionsStore {
} }
m_subscriptions.add(s); m_subscriptions.add(s);
} }
void addChild(TreeNode child) { void addChild(TreeNode child) {
m_children.add(child); m_children.add(child);
} }
boolean isLeaf() { boolean isLeaf() {
return m_children.isEmpty(); return m_children.isEmpty();
} }
/** /**
* Search for children that has the specified token, if not found return * Search for children that has the specified token, if not found return
* null; * null;
@@ -61,18 +71,17 @@ public class SubscriptionsStore {
return child; return child;
} }
} }
return null; return null;
} }
List<Subscription> subscriptions() { List<Subscription> subscriptions() {
return m_subscriptions; return m_subscriptions;
} }
void matches(Queue<Token> tokens, List<Subscription> matchingSubs) { void matches(Queue<Token> tokens, List<Subscription> matchingSubs) {
Token t = tokens.poll(); Token t = tokens.poll();
//check if t is null <=> tokens finished //check if t is null <=> tokens finished
if (t == null) { if (t == null) {
matchingSubs.addAll(m_subscriptions); matchingSubs.addAll(m_subscriptions);
@@ -82,16 +91,16 @@ public class SubscriptionsStore {
matchingSubs.addAll(n.subscriptions()); matchingSubs.addAll(n.subscriptions());
} }
} }
return; return;
} }
//we are on MULTI, than add subscriptions and return //we are on MULTI, than add subscriptions and return
if (m_token == Token.MULTI) { if (m_token == Token.MULTI) {
matchingSubs.addAll(m_subscriptions); matchingSubs.addAll(m_subscriptions);
return; return;
} }
for (TreeNode n : m_children) { for (TreeNode n : m_children) {
if (n.getToken().match(t)) { if (n.getToken().match(t)) {
//Create a copy of token, alse if navigate 2 sibling it //Create a copy of token, alse if navigate 2 sibling it
@@ -100,7 +109,7 @@ public class SubscriptionsStore {
} }
} }
} }
/** /**
* Return the number of registered subscriptions * Return the number of registered subscriptions
*/ */
@@ -115,51 +124,50 @@ public class SubscriptionsStore {
void removeClientSubscriptions(String clientID) { void removeClientSubscriptions(String clientID) {
//collect what to delete and then delete to avoid ConcurrentModification //collect what to delete and then delete to avoid ConcurrentModification
List<Subscription> subsToRemove = new ArrayList<Subscription>(); List<Subscription> subsToRemove = new ArrayList<Subscription>();
for(Subscription s : m_subscriptions) { for (Subscription s : m_subscriptions) {
if (s.clientId.equals(clientID)) { if (s.clientId.equals(clientID)) {
subsToRemove.add(s); subsToRemove.add(s);
} }
} }
for (Subscription s : subsToRemove) { for (Subscription s : subsToRemove) {
m_subscriptions.remove(s); m_subscriptions.remove(s);
} }
//go deep //go deep
for (TreeNode child : m_children) { for (TreeNode child : m_children) {
child.removeClientSubscriptions(clientID); child.removeClientSubscriptions(clientID);
} }
} }
} }
protected static class Token { protected static class Token {
static final Token EMPTY = new Token(""); static final Token EMPTY = new Token("");
static final Token MULTI = new Token("#"); static final Token MULTI = new Token("#");
static final Token SINGLE = new Token("+"); static final Token SINGLE = new Token("+");
String name; String name;
protected Token(String s) { protected Token(String s) {
name = s; name = s;
} }
protected String name() { protected String name() {
return name; return name;
} }
protected boolean match(Token t) { protected boolean match(Token t) {
if (t == MULTI || t == SINGLE) { if (t == MULTI || t == SINGLE) {
return false; return false;
} }
if (this == MULTI || this == SINGLE) { if (this == MULTI || this == SINGLE) {
return true; return true;
} }
return equals(t); return equals(t);
} }
@Override @Override
public int hashCode() { public int hashCode() {
int hash = 7; int hash = 7;
@@ -181,29 +189,70 @@ public class SubscriptionsStore {
} }
return true; return true;
} }
@Override @Override
public String toString() { public String toString() {
return name; return name;
} }
} }
// private List<Subscription> subscriptions = new ArrayList<Subscription>(); // private List<Subscription> subscriptions = new ArrayList<Subscription>();
private TreeNode subscriptions = new TreeNode(null); private TreeNode subscriptions = new TreeNode(null);
private static final Logger LOG = LoggerFactory.getLogger(SubscriptionsStore.class);
//pesistent Map of clientID, list of Subscriptions
private SortedIndex<String, List<Subscription>> m_persistent;
private PageFile pageFile;
public void add(Subscription newSubscription) { /**
* Initialize basic store structures, like the FS storage to maintain
* client's topics subscriptions
*/
public void init() {
PageFileFactory factory = new PageFileFactory();
File tmpFile;
try {
tmpFile = File.createTempFile("hawtdb", "test");
} catch (IOException ex) {
LOG.error(null, ex);
throw new MQTTException("Can't create temp file for subscriptions storage", ex);
}
factory.setFile(/*new File("mydb.dat")*/tmpFile);
factory.open();
pageFile = factory.getPageFile();
// HashIndexFactory<String, List<Subscription>> indexFactory =
// new HashIndexFactory<String, List<Subscription>>();
BTreeIndexFactory<String, List<Subscription>> indexFactory =
new BTreeIndexFactory<String, List<Subscription>>();
indexFactory.setKeyCodec(StringCodec.INSTANCE);
m_persistent = indexFactory.openOrCreate(pageFile);
//reaload any subscriptions persisted
LOG.debug("Reloading all stored subscriptions...");
for(Map.Entry<String, List<Subscription>> entry: m_persistent) {
for (Subscription subscription : entry.getValue()) {
addDirect(subscription);
}
}
LOG.debug("Finished loading");
}
protected void addDirect(Subscription newSubscription) {
List<Token> tokens = new ArrayList<Token>(); List<Token> tokens = new ArrayList<Token>();
try { try {
tokens = splitTopic(newSubscription.topic); tokens = splitTopic(newSubscription.topic);
} catch (ParseException ex) { } catch (ParseException ex) {
//TODO handle the parse exception //TODO handle the parse exception
Logger.getLogger(SubscriptionsStore.class.getName()).log(Level.SEVERE, null, ex); LOG.error(null, ex);
} }
TreeNode current = subscriptions; TreeNode current = subscriptions;
for (Token token : tokens) { for (Token token : tokens) {
TreeNode matchingChildren; TreeNode matchingChildren;
//check if a children with the same token already exists //check if a children with the same token already exists
if ((matchingChildren = current.childWithToken(token)) != null) { if ((matchingChildren = current.childWithToken(token)) != null) {
current = matchingChildren; current = matchingChildren;
@@ -217,12 +266,29 @@ public class SubscriptionsStore {
} }
current.addSubcription(newSubscription); current.addSubcription(newSubscription);
} }
public void add(Subscription newSubscription) {
addDirect(newSubscription);
//log the subscription
String clientID = newSubscription.getClientId();
if (!m_persistent.containsKey(clientID)) {
m_persistent.put(clientID, new ArrayList<Subscription>());
}
List<Subscription> subs = m_persistent.get(clientID);
subs.add(newSubscription);
pageFile.flush();
}
/** /**
* Visit the topics tree to remove matching subscriptions with clientID * Visit the topics tree to remove matching subscriptions with clientID
*/ */
public void removeForClient(String clientID) { public void removeForClient(String clientID) {
subscriptions.removeClientSubscriptions(clientID); subscriptions.removeClientSubscriptions(clientID);
//remove from log all subscriptions
m_persistent.remove(clientID);
} }
public List<Subscription> matches(String topic) { public List<Subscription> matches(String topic) {
@@ -231,9 +297,9 @@ public class SubscriptionsStore {
tokens = splitTopic(topic); tokens = splitTopic(topic);
} catch (ParseException ex) { } catch (ParseException ex) {
//TODO handle the parse exception //TODO handle the parse exception
Logger.getLogger(SubscriptionsStore.class.getName()).log(Level.SEVERE, null, ex); LOG.error(null, ex);
} }
Queue<Token> tokenQueue = new LinkedBlockingDeque<Token>(tokens); Queue<Token> tokenQueue = new LinkedBlockingDeque<Token>(tokens);
List<Subscription> matchingSubs = new ArrayList<Subscription>(); List<Subscription> matchingSubs = new ArrayList<Subscription>();
subscriptions.matches(tokenQueue, matchingSubs); subscriptions.matches(tokenQueue, matchingSubs);
@@ -243,29 +309,29 @@ public class SubscriptionsStore {
public boolean contains(Subscription sub) { public boolean contains(Subscription sub) {
return !matches(sub.topic).isEmpty(); return !matches(sub.topic).isEmpty();
} }
public int size() { public int size() {
return subscriptions.size(); return subscriptions.size();
} }
protected List<Token> splitTopic(String topic) throws ParseException { protected List<Token> splitTopic(String topic) throws ParseException {
List res = new ArrayList<Token>(); List res = new ArrayList<Token>();
String[] splitted = topic.split("/"); String[] splitted = topic.split("/");
if (splitted.length == 0) { if (splitted.length == 0) {
res.add(Token.EMPTY); res.add(Token.EMPTY);
} }
for (int i=0; i<splitted.length; i++) { for (int i = 0; i < splitted.length; i++) {
String s = splitted[i]; String s = splitted[i];
if (s.isEmpty()) { if (s.isEmpty()) {
if (i != 0){ if (i != 0) {
throw new ParseException("Bad format of topic, expetec topic name between separators", i); throw new ParseException("Bad format of topic, expetec topic name between separators", i);
} }
res.add(Token.EMPTY); res.add(Token.EMPTY);
} else if (s.equals("#")) { } else if (s.equals("#")) {
//check that multi is the last symbol //check that multi is the last symbol
if (i != splitted.length - 1 ) { if (i != splitted.length - 1) {
throw new ParseException("Bad format of topic, the multi symbol (#) has to be the last one after a separator", i); throw new ParseException("Bad format of topic, the multi symbol (#) has to be the last one after a separator", i);
} }
res.add(Token.MULTI); res.add(Token.MULTI);
@@ -279,7 +345,7 @@ public class SubscriptionsStore {
res.add(new Token(s)); res.add(new Token(s));
} }
} }
return res; return res;
} }
} }
@@ -155,40 +155,40 @@ public class ServerIntegrationTest {
} }
// @Test @Test
// public void testCleanSession_maintainClientSubscriptions_withServerRestart() throws IOException { public void testCleanSession_maintainClientSubscriptions_withServerRestart() throws IOException {
// Client client = new Client("localhost", Server.PORT, "CLID_123"); Client client = new Client("localhost", Server.PORT, "CLID_123");
// client.connect(false); //without session cleanup client.connect(false); //without session cleanup
//
// client.subscribe("/topic", new IPublishCallback() { client.subscribe("/topic", new IPublishCallback() {
//
// public void published(String topic, byte[] message) { public void published(String topic, byte[] message) {
// received = true; received = true;
// } }
// }); });
// client.close(); client.close();
// client.shutdown(); client.shutdown();
// server.stopServer(); server.stopServer();
//
// server.startServer(); server.startServer();
//
// //Exercise that the client maintain the subscriptions //Exercise that the client maintain the subscriptions
// client = new Client("localhost", Server.PORT, "CLID_123"); client = new Client("localhost", Server.PORT, "CLID_123");
// client.register("/topic", new IPublishCallback() { client.register("/topic", new IPublishCallback() {
//
// public void published(String topic, byte[] message) { public void published(String topic, byte[] message) {
// received = true; received = true;
// } }
// }); });
// client.connect(false); client.connect(false);
// client.publish("/topic", "Test my payload".getBytes()); client.publish("/topic", "Test my payload".getBytes());
// client.close(); client.close();
//
// //Verify //Verify
// assertTrue(received); assertTrue(received);
//
// //TearDown //TearDown
// client.shutdown(); client.shutdown();
// } }
} }