Moved project package from org.eclipse.moquette to io.moquette
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 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 java.io.Serializable;
|
||||
import io.moquette.proto.messages.AbstractMessage.QOSType;
|
||||
|
||||
/**
|
||||
* Maintain the information about which Topic a certain ClientID is subscribed
|
||||
* and at which QoS
|
||||
*
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class Subscription implements Serializable {
|
||||
|
||||
QOSType requestedQos; //max QoS acceptable
|
||||
String clientId;
|
||||
String topicFilter;
|
||||
boolean cleanSession;
|
||||
boolean active = true;
|
||||
|
||||
public Subscription(String clientId, String topicFilter, QOSType requestedQos, boolean cleanSession) {
|
||||
this.requestedQos = requestedQos;
|
||||
this.clientId = clientId;
|
||||
this.topicFilter = topicFilter;
|
||||
this.cleanSession = cleanSession;
|
||||
}
|
||||
|
||||
public Subscription(Subscription orig) {
|
||||
this.requestedQos = orig.requestedQos;
|
||||
this.clientId = orig.clientId;
|
||||
this.topicFilter = orig.topicFilter;
|
||||
this.cleanSession = orig.cleanSession;
|
||||
this.active = orig.active;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public QOSType getRequestedQos() {
|
||||
return requestedQos;
|
||||
}
|
||||
|
||||
public String getTopicFilter() {
|
||||
return topicFilter;
|
||||
}
|
||||
|
||||
public boolean isCleanSession() {
|
||||
return this.cleanSession;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Subscription that = (Subscription) o;
|
||||
|
||||
if (cleanSession != that.cleanSession) return false;
|
||||
if (clientId != null ? !clientId.equals(that.clientId) : that.clientId != null) return false;
|
||||
if (requestedQos != that.requestedQos) return false;
|
||||
if (topicFilter != null ? !topicFilter.equals(that.topicFilter) : that.topicFilter != null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = requestedQos.hashCode();
|
||||
result = 31 * result + (clientId != null ? clientId.hashCode() : 0);
|
||||
result = 31 * result + (topicFilter != null ? topicFilter.hashCode() : 0);
|
||||
result = 31 * result + (cleanSession ? 1 : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[filter:%s, cliID: %s, qos: %s, active: %s]", this.topicFilter, this.clientId, this.requestedQos, this.active);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Subscription clone() {
|
||||
try {
|
||||
return (Subscription) super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 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 java.text.ParseException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import io.moquette.spi.ISessionsStore;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Represents a tree of topics subscriptions.
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class SubscriptionsStore {
|
||||
|
||||
public static class NodeCouple {
|
||||
final TreeNode root;
|
||||
final TreeNode createdNode;
|
||||
|
||||
public NodeCouple(TreeNode root, TreeNode createdNode) {
|
||||
this.root = root;
|
||||
this.createdNode = createdNode;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the topic filter of the subscription is well formed
|
||||
* */
|
||||
public static boolean validate(String topicFilter) {
|
||||
try {
|
||||
parseTopic(topicFilter);
|
||||
return true;
|
||||
} catch (ParseException pex) {
|
||||
LOG.info("Bad matching topic filter <{}>", topicFilter);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
String subScriptionsStr = "";
|
||||
String indentTabs = indentTabs(deep);
|
||||
for (Subscription sub : node.m_subscriptions) {
|
||||
subScriptionsStr += indentTabs + sub.toString() + "\n";
|
||||
}
|
||||
s += node.getToken() == null ? "" : node.getToken().toString();
|
||||
s += "\n" + (node.m_subscriptions.isEmpty() ? indentTabs : "") + subScriptionsStr /*+ "\n"*/;
|
||||
}
|
||||
|
||||
private String indentTabs(int deep) {
|
||||
String s = "";
|
||||
for (int i=0; i < deep; i++) {
|
||||
s += "\t";
|
||||
// s += "--";
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResult() {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
private AtomicReference<TreeNode> subscriptions = new AtomicReference<>(new TreeNode(null));
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SubscriptionsStore.class);
|
||||
|
||||
/**
|
||||
* Initialize the subscription tree with the list of subscriptions.
|
||||
* Maintained for compatibility reasons.
|
||||
*/
|
||||
public void init(ISessionsStore sessionsStore) {
|
||||
LOG.debug("init invoked");
|
||||
List<Subscription> subscriptions = sessionsStore.listAllSubscriptions();
|
||||
//reload any subscriptions persisted
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Reloading all stored subscriptions...subscription tree before {}", dumpTree());
|
||||
}
|
||||
|
||||
for (Subscription subscription : subscriptions) {
|
||||
LOG.debug("Re-subscribing {} to topic {}", subscription.getClientId(), subscription.getTopicFilter());
|
||||
add(subscription);
|
||||
}
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Finished loading. Subscription tree after {}", dumpTree());
|
||||
}
|
||||
}
|
||||
|
||||
public void add(Subscription newSubscription) {
|
||||
TreeNode oldRoot;
|
||||
NodeCouple couple;
|
||||
do {
|
||||
oldRoot = subscriptions.get();
|
||||
couple = recreatePath(newSubscription.topicFilter, oldRoot);
|
||||
couple.createdNode.addSubscription(newSubscription); //createdNode could be null?
|
||||
//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("root ref {}, original root was {}", couple.root, oldRoot);
|
||||
}
|
||||
|
||||
|
||||
protected NodeCouple recreatePath(String topic, final TreeNode oldRoot) {
|
||||
List<Token> tokens = new ArrayList<>();
|
||||
try {
|
||||
tokens = parseTopic(topic);
|
||||
} catch (ParseException ex) {
|
||||
//TODO handle the parse exception
|
||||
LOG.error(null, ex);
|
||||
}
|
||||
|
||||
final TreeNode newRoot = oldRoot.copy();
|
||||
TreeNode parent = newRoot;
|
||||
TreeNode current = newRoot;
|
||||
for (Token token : tokens) {
|
||||
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();
|
||||
current.m_parent = parent;
|
||||
//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(current);
|
||||
matchingChildren.setToken(token);
|
||||
current.addChild(matchingChildren);
|
||||
current = matchingChildren;
|
||||
}
|
||||
}
|
||||
return new NodeCouple(newRoot, current);
|
||||
}
|
||||
|
||||
public void removeSubscription(String topic, String clientID) {
|
||||
TreeNode oldRoot;
|
||||
NodeCouple couple;
|
||||
do {
|
||||
oldRoot = subscriptions.get();
|
||||
couple = recreatePath(topic, oldRoot);
|
||||
|
||||
//do the job
|
||||
//search for the subscription to remove
|
||||
Subscription toBeRemoved = null;
|
||||
for (Subscription sub : couple.createdNode.subscriptions()) {
|
||||
if (sub.topicFilter.equals(topic) && sub.getClientId().equals(clientID)) {
|
||||
toBeRemoved = sub;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (toBeRemoved != null) {
|
||||
couple.createdNode.subscriptions().remove(toBeRemoved);
|
||||
}
|
||||
//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).
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public List<Subscription> matches(String topic) {
|
||||
List<Token> tokens;
|
||||
try {
|
||||
tokens = parseTopic(topic);
|
||||
} catch (ParseException ex) {
|
||||
//TODO handle the parse exception
|
||||
LOG.error(null, ex);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
Queue<Token> tokenQueue = new LinkedBlockingDeque<>(tokens);
|
||||
List<Subscription> matchingSubs = new ArrayList<>();
|
||||
subscriptions.get().matches(tokenQueue, matchingSubs);
|
||||
|
||||
//remove the overlapping subscriptions, selecting ones with greatest qos
|
||||
Map<String, Subscription> subsForClient = new HashMap<>();
|
||||
for (Subscription sub : matchingSubs) {
|
||||
Subscription existingSub = subsForClient.get(sub.getClientId());
|
||||
//update the selected subscriptions if not present or if has a greater qos
|
||||
if (existingSub == null || existingSub.getRequestedQos().byteValue() < sub.getRequestedQos().byteValue()) {
|
||||
subsForClient.put(sub.getClientId(), sub);
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(subsForClient.values());
|
||||
}
|
||||
|
||||
public boolean contains(Subscription sub) {
|
||||
return !matches(sub.topicFilter).isEmpty();
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return subscriptions.get().size();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if the 2 topics matching respecting the rules of MQTT Appendix A
|
||||
*/
|
||||
//TODO reimplement with iterators or with queues
|
||||
public static boolean matchTopics(String msgTopic, String subscriptionTopic) {
|
||||
try {
|
||||
List<Token> msgTokens = SubscriptionsStore.parseTopic(msgTopic);
|
||||
List<Token> subscriptionTokens = SubscriptionsStore.parseTopic(subscriptionTopic);
|
||||
int i = 0;
|
||||
for (; i< subscriptionTokens.size(); i++) {
|
||||
Token subToken = subscriptionTokens.get(i);
|
||||
if (subToken != Token.MULTI && subToken != Token.SINGLE) {
|
||||
if (i >= msgTokens.size()) {
|
||||
return false;
|
||||
}
|
||||
Token msgToken = msgTokens.get(i);
|
||||
if (!msgToken.equals(subToken)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (subToken == Token.MULTI) {
|
||||
return true;
|
||||
}
|
||||
if (subToken == Token.SINGLE) {
|
||||
//skip a step forward
|
||||
}
|
||||
}
|
||||
}
|
||||
//if last token was a SINGLE then treat it as an empty
|
||||
// if (subToken == Token.SINGLE && (i - msgTokens.size() == 1)) {
|
||||
// i--;
|
||||
// }
|
||||
return i == msgTokens.size();
|
||||
} catch (ParseException ex) {
|
||||
LOG.error(null, ex);
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected static List<Token> parseTopic(String topic) throws ParseException {
|
||||
List<Token> res = new ArrayList<>();
|
||||
String[] splitted = topic.split("/");
|
||||
|
||||
if (splitted.length == 0) {
|
||||
res.add(Token.EMPTY);
|
||||
}
|
||||
|
||||
if (topic.endsWith("/")) {
|
||||
//Add a fictious space
|
||||
String[] newSplitted = new String[splitted.length + 1];
|
||||
System.arraycopy(splitted, 0, newSplitted, 0, splitted.length);
|
||||
newSplitted[splitted.length] = "";
|
||||
splitted = newSplitted;
|
||||
}
|
||||
|
||||
for (int i = 0; i < splitted.length; i++) {
|
||||
String s = splitted[i];
|
||||
if (s.isEmpty()) {
|
||||
// if (i != 0) {
|
||||
// throw new ParseException("Bad format of topic, expetec topic name between separators", i);
|
||||
// }
|
||||
res.add(Token.EMPTY);
|
||||
} else if (s.equals("#")) {
|
||||
//check that multi is the last symbol
|
||||
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);
|
||||
}
|
||||
res.add(Token.MULTI);
|
||||
} else if (s.contains("#")) {
|
||||
throw new ParseException("Bad format of topic, invalid subtopic name: " + s, i);
|
||||
} else if (s.equals("+")) {
|
||||
res.add(Token.SINGLE);
|
||||
} else if (s.contains("+")) {
|
||||
throw new ParseException("Bad format of topic, invalid subtopic name: " + s, i);
|
||||
} else {
|
||||
res.add(new Token(s));
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 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;
|
||||
|
||||
/**
|
||||
* Internal use only class.
|
||||
* */
|
||||
public class Token {
|
||||
|
||||
static final Token EMPTY = new Token("");
|
||||
static final Token MULTI = new Token("#");
|
||||
static final Token SINGLE = new Token("+");
|
||||
final String name;
|
||||
|
||||
protected Token(String s) {
|
||||
name = s;
|
||||
}
|
||||
|
||||
protected String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
protected boolean match(Token t) {
|
||||
if (t == MULTI || t == SINGLE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this == MULTI || this == SINGLE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return equals(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 7;
|
||||
hash = 29 * hash + (this.name != null ? this.name.hashCode() : 0);
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final Token other = (Token) obj;
|
||||
if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 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 java.util.*;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
class TreeNode {
|
||||
|
||||
private class ClientIDComparator implements Comparator<Subscription> {
|
||||
|
||||
public int compare(Subscription o1, Subscription o2) {
|
||||
return o1.getClientId().compareTo(o2.getClientId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TreeNode m_parent;
|
||||
Token m_token;
|
||||
List<TreeNode> m_children = new ArrayList<>();
|
||||
List<Subscription> m_subscriptions = new ArrayList<>();
|
||||
|
||||
TreeNode(TreeNode parent) {
|
||||
this.m_parent = parent;
|
||||
}
|
||||
|
||||
Token getToken() {
|
||||
return m_token;
|
||||
}
|
||||
|
||||
void setToken(Token topic) {
|
||||
this.m_token = topic;
|
||||
}
|
||||
|
||||
void addSubscription(Subscription s) {
|
||||
//avoid double registering for same clientID, topic and QoS
|
||||
if (m_subscriptions.contains(s)) {
|
||||
return;
|
||||
}
|
||||
//remove existing subscription for same client and topic but different QoS
|
||||
Comparator<Subscription> comparator = new ClientIDComparator();
|
||||
Collections.sort(m_subscriptions, comparator);
|
||||
int existingSubIdx = Collections.binarySearch(m_subscriptions, s, comparator);
|
||||
if (existingSubIdx >= 0) {
|
||||
m_subscriptions.remove(existingSubIdx);
|
||||
}
|
||||
|
||||
m_subscriptions.add(s);
|
||||
}
|
||||
|
||||
void addChild(TreeNode child) {
|
||||
m_children.add(child);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a shallow copy of the current node.
|
||||
* Copy the token and the children.
|
||||
* */
|
||||
TreeNode copy() {
|
||||
final TreeNode copy = new TreeNode(this);
|
||||
copy.m_parent = m_parent;
|
||||
copy.m_children = new ArrayList<>(m_children);
|
||||
copy.m_subscriptions = new ArrayList<>(m_subscriptions.size());
|
||||
for (Subscription sub : m_subscriptions) {
|
||||
copy.m_subscriptions.add(new Subscription(sub));
|
||||
}
|
||||
copy.m_token = m_token;
|
||||
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);
|
||||
}
|
||||
|
||||
List<Subscription> subscriptions() {
|
||||
return m_subscriptions;
|
||||
}
|
||||
|
||||
void matches(Queue<Token> tokens, List<Subscription> 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() {
|
||||
int res = m_subscriptions.size();
|
||||
for (TreeNode child : m_children) {
|
||||
res += child.size();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
List<Subscription> subsToRemove = new ArrayList<>();
|
||||
for (Subscription s : newSubRoot.m_subscriptions) {
|
||||
if (s.clientId.equals(clientID)) {
|
||||
subsToRemove.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
for (Subscription s : subsToRemove) {
|
||||
newSubRoot.m_subscriptions.remove(s);
|
||||
}
|
||||
|
||||
//go deep
|
||||
List<TreeNode> newChildren = new ArrayList<>(newSubRoot.m_children.size());
|
||||
for (TreeNode child : newSubRoot.m_children) {
|
||||
newChildren.add(child.removeClientSubscriptions(clientID));
|
||||
}
|
||||
newSubRoot.m_children = newChildren;
|
||||
return newSubRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the set of subscriptions for the given client.
|
||||
* */
|
||||
Set<Subscription> findAllByClientID(String clientID) {
|
||||
Set<Subscription> subs = new HashSet<>();
|
||||
for (Subscription s : m_subscriptions) {
|
||||
if (s.clientId.equals(clientID)) {
|
||||
subs.add(s);
|
||||
}
|
||||
}
|
||||
//go deep
|
||||
for (TreeNode child : m_children) {
|
||||
subs.addAll(child.findAllByClientID(clientID));
|
||||
}
|
||||
return subs;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user