Big refactory on session management:
- introduced a unique point of access and management of sessions state (SessionRepository) - separated behaiour for sessions with clean flag to false into DurableSession - separated behaviour for session with clean flag to true into TransientSession
This commit is contained in:
@@ -15,15 +15,16 @@
|
||||
*/
|
||||
package io.moquette.spi.impl.subscriptions;
|
||||
|
||||
import io.moquette.spi.ISubscriptionsStore.ClientTopicCouple;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
class CNode {
|
||||
|
||||
Token token;
|
||||
private List<INode> children;
|
||||
Set<ClientTopicCouple> subscriptions = new HashSet<>();
|
||||
Set<Subscription> subscriptions = new HashSet<>();
|
||||
|
||||
//private int subtreeSubscriptions;
|
||||
|
||||
@@ -33,7 +34,7 @@ class CNode {
|
||||
}
|
||||
|
||||
//Copy constructor
|
||||
private CNode(Token token, List<INode> children, Set<ClientTopicCouple> subscriptions) {
|
||||
private CNode(Token token, List<INode> children, Set<Subscription> subscriptions) {
|
||||
this.token = token;
|
||||
this.children = children;
|
||||
this.subscriptions = subscriptions;
|
||||
@@ -64,10 +65,7 @@ class CNode {
|
||||
}
|
||||
|
||||
private boolean equals(Token token) {
|
||||
if (this.token == null) {
|
||||
return false;
|
||||
}
|
||||
return this.token.equals(token);
|
||||
return this.token != null && this.token.equals(token);
|
||||
}
|
||||
|
||||
CNode copy() {
|
||||
@@ -79,7 +77,7 @@ class CNode {
|
||||
}
|
||||
|
||||
CNode addSubscription(String clientId, Topic topic) {
|
||||
this.subscriptions.add(new ClientTopicCouple(clientId, topic));
|
||||
this.subscriptions.add(new Subscription(clientId, topic));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -87,8 +85,8 @@ class CNode {
|
||||
* @return true iff the subscriptions contained in this node are owned by clientId
|
||||
* */
|
||||
boolean containsOnly(String clientId) {
|
||||
for (ClientTopicCouple sub : this.subscriptions) {
|
||||
if (!sub.clientID.equals(clientId)) {
|
||||
for (Subscription sub : this.subscriptions) {
|
||||
if (!sub.clientId.equals(clientId)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -97,8 +95,8 @@ class CNode {
|
||||
|
||||
//TODO this is equivalent to negate(containsOnly(clientId))
|
||||
public boolean contains(String clientId) {
|
||||
for (ClientTopicCouple sub : this.subscriptions) {
|
||||
if (sub.clientID.equals(clientId)) {
|
||||
for (Subscription sub : this.subscriptions) {
|
||||
if (sub.clientId.equals(clientId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -106,23 +104,12 @@ class CNode {
|
||||
}
|
||||
|
||||
void removeSubscriptionsFor(String clientId) {
|
||||
Set<ClientTopicCouple> toRemove = new HashSet<>();
|
||||
for (ClientTopicCouple sub : this.subscriptions) {
|
||||
if (sub.clientID.equals(clientId)) {
|
||||
Set<Subscription> toRemove = new HashSet<>();
|
||||
for (Subscription sub : this.subscriptions) {
|
||||
if (sub.clientId.equals(clientId)) {
|
||||
toRemove.add(sub);
|
||||
}
|
||||
}
|
||||
this.subscriptions.removeAll(toRemove);
|
||||
}
|
||||
|
||||
// public List<INode> childrenMatching(Token token) {
|
||||
// List<INode> res = new ArrayList<>();
|
||||
// for (INode iNode : children) {
|
||||
// final CNode child = iNode.mainNode();
|
||||
// if (child.equals(token) || (child.token == Token.MULTI || child.token == Token.SINGLE)) {
|
||||
// res.add(iNode);
|
||||
// }
|
||||
// }
|
||||
// return res;
|
||||
// }
|
||||
}
|
||||
|
||||
+34
-27
@@ -15,9 +15,8 @@
|
||||
*/
|
||||
package io.moquette.spi.impl.subscriptions;
|
||||
|
||||
import io.moquette.spi.ISessionsStore;
|
||||
import io.moquette.spi.ISubscriptionsStore;
|
||||
import io.moquette.spi.ISubscriptionsStore.ClientTopicCouple;
|
||||
import io.moquette.spi.ClientSession;
|
||||
import io.moquette.spi.impl.SessionsRepository;
|
||||
import io.netty.util.internal.StringUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -25,14 +24,16 @@ import org.slf4j.LoggerFactory;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
public class CTrieSubscriptionDirectory implements ISubscriptionsDirectory {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CTrieSubscriptionDirectory.class);
|
||||
|
||||
private static final Token ROOT = new Token("root");
|
||||
|
||||
protected INode root;
|
||||
private volatile ISubscriptionsStore subscriptionsStore;
|
||||
INode root;
|
||||
private volatile SessionsRepository sessionsRepository;
|
||||
|
||||
private interface IVisitor<T> {
|
||||
|
||||
@@ -57,10 +58,10 @@ public class CTrieSubscriptionDirectory implements ISubscriptionsDirectory {
|
||||
}
|
||||
StringBuilder subScriptionsStr = new StringBuilder(" ~~[");
|
||||
int counter = 0;
|
||||
for (ClientTopicCouple couple : node.subscriptions) {
|
||||
for (Subscription couple : node.subscriptions) {
|
||||
subScriptionsStr
|
||||
.append("{filter=").append(couple.topicFilter).append(", ")
|
||||
.append("client='").append(couple.clientID).append("'}");
|
||||
.append("client='").append(couple.clientId).append("'}");
|
||||
counter++;
|
||||
if (counter < node.subscriptions.size()) {
|
||||
subScriptionsStr.append(";");
|
||||
@@ -90,24 +91,24 @@ public class CTrieSubscriptionDirectory implements ISubscriptionsDirectory {
|
||||
OK, REPEAT
|
||||
}
|
||||
|
||||
public void init(ISessionsStore sessionsStore) {
|
||||
public void init(SessionsRepository sessionsRepository) {
|
||||
LOG.info("Initializing CTrie");
|
||||
final CNode mainNode = new CNode();
|
||||
mainNode.token = ROOT;
|
||||
this.root = new INode(mainNode);
|
||||
|
||||
LOG.info("Initializing subscriptions store...");
|
||||
this.subscriptionsStore = sessionsStore.subscriptionStore();
|
||||
List<ClientTopicCouple> subscriptions = this.subscriptionsStore.listAllSubscriptions();
|
||||
this.sessionsRepository = sessionsRepository;
|
||||
// reload any subscriptions persisted
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("Reloading all stored subscriptions. SubscriptionTree = {}", dumpTree());
|
||||
}
|
||||
|
||||
for (ClientTopicCouple clientTopic : subscriptions) {
|
||||
LOG.info("Re-subscribing client to topic CId={}, topicFilter={}", clientTopic.clientID,
|
||||
clientTopic.topicFilter);
|
||||
add(clientTopic);
|
||||
for (ClientSession session : this.sessionsRepository.getAllSessions()) {
|
||||
for (Subscription subscription : session.getSubscriptions()) {
|
||||
LOG.info("Re-subscribing client to topic CId={}, topicFilter={}", subscription.clientId,
|
||||
subscription.topicFilter);
|
||||
add(subscription);
|
||||
}
|
||||
}
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("Stored subscriptions have been reloaded. SubscriptionTree = {}", dumpTree());
|
||||
@@ -145,25 +146,31 @@ public class CTrieSubscriptionDirectory implements ISubscriptionsDirectory {
|
||||
* @return the list of matching subscriptions, or empty if not matching.
|
||||
*/
|
||||
Set<Subscription> match(Topic topic) {
|
||||
final Set<ClientTopicCouple> matchingSubs = recursiveMatch(topic, this.root);
|
||||
final Set<Subscription> matchingSubs = recursiveMatch(topic, this.root);
|
||||
// remove the overlapping subscriptions, selecting ones with greatest qos
|
||||
Map<String, Subscription> subsForClient = new HashMap<>();
|
||||
for (ClientTopicCouple matchingCouple : matchingSubs) {
|
||||
Subscription existingSub = subsForClient.get(matchingCouple.clientID);
|
||||
Subscription sub = this.subscriptionsStore.getSubscription(matchingCouple);
|
||||
if (sub == null) {
|
||||
// if the m_sessionStore hasn't the sub because the client disconnected
|
||||
for (Subscription matchingSub : matchingSubs) {
|
||||
Subscription existingSub = subsForClient.get(matchingSub.clientId);
|
||||
final ClientSession subscribedSession = this.sessionsRepository.sessionForClient(matchingSub.clientId);
|
||||
if (subscribedSession == null) {
|
||||
//clean session disconnected
|
||||
continue;
|
||||
}
|
||||
Subscription sub = subscribedSession.findSubscriptionByTopicFilter(matchingSub);
|
||||
if (sub == null) {
|
||||
final String excpMesg = format("Target session %s is connected but doesn't anymore subscribed to %s",
|
||||
matchingSub.clientId, matchingSub);
|
||||
throw new IllegalStateException(excpMesg);
|
||||
}
|
||||
// update the selected subscriptions if not present or if has a greater qos
|
||||
if (existingSub == null || existingSub.getRequestedQos().value() < sub.getRequestedQos().value()) {
|
||||
subsForClient.put(matchingCouple.clientID, sub);
|
||||
if (existingSub == null || existingSub.qosLessThan(sub)) {
|
||||
subsForClient.put(sub.clientId, sub);
|
||||
}
|
||||
}
|
||||
return new HashSet<>(subsForClient.values());
|
||||
}
|
||||
|
||||
Set<ClientTopicCouple> recursiveMatch(Topic topic, INode inode) {
|
||||
Set<Subscription> recursiveMatch(Topic topic, INode inode) {
|
||||
CNode cnode = inode.mainNode();
|
||||
if (cnode.token == Token.MULTI) {
|
||||
return cnode.subscriptions;
|
||||
@@ -179,7 +186,7 @@ public class CTrieSubscriptionDirectory implements ISubscriptionsDirectory {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
Topic remainingTopic = (cnode.token == ROOT) ? topic : topic.exceptHeadToken();
|
||||
Set<ClientTopicCouple> subscriptions = new HashSet<>();
|
||||
Set<Subscription> subscriptions = new HashSet<>();
|
||||
if (remainingTopic.isEmpty()) {
|
||||
subscriptions.addAll(cnode.subscriptions);
|
||||
}
|
||||
@@ -190,10 +197,10 @@ public class CTrieSubscriptionDirectory implements ISubscriptionsDirectory {
|
||||
}
|
||||
|
||||
|
||||
public void add(ClientTopicCouple newSubscription) {
|
||||
public void add(Subscription newSubscription) {
|
||||
Action res;
|
||||
do {
|
||||
res = insert(newSubscription.clientID, newSubscription.topicFilter, this.root, newSubscription.topicFilter);
|
||||
res = insert(newSubscription.clientId, newSubscription.topicFilter, this.root, newSubscription.topicFilter);
|
||||
} while (res == Action.REPEAT);
|
||||
}
|
||||
|
||||
|
||||
+3
-4
@@ -15,16 +15,15 @@
|
||||
*/
|
||||
package io.moquette.spi.impl.subscriptions;
|
||||
|
||||
import io.moquette.spi.ISessionsStore;
|
||||
import io.moquette.spi.ISubscriptionsStore;
|
||||
import io.moquette.spi.impl.SessionsRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ISubscriptionsDirectory {
|
||||
|
||||
void init(ISessionsStore sessionsStore);
|
||||
void init(SessionsRepository sessionsRepository);
|
||||
|
||||
void add(ISubscriptionsStore.ClientTopicCouple newSubscription);
|
||||
void add(Subscription newSubscription);
|
||||
|
||||
void removeSubscription(Topic topic, String clientID);
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
|
||||
package io.moquette.spi.impl.subscriptions;
|
||||
|
||||
import io.moquette.spi.ISubscriptionsStore.ClientTopicCouple;
|
||||
import io.netty.handler.codec.mqtt.MqttQoS;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
@@ -26,10 +26,10 @@ import java.io.Serializable;
|
||||
public final class Subscription implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -3383457629635732794L;
|
||||
final MqttQoS requestedQos; // max QoS acceptable
|
||||
private final MqttQoS requestedQos; // max QoS acceptable
|
||||
final String clientId;
|
||||
final Topic topicFilter;
|
||||
final boolean active;
|
||||
private final boolean active;
|
||||
|
||||
public Subscription(String clientId, Topic topicFilter, MqttQoS requestedQos) {
|
||||
this.requestedQos = requestedQos;
|
||||
@@ -45,6 +45,16 @@ public final class Subscription implements Serializable {
|
||||
this.active = orig.active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with undefined maximum QoS
|
||||
* */
|
||||
public Subscription(String clientId, Topic topicFilter) {
|
||||
this.requestedQos = null;
|
||||
this.clientId = clientId;
|
||||
this.topicFilter = topicFilter;
|
||||
this.active = true;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
@@ -61,6 +71,10 @@ public final class Subscription implements Serializable {
|
||||
return active;
|
||||
}
|
||||
|
||||
public boolean qosLessThan(Subscription sub) {
|
||||
return requestedQos.value() < sub.requestedQos.value();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
@@ -100,8 +114,4 @@ public final class Subscription implements Serializable {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public ClientTopicCouple asClientTopicCouple() {
|
||||
return new ClientTopicCouple(this.clientId, this.topicFilter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,243 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2017 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.spi.impl.subscriptions;
|
||||
|
||||
import io.moquette.spi.ISessionsStore;
|
||||
import io.moquette.spi.ISubscriptionsStore;
|
||||
import io.moquette.spi.ISubscriptionsStore.ClientTopicCouple;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* Represents a tree of topics subscriptions.
|
||||
*/
|
||||
public class SubscriptionsDirectory implements ISubscriptionsDirectory {
|
||||
|
||||
static class NodeCouple {
|
||||
|
||||
final TreeNode root;
|
||||
final TreeNode createdNode;
|
||||
|
||||
NodeCouple(TreeNode root, TreeNode createdNode) {
|
||||
this.root = root;
|
||||
this.createdNode = createdNode;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IVisitor<T> {
|
||||
|
||||
void visit(TreeNode node, int deep);
|
||||
|
||||
T getResult();
|
||||
}
|
||||
|
||||
private class DumpTreeVisitor implements IVisitor<String> {
|
||||
|
||||
String s = "";
|
||||
|
||||
@Override
|
||||
public void visit(TreeNode node, int deep) {
|
||||
StringBuilder subScriptionsStr = new StringBuilder();
|
||||
String indentTabs = indentTabs(deep);
|
||||
for (ClientTopicCouple couple : node.m_subscriptions) {
|
||||
subScriptionsStr.append(indentTabs).append(couple.toString()).append("\n");
|
||||
}
|
||||
s += node.getToken() == null ? "" : node.getToken().toString();
|
||||
s += "\n" + (node.m_subscriptions.isEmpty() ? indentTabs : "")
|
||||
+ subScriptionsStr /* + "\n" */;
|
||||
}
|
||||
|
||||
private String indentTabs(int deep) {
|
||||
StringBuilder s = new StringBuilder();
|
||||
for (int i = 0; i < deep; i++) {
|
||||
s.append("\t");
|
||||
// s += "--";
|
||||
}
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResult() {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
private AtomicReference<TreeNode> subscriptions = new AtomicReference<>(new TreeNode());
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SubscriptionsDirectory.class);
|
||||
private volatile ISubscriptionsStore subscriptionsStore;
|
||||
|
||||
/**
|
||||
* Initialize the subscription tree with the list of subscriptions. Maintained for compatibility
|
||||
* reasons.
|
||||
*
|
||||
* @param sessionsStore
|
||||
* to be used as backing store from the subscription store.
|
||||
*/
|
||||
@Override
|
||||
public void init(ISessionsStore sessionsStore) {
|
||||
LOG.info("Initializing subscriptions store...");
|
||||
this.subscriptionsStore = sessionsStore.subscriptionStore();
|
||||
List<ClientTopicCouple> subscriptions = this.subscriptionsStore.listAllSubscriptions();
|
||||
// reload any subscriptions persisted
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("Reloading all stored subscriptions. SubscriptionTree = {}", dumpTree());
|
||||
}
|
||||
|
||||
for (ClientTopicCouple clientTopic : subscriptions) {
|
||||
LOG.info("Re-subscribing client to topic CId={}, topicFilter={}", clientTopic.clientID,
|
||||
clientTopic.topicFilter);
|
||||
add(clientTopic);
|
||||
}
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("Stored subscriptions have been reloaded. SubscriptionTree = {}", dumpTree());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(ClientTopicCouple newSubscription) {
|
||||
/*
|
||||
* The topic filters have already been validated at the ProtocolProcessor. We can assume
|
||||
* they are valid.
|
||||
*/
|
||||
TreeNode oldRoot;
|
||||
NodeCouple couple;
|
||||
do {
|
||||
oldRoot = subscriptions.get();
|
||||
couple = recreatePath(newSubscription.topicFilter, oldRoot);
|
||||
couple.createdNode.addSubscription(newSubscription); //createdNode could be null?
|
||||
couple.root.recalculateSubscriptionsSize();
|
||||
//spin lock repeating till we can, swap root, if can't swap just re-do the operation
|
||||
} while (!subscriptions.compareAndSet(oldRoot, couple.root));
|
||||
LOG.debug("A subscription has been added. Root = {}, oldRoot = {}.", couple.root, oldRoot);
|
||||
}
|
||||
|
||||
NodeCouple recreatePath(Topic topic, final TreeNode oldRoot) {
|
||||
final TreeNode newRoot = oldRoot.copy();
|
||||
TreeNode parent = newRoot;
|
||||
TreeNode current = newRoot;
|
||||
for (Token token : topic.getTokens()) {
|
||||
TreeNode matchingChildren;
|
||||
|
||||
// check if a children with the same token already exists
|
||||
if ((matchingChildren = current.childWithToken(token)) != null) {
|
||||
// copy the traversed node
|
||||
current = matchingChildren.copy();
|
||||
// update the child just added in the children list
|
||||
parent.updateChild(matchingChildren, current);
|
||||
parent = current;
|
||||
} else {
|
||||
// create a new node for the newly inserted token
|
||||
matchingChildren = new TreeNode();
|
||||
matchingChildren.setToken(token);
|
||||
current.addChild(matchingChildren);
|
||||
current = matchingChildren;
|
||||
}
|
||||
}
|
||||
return new NodeCouple(newRoot, current);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSubscription(Topic topic, String clientID) {
|
||||
/*
|
||||
* The topic filters have already been validated at the ProtocolProcessor. We can assume
|
||||
* they are valid.
|
||||
*/
|
||||
TreeNode oldRoot;
|
||||
NodeCouple couple;
|
||||
do {
|
||||
oldRoot = subscriptions.get();
|
||||
couple = recreatePath(topic, oldRoot);
|
||||
|
||||
couple.createdNode.remove(new ClientTopicCouple(clientID, topic));
|
||||
couple.root.recalculateSubscriptionsSize();
|
||||
//spin lock repeating till we can, swap root, if can't swap just re-do the operation
|
||||
} while (!subscriptions.compareAndSet(oldRoot, couple.root));
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Visit the topics tree to remove matching subscriptions with clientID. It's a mutating
|
||||
// * structure operation so create a new subscription tree (partial or total).
|
||||
// *
|
||||
// * @param clientID
|
||||
// * the client ID to remove.
|
||||
// */
|
||||
// void removeForClient(String clientID) {
|
||||
// TreeNode oldRoot;
|
||||
// TreeNode newRoot;
|
||||
// do {
|
||||
// oldRoot = subscriptions.get();
|
||||
// newRoot = oldRoot.removeClientSubscriptions(clientID);
|
||||
// // spin lock repeating till we can, swap root, if can't swap just re-do the operation
|
||||
// } while (!subscriptions.compareAndSet(oldRoot, newRoot));
|
||||
// }
|
||||
|
||||
/**
|
||||
* Given a topic string return the clients subscriptions that matches it. Topic string can't
|
||||
* contain character # and + because they are reserved to listeners subscriptions, and not topic
|
||||
* publishing.
|
||||
*
|
||||
* @param topic
|
||||
* to use fo searching matching subscriptions.
|
||||
* @return the list of matching subscriptions, or empty if not matching.
|
||||
*/
|
||||
@Override
|
||||
public List<Subscription> matches(Topic topic) {
|
||||
Queue<Token> tokenQueue = new LinkedBlockingDeque<>(topic.getTokens());
|
||||
List<ClientTopicCouple> matchingSubs = new ArrayList<>();
|
||||
subscriptions.get().matches(tokenQueue, matchingSubs);
|
||||
|
||||
// remove the overlapping subscriptions, selecting ones with greatest qos
|
||||
Map<String, Subscription> subsForClient = new HashMap<>();
|
||||
for (ClientTopicCouple matchingCouple : matchingSubs) {
|
||||
Subscription existingSub = subsForClient.get(matchingCouple.clientID);
|
||||
Subscription sub = this.subscriptionsStore.getSubscription(matchingCouple);
|
||||
if (sub == null) {
|
||||
// if the m_sessionStore hasn't the sub because the client disconnected
|
||||
continue;
|
||||
}
|
||||
// update the selected subscriptions if not present or if has a greater qos
|
||||
if (existingSub == null || existingSub.getRequestedQos().value() < sub.getRequestedQos().value()) {
|
||||
subsForClient.put(matchingCouple.clientID, sub);
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(subsForClient.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return subscriptions.get().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String dumpTree() {
|
||||
DumpTreeVisitor visitor = new DumpTreeVisitor();
|
||||
bfsVisit(subscriptions.get(), visitor, 0);
|
||||
return visitor.getResult();
|
||||
}
|
||||
|
||||
private void bfsVisit(TreeNode node, IVisitor<?> visitor, int deep) {
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
visitor.visit(node, deep);
|
||||
for (TreeNode child : node.m_children) {
|
||||
bfsVisit(child, visitor, ++deep);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2017 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
|
||||
package io.moquette.spi.impl.subscriptions;
|
||||
|
||||
import io.moquette.spi.ISubscriptionsStore.ClientTopicCouple;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
class TreeNode {
|
||||
|
||||
Token m_token;
|
||||
List<TreeNode> m_children = new ArrayList<>();
|
||||
Set<ClientTopicCouple> m_subscriptions = new HashSet<>();
|
||||
|
||||
private int subtreeSubscriptions;
|
||||
|
||||
TreeNode() {
|
||||
}
|
||||
|
||||
Token getToken() {
|
||||
return m_token;
|
||||
}
|
||||
|
||||
void setToken(Token topic) {
|
||||
this.m_token = topic;
|
||||
}
|
||||
|
||||
void addSubscription(ClientTopicCouple s) {
|
||||
m_subscriptions.add(s);
|
||||
this.subtreeSubscriptions++;
|
||||
}
|
||||
|
||||
void addChild(TreeNode child) {
|
||||
m_children.add(child);
|
||||
this.subtreeSubscriptions += child.subtreeSubscriptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a shallow copy of the current node. Copy the token and the children.
|
||||
*/
|
||||
TreeNode copy() {
|
||||
final TreeNode copy = new TreeNode();
|
||||
copy.m_children = new ArrayList<>(m_children);
|
||||
copy.m_subscriptions = new HashSet<>(m_subscriptions);
|
||||
copy.m_token = m_token;
|
||||
copy.subtreeSubscriptions = this.subtreeSubscriptions;
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for children that has the specified token, if not found return null;
|
||||
*/
|
||||
TreeNode childWithToken(Token token) {
|
||||
for (TreeNode child : m_children) {
|
||||
if (child.getToken().equals(token)) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void updateChild(TreeNode oldChild, TreeNode newChild) {
|
||||
m_children.remove(oldChild);
|
||||
m_children.add(newChild);
|
||||
this.subtreeSubscriptions += newChild.subtreeSubscriptions - oldChild.subtreeSubscriptions;
|
||||
}
|
||||
|
||||
Collection<ClientTopicCouple> subscriptions() {
|
||||
return m_subscriptions;
|
||||
}
|
||||
|
||||
public void remove(ClientTopicCouple clientTopicCouple) {
|
||||
m_subscriptions.remove(clientTopicCouple);
|
||||
this.subtreeSubscriptions--;
|
||||
}
|
||||
|
||||
// TODO smell a query method that return the result modifing the parameter (matchingSubs)
|
||||
void matches(Queue<Token> tokens, List<ClientTopicCouple> matchingSubs) {
|
||||
Token t = tokens.poll();
|
||||
|
||||
// check if t is null <=> tokens finished
|
||||
if (t == null) {
|
||||
matchingSubs.addAll(m_subscriptions);
|
||||
// check if it has got a MULTI child and add its subscriptions
|
||||
for (TreeNode n : m_children) {
|
||||
if (n.getToken() == Token.MULTI || n.getToken() == Token.SINGLE) {
|
||||
matchingSubs.addAll(n.subscriptions());
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// we are on MULTI, than add subscriptions and return
|
||||
if (m_token == Token.MULTI) {
|
||||
matchingSubs.addAll(m_subscriptions);
|
||||
return;
|
||||
}
|
||||
|
||||
for (TreeNode n : m_children) {
|
||||
if (n.getToken().match(t)) {
|
||||
// Create a copy of token, else if navigate 2 sibling it
|
||||
// consumes 2 elements on the queue instead of one
|
||||
n.matches(new LinkedBlockingQueue<>(tokens), matchingSubs);
|
||||
// TODO don't create a copy n.matches(tokens, matchingSubs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of registered subscriptions
|
||||
*/
|
||||
int size() {
|
||||
return this.subtreeSubscriptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a copied subtree rooted on this node but purged of clientID's subscriptions.
|
||||
*/
|
||||
TreeNode removeClientSubscriptions(String clientID) {
|
||||
// collect what to delete and then delete to avoid ConcurrentModification
|
||||
TreeNode newSubRoot = this.copy();
|
||||
remoteSubscriptions(clientID, newSubRoot);
|
||||
removeSubscriptionFromChildren(clientID, newSubRoot);
|
||||
return newSubRoot;
|
||||
}
|
||||
|
||||
private void removeSubscriptionFromChildren(String clientID, TreeNode newSubRoot) {
|
||||
int newSubtreeSubscriptions = 0;
|
||||
//go deep
|
||||
List<TreeNode> newChildren = new ArrayList<>(newSubRoot.m_children.size());
|
||||
for (TreeNode child : newSubRoot.m_children) {
|
||||
final TreeNode purgedSubtree = child.removeClientSubscriptions(clientID);
|
||||
if (purgedSubtree.size() != 0) {
|
||||
newSubtreeSubscriptions += purgedSubtree.size();
|
||||
newChildren.add(purgedSubtree);
|
||||
}
|
||||
}
|
||||
newSubRoot.m_children = newChildren;
|
||||
newSubRoot.subtreeSubscriptions += newSubtreeSubscriptions;
|
||||
}
|
||||
|
||||
private void remoteSubscriptions(String clientID, TreeNode newSubRoot) {
|
||||
List<ClientTopicCouple> subsToRemove = new ArrayList<>();
|
||||
for (ClientTopicCouple s : newSubRoot.m_subscriptions) {
|
||||
if (s.clientID.equals(clientID)) {
|
||||
subsToRemove.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
for (ClientTopicCouple s : subsToRemove) {
|
||||
newSubRoot.m_subscriptions.remove(s);
|
||||
}
|
||||
newSubRoot.subtreeSubscriptions = newSubRoot.m_subscriptions.size();
|
||||
}
|
||||
|
||||
int recalculateSubscriptionsSize() {
|
||||
int res = m_subscriptions.size();
|
||||
for (TreeNode child : m_children) {
|
||||
res += child.recalculateSubscriptionsSize();
|
||||
}
|
||||
this.subtreeSubscriptions = res;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user