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:
andsel
2017-10-14 17:37:26 +02:00
parent 6bacce6cec
commit 1b272e5849
43 changed files with 1107 additions and 1710 deletions
@@ -2,7 +2,7 @@ package io.moquette.persistence;
import io.moquette.spi.*;
import io.moquette.spi.IMessagesStore.StoredMessage;
import io.moquette.spi.ISubscriptionsStore.ClientTopicCouple;
import io.moquette.spi.impl.SessionsRepository;
import io.moquette.spi.impl.subscriptions.Subscription;
import io.moquette.spi.impl.subscriptions.Topic;
import io.netty.buffer.ByteBuf;
@@ -25,10 +25,11 @@ public abstract class MessageStoreTCK {
public static final String TEST_CLIENT = "TestClient";
protected ISessionsStore sessionsStore;
protected IMessagesStore messagesStore;
protected SessionsRepository sessionsRepository;
@Test
public void testDropMessagesInSessionDoesntCleanAnyRetainedStoredMessages() {
final ClientSession session = sessionsStore.createNewSession(TEST_CLIENT, true);
final ClientSession session = sessionsRepository.createNewSession(TEST_CLIENT, true);
StoredMessage publishToStore = new StoredMessage("Hello".getBytes(), EXACTLY_ONCE, "/topic");
publishToStore.setClientID(TEST_CLIENT);
publishToStore.setRetained(true);
@@ -72,7 +73,7 @@ public abstract class MessageStoreTCK {
@Test
public void givenSubscriptionAlreadyStoredIsOverwrittenByAnotherWithSameTopic() {
ClientSession session1 = sessionsStore.createNewSession("SESSION_ID_1", true);
ClientSession session1 = sessionsRepository.createNewSession("SESSION_ID_1", false);
// Subscribe on /topic with QOSType.MOST_ONE
Subscription oldSubscription = new Subscription(session1.clientID, new Topic("/topic"), AT_MOST_ONCE);
@@ -84,9 +85,9 @@ public abstract class MessageStoreTCK {
// Verify
final ISubscriptionsStore subscriptionsStore = sessionsStore.subscriptionStore();
List<ClientTopicCouple> subscriptions = subscriptionsStore.listAllSubscriptions();
List<Subscription> subscriptions = subscriptionsStore.listAllSubscriptions();
assertEquals(1, subscriptions.size());
Subscription sub = subscriptionsStore.getSubscription(subscriptions.get(0));
Subscription sub = subscriptions.get(0);
assertEquals(overridingSubscription.getRequestedQos(), sub.getRequestedQos());
}
}
@@ -17,7 +17,11 @@
package io.moquette.spi;
import io.moquette.persistence.MemoryStorageService;
import io.moquette.spi.impl.subscriptions.*;
import io.moquette.spi.impl.SessionsRepository;
import io.moquette.spi.impl.subscriptions.CTrieSubscriptionDirectory;
import io.moquette.spi.impl.subscriptions.ISubscriptionsDirectory;
import io.moquette.spi.impl.subscriptions.Subscription;
import io.moquette.spi.impl.subscriptions.Topic;
import org.junit.Before;
import org.junit.Test;
@@ -29,38 +33,46 @@ import static org.junit.Assert.assertEquals;
public class ClientSessionTest {
ClientSession session1;
ClientSession session2;
ISessionsStore sessionsStore;
ISubscriptionsDirectory store;
private ISubscriptionsDirectory subscriptionsDirectory;
private SessionsRepository sessionsRepository;
@Before
public void setUp() {
store = new CTrieSubscriptionDirectory();
subscriptionsDirectory = new CTrieSubscriptionDirectory();
MemoryStorageService storageService = new MemoryStorageService(null, null);
this.sessionsStore = storageService.sessionsStore();
store.init(sessionsStore);
session1 = sessionsStore.createNewSession("SESSION_ID_1", true);
session2 = sessionsStore.createNewSession("SESSION_ID_2", true);
ISessionsStore sessionsStore = storageService.sessionsStore();
sessionsRepository = new SessionsRepository(sessionsStore);
subscriptionsDirectory.init(sessionsRepository);
}
@Test
public void overridingSubscriptions() {
public void overridingSubscriptionsForTransientSession() {
ClientSession sessionClean = sessionsRepository.createNewSession("SESSION_ID_1", true);
checkOverridingSubcription(sessionClean);
}
@Test
public void overridingSubscriptionsForPersistentSession() {
ClientSession sessionClean = sessionsRepository.createNewSession("SESSION_ID_1", false);
checkOverridingSubcription(sessionClean);
}
private void checkOverridingSubcription(ClientSession sessionClean) {
// Subscribe on /topic with QOSType.MOST_ONE
Subscription oldSubscription = new Subscription(session1.clientID, new Topic("/topic"), AT_MOST_ONCE);
session1.subscribe(oldSubscription);
store.add(oldSubscription.asClientTopicCouple());
Subscription oldSubscription = new Subscription(sessionClean.clientID, new Topic("/topic"), AT_MOST_ONCE);
sessionClean.subscribe(oldSubscription);
subscriptionsDirectory.add(oldSubscription);
// Subscribe on /topic again that overrides the previous subscription.
Subscription overrindingSubscription = new Subscription(session1.clientID, new Topic("/topic"), EXACTLY_ONCE);
session1.subscribe(overrindingSubscription);
store.add(overrindingSubscription.asClientTopicCouple());
Subscription overrindingSubscription = new Subscription(sessionClean.clientID, new Topic("/topic"), EXACTLY_ONCE);
sessionClean.subscribe(overrindingSubscription);
subscriptionsDirectory.add(overrindingSubscription);
// Verify
List<Subscription> subscriptions = store.matches(new Topic("/topic"));
assertEquals(1, subscriptions.size());
List<Subscription> subscriptions = subscriptionsDirectory.matches(new Topic("/topic"));
assertEquals("Only one subscription must match /topic", 1, subscriptions.size());
Subscription sub = subscriptions.get(0);
assertEquals(overrindingSubscription.getRequestedQos(), sub.getRequestedQos());
assertEquals("Matching subscription MUST have higher QoS",
overrindingSubscription.getRequestedQos(), sub.getRequestedQos());
}
}
@@ -20,7 +20,6 @@ import io.moquette.persistence.MemoryStorageService;
import io.moquette.server.netty.NettyUtils;
import io.moquette.spi.IMessagesStore;
import io.moquette.spi.ISessionsStore;
import io.moquette.spi.ISubscriptionsStore.ClientTopicCouple;
import io.moquette.spi.impl.security.PermitAllAuthorizator;
import io.moquette.spi.impl.subscriptions.*;
import io.netty.buffer.Unpooled;
@@ -56,6 +55,7 @@ abstract class AbstractProtocolProcessorCommonUtils {
ISessionsStore m_sessionStore;
ISubscriptionsDirectory subscriptions;
MockAuthenticator m_mockAuthenticator;
protected SessionsRepository sessionsRepository;
protected void initializeProcessorAndSubsystems() {
m_channel = new EmbeddedChannel();
@@ -68,6 +68,8 @@ abstract class AbstractProtocolProcessorCommonUtils {
m_sessionStore = memStorage.sessionsStore();
// m_messagesStore.initStore();
sessionsRepository = new SessionsRepository(m_sessionStore);
Set<String> clientIds = new HashSet<>();
clientIds.add(FAKE_CLIENT_ID);
clientIds.add(FAKE_CLIENT_ID2);
@@ -76,14 +78,15 @@ abstract class AbstractProtocolProcessorCommonUtils {
m_mockAuthenticator = new MockAuthenticator(clientIds, users);
subscriptions = new CTrieSubscriptionDirectory();
subscriptions.init(memStorage.sessionsStore());
subscriptions.init(sessionsRepository);
m_processor = new ProtocolProcessor();
m_processor.init(subscriptions, m_messagesStore, m_sessionStore, m_mockAuthenticator, true,
new PermitAllAuthorizator(), NO_OBSERVERS_INTERCEPTOR);
new PermitAllAuthorizator(), NO_OBSERVERS_INTERCEPTOR, sessionsRepository);
}
protected void verifyNoPublishIsReceived() {
assertNull("Received an out message from processor while not expected", m_channel.readOutbound());
final Object messageReceived = m_channel.readOutbound();
assertNull("Received an out message from processor while not expected", messageReceived);
}
protected void subscribe(String topic, MqttQoS desiredQos) {
@@ -106,8 +109,7 @@ abstract class AbstractProtocolProcessorCommonUtils {
protected static void verifySubscriptionExists(Channel channel, ISessionsStore sessionsStore, Subscription expectedSubscription) {
final String clientId = NettyUtils.clientID(channel);
final Subscription subscription = sessionsStore.subscriptionStore()
.getSubscription(new ClientTopicCouple(clientId, expectedSubscription.getTopicFilter()));
final Subscription subscription = sessionsStore.subscriptionStore().reload(expectedSubscription);
assertEquals(expectedSubscription, subscription);
}
@@ -22,9 +22,9 @@ import io.moquette.server.netty.NettyUtils;
import io.moquette.spi.IMessagesStore;
import io.moquette.spi.IMessagesStore.StoredMessage;
import io.moquette.spi.impl.security.PermitAllAuthorizator;
import io.moquette.spi.impl.subscriptions.CTrieSubscriptionDirectory;
import io.moquette.spi.impl.subscriptions.ISubscriptionsDirectory;
import io.moquette.spi.impl.subscriptions.Subscription;
import io.moquette.spi.impl.subscriptions.SubscriptionsDirectory;
import io.moquette.spi.impl.subscriptions.Topic;
import io.moquette.spi.security.IAuthorizator;
import io.netty.buffer.Unpooled;
@@ -33,7 +33,10 @@ import io.netty.handler.codec.mqtt.*;
import org.junit.Before;
import org.junit.Test;
import java.util.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static io.moquette.spi.impl.NettyChannelAssertions.assertConnAckAccepted;
import static io.moquette.spi.impl.ProtocolProcessor.lowerQosToTheSubscriptionDesired;
@@ -69,7 +72,7 @@ public class ProtocolProcessorTest extends AbstractProtocolProcessorCommonUtils
final Subscription subscription = new Subscription(FAKE_CLIENT_ID, new Topic(FAKE_TOPIC), AT_MOST_ONCE);
// subscriptions.matches(topic) redefine the method to return true
ISubscriptionsDirectory subs = new SubscriptionsDirectory() {
ISubscriptionsDirectory subs = new CTrieSubscriptionDirectory() {
@Override
public List<Subscription> matches(Topic topic) {
@@ -83,9 +86,10 @@ public class ProtocolProcessorTest extends AbstractProtocolProcessorCommonUtils
// simulate a connect that register a clientID to an IoSession
MemoryStorageService storageService = new MemoryStorageService(null, null);
subs.init(storageService.sessionsStore());
SessionsRepository sessionsRepository = new SessionsRepository(storageService.sessionsStore());
subs.init(sessionsRepository);
m_processor.init(subs, m_messagesStore, m_sessionStore, null, true, new PermitAllAuthorizator(),
NO_OBSERVERS_INTERCEPTOR);
NO_OBSERVERS_INTERCEPTOR, this.sessionsRepository);
connect_v3_1();
@@ -105,7 +109,7 @@ public class ProtocolProcessorTest extends AbstractProtocolProcessorCommonUtils
AT_MOST_ONCE);
// subscriptions.matches(topic) redefine the method to return true
ISubscriptionsDirectory subs = new SubscriptionsDirectory() {
ISubscriptionsDirectory subs = new CTrieSubscriptionDirectory() {
@Override
public List<Subscription> matches(Topic topic) {
@@ -119,9 +123,10 @@ public class ProtocolProcessorTest extends AbstractProtocolProcessorCommonUtils
// simulate a connect that register a clientID to an IoSession
MemoryStorageService storageService = new MemoryStorageService(null, null);
subs.init(storageService.sessionsStore());
SessionsRepository sessionsRepository = new SessionsRepository(storageService.sessionsStore());
subs.init(sessionsRepository);
m_processor.init(subs, m_messagesStore, m_sessionStore, null, true, new PermitAllAuthorizator(),
NO_OBSERVERS_INTERCEPTOR);
NO_OBSERVERS_INTERCEPTOR, this.sessionsRepository);
EmbeddedChannel firstReceiverChannel = new EmbeddedChannel();
MqttConnectMessage connectMessage = MqttMessageBuilders.connect().protocolVersion(MqttVersion.MQTT_3_1)
@@ -174,7 +179,7 @@ public class ProtocolProcessorTest extends AbstractProtocolProcessorCommonUtils
.thenReturn(false);
m_processor.init(subscriptions, m_messagesStore, m_sessionStore, m_mockAuthenticator, true, mockAuthorizator,
NO_OBSERVERS_INTERCEPTOR);
NO_OBSERVERS_INTERCEPTOR, this.sessionsRepository);
connect();
@@ -229,7 +234,7 @@ public class ProtocolProcessorTest extends AbstractProtocolProcessorCommonUtils
new Subscription(FAKE_PUBLISHER_ID, new Topic(FAKE_TOPIC), AT_MOST_ONCE);
// subscriptions.matches(topic) redefine the method to return true
ISubscriptionsDirectory subs = new SubscriptionsDirectory() {
ISubscriptionsDirectory subs = new CTrieSubscriptionDirectory() {
@Override
public List<Subscription> matches(Topic topic) {
@@ -241,11 +246,12 @@ public class ProtocolProcessorTest extends AbstractProtocolProcessorCommonUtils
}
};
MemoryStorageService storageService = new MemoryStorageService(null, null);
subs.init(storageService.sessionsStore());
SessionsRepository sessionsRepository = new SessionsRepository(storageService.sessionsStore());
subs.init(sessionsRepository);
// simulate a connect that register a clientID to an IoSession
m_processor.init(subs, m_messagesStore, m_sessionStore, null, true, new PermitAllAuthorizator(),
NO_OBSERVERS_INTERCEPTOR);
NO_OBSERVERS_INTERCEPTOR, this.sessionsRepository);
connect_v3_1_asClient(FAKE_PUBLISHER_ID);
publishToAs(FAKE_PUBLISHER_ID, FAKE_TOPIC, AT_MOST_ONCE, true);
NettyUtils.cleanSession(m_channel, false);
@@ -259,7 +265,7 @@ public class ProtocolProcessorTest extends AbstractProtocolProcessorCommonUtils
@Test
public void testRepublishAndConsumePersistedMessages_onReconnect() {
ISubscriptionsDirectory subs = mock(SubscriptionsDirectory.class);
ISubscriptionsDirectory subs = mock(ISubscriptionsDirectory.class);
List<Subscription> emptySubs = Collections.emptyList();
when(subs.matches(any(Topic.class))).thenReturn(emptySubs);
@@ -269,27 +275,26 @@ public class ProtocolProcessorTest extends AbstractProtocolProcessorCommonUtils
m_messagesStore.storeRetained(new Topic("/topic"), retainedMessage);
m_processor.init(subs, m_messagesStore, m_sessionStore, null, true, new PermitAllAuthorizator(),
NO_OBSERVERS_INTERCEPTOR);
NO_OBSERVERS_INTERCEPTOR, this.sessionsRepository);
connect_v3_1_asClient(FAKE_PUBLISHER_ID);
// Verify no messages are still stored
Queue<StoredMessage> messages = m_sessionStore.queue(FAKE_PUBLISHER_ID);
assertTrue(messages.isEmpty());
assertTrue(this.sessionsRepository.sessionForClient(FAKE_PUBLISHER_ID).isEmptyQueue());
}
@Test
public void publishNoPublishToInactiveSession() {
// create an inactive session for Subscriber
m_sessionStore.createNewSession("Subscriber", false);
ISubscriptionsDirectory mockedSubscriptions = mock(SubscriptionsDirectory.class);
ISubscriptionsDirectory mockedSubscriptions = mock(ISubscriptionsDirectory.class);
Subscription inactiveSub = new Subscription("Subscriber", new Topic("/topic"), MqttQoS.AT_LEAST_ONCE);
List<Subscription> inactiveSubscriptions = Collections.singletonList(inactiveSub);
when(mockedSubscriptions.matches(eq(new Topic("/topic")))).thenReturn(inactiveSubscriptions);
m_processor = new ProtocolProcessor();
m_processor.init(mockedSubscriptions, m_messagesStore, m_sessionStore, null, true, new PermitAllAuthorizator(),
NO_OBSERVERS_INTERCEPTOR);
NO_OBSERVERS_INTERCEPTOR, this.sessionsRepository);
m_processor.sessionsRepository.createNewSession("Subscriber", false);
// Exercise
connectAsClient("Publisher");
@@ -22,7 +22,10 @@ import io.moquette.spi.ClientSession;
import io.moquette.spi.IMessagesStore;
import io.moquette.spi.ISessionsStore;
import io.moquette.spi.impl.security.PermitAllAuthorizator;
import io.moquette.spi.impl.subscriptions.*;
import io.moquette.spi.impl.subscriptions.CTrieSubscriptionDirectory;
import io.moquette.spi.impl.subscriptions.ISubscriptionsDirectory;
import io.moquette.spi.impl.subscriptions.Subscription;
import io.moquette.spi.impl.subscriptions.Topic;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.mqtt.*;
import org.junit.Before;
@@ -62,10 +65,11 @@ public class ProtocolProcessor_CONNECT_Test {
m_mockAuthenticator = new MockAuthenticator(singleton(FAKE_CLIENT_ID), singletonMap(TEST_USER, TEST_PWD));
subscriptions = new CTrieSubscriptionDirectory();
subscriptions.init(m_sessionStore);
SessionsRepository sessionsRepository = new SessionsRepository(m_sessionStore);
subscriptions.init(sessionsRepository);
m_processor = new ProtocolProcessor();
m_processor.init(subscriptions, m_messagesStore, m_sessionStore, m_mockAuthenticator, true,
new PermitAllAuthorizator(), NO_OBSERVERS_INTERCEPTOR);
new PermitAllAuthorizator(), NO_OBSERVERS_INTERCEPTOR, new SessionsRepository(this.m_sessionStore));
}
@Test
@@ -155,7 +159,7 @@ public class ProtocolProcessor_CONNECT_Test {
protected void reinitProcessorProhibitingAnonymousClients() {
m_processor.init(subscriptions, m_messagesStore, m_sessionStore, m_mockAuthenticator, false,
new PermitAllAuthorizator(), NO_OBSERVERS_INTERCEPTOR);
new PermitAllAuthorizator(), NO_OBSERVERS_INTERCEPTOR, new SessionsRepository(this.m_sessionStore));
}
@Test
@@ -203,7 +207,7 @@ public class ProtocolProcessor_CONNECT_Test {
assertEqualsConnAck(CONNECTION_ACCEPTED, m_session.readOutbound());
// Verify client session is clean false
ClientSession session = m_sessionStore.sessionForClient(FAKE_CLIENT_ID);
ClientSession session = m_processor.sessionsRepository.sessionForClient(FAKE_CLIENT_ID);
assertFalse(session.isCleanSession());
// Verify
@@ -320,7 +324,7 @@ public class ProtocolProcessor_CONNECT_Test {
protected void reinitProtocolProcessorWithZeroLengthClientIdAndAnonymousClients() {
m_processor.init(subscriptions, m_messagesStore, m_sessionStore, m_mockAuthenticator, true, true,
new PermitAllAuthorizator(), NO_OBSERVERS_INTERCEPTOR);
new PermitAllAuthorizator(), NO_OBSERVERS_INTERCEPTOR, new SessionsRepository(this.m_sessionStore));
}
@Test
@@ -177,13 +177,13 @@ public class ProtocolProcessor_InternalPublish_Test extends AbstractProtocolProc
public void testClientSubscribeWithoutCleanSession() throws Exception {
subscribe("foo", AT_MOST_ONCE);
disconnect();
assertEquals(1, m_processor.getSessionsStore().subscriptionStore().getSubscriptions().size());
assertEquals(1, m_sessionStore.subscriptionStore().listAllSubscriptions().size());
m_channel = new EmbeddedChannel();
NettyUtils.clientID(m_channel, FAKE_CLIENT_ID);
connectNoCleanSession();
assertEquals(1, m_processor.getSessionsStore().subscriptionStore().getSubscriptions().size());
assertEquals(1, m_sessionStore.subscriptionStore().listAllSubscriptions().size());
internalPublishTo("foo", MqttQoS.AT_MOST_ONCE, false);
verifyPublishIsReceived();
}
@@ -15,18 +15,14 @@
*/
package io.moquette.spi.impl;
import io.moquette.server.MessageCollector;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.mqtt.MqttQoS;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import static io.netty.handler.codec.mqtt.MqttQoS.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class ProtocolProcessor_VariousUseCases_Test extends AbstractProtocolProcessorCommonUtils {
@@ -43,8 +39,6 @@ public class ProtocolProcessor_VariousUseCases_Test extends AbstractProtocolProc
// reconnect and publish
EmbeddedChannel anotherChannel = new EmbeddedChannel();
// NettyUtils.clientID(anotherChannel, "TestClient");
// NettyUtils.cleanSession(anotherChannel, false);
connectAsClient(anotherChannel, "TestClient");
publishToAs(anotherChannel, "TestClient", "/topic", AT_MOST_ONCE, false);
@@ -18,13 +18,11 @@ import io.moquette.persistence.MemoryStorageService;
import io.moquette.spi.ClientSession;
import io.moquette.spi.ISessionsStore;
import io.moquette.spi.ISubscriptionsStore;
import io.moquette.spi.ISubscriptionsStore.ClientTopicCouple;
import io.moquette.spi.impl.SessionsRepository;
import io.netty.handler.codec.mqtt.MqttQoS;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -37,28 +35,27 @@ import static org.junit.Assert.assertTrue;
public class CTrieSubscriptionDirectoryMatchingTest {
private CTrieSubscriptionDirectory sut;
private ISessionsStore sessionsStore;
private ISubscriptionsStore subscriptionsStore;
private MemoryStorageService storageService;
private SessionsRepository sessionsRepository;
@Before
public void setUp() {
sut = new CTrieSubscriptionDirectory();
this.storageService = new MemoryStorageService(null, null);
this.sessionsStore = storageService.sessionsStore();
sut.init(sessionsStore);
this.subscriptionsStore = this.sessionsStore.subscriptionStore();
MemoryStorageService storageService = new MemoryStorageService(null, null);
ISessionsStore sessionsStore = storageService.sessionsStore();
this.subscriptionsStore = sessionsStore.subscriptionStore();
this.sessionsRepository = new SessionsRepository(sessionsStore);
sut.init(this.sessionsRepository);
}
@Test
public void testMatchSimple() {
ClientTopicCouple slashSub = clientSubOnTopic("TempSensor1", "/");
Subscription slashSub = clientSubOnTopic("TempSensor1", "/");
sut.add(slashSub);
assertThat(sut.recursiveMatch(asTopic("finance"), sut.root)).isEmpty();
ClientTopicCouple slashFinanceSub = clientSubOnTopic("TempSensor1", "/finance");
Subscription slashFinanceSub = clientSubOnTopic("TempSensor1", "/finance");
sut.add(slashFinanceSub);
assertThat(sut.recursiveMatch(asTopic("finance"), sut.root)).isEmpty();
@@ -68,19 +65,19 @@ public class CTrieSubscriptionDirectoryMatchingTest {
@Test
public void testMatchSimpleMulti() {
ClientTopicCouple anySub = clientSubOnTopic("TempSensor1", "#");
Subscription anySub = clientSubOnTopic("TempSensor1", "#");
sut.add(anySub);
assertThat(sut.recursiveMatch(asTopic("finance"), sut.root)).contains(anySub);
ClientTopicCouple financeAnySub = clientSubOnTopic("TempSensor1", "finance/#");
Subscription financeAnySub = clientSubOnTopic("TempSensor1", "finance/#");
sut.add(financeAnySub);
assertThat(sut.recursiveMatch(asTopic("finance"), sut.root)).containsExactlyInAnyOrder(financeAnySub, anySub);
}
@Test
public void testMatchingDeepMulti_one_layer() {
ClientTopicCouple anySub = clientSubOnTopic("AllSensor1", "#");
ClientTopicCouple financeAnySub = clientSubOnTopic("FinanceSensor", "finance/#");
Subscription anySub = clientSubOnTopic("AllSensor1", "#");
Subscription financeAnySub = clientSubOnTopic("FinanceSensor", "finance/#");
sut.add(anySub);
sut.add(financeAnySub);
@@ -93,7 +90,7 @@ public class CTrieSubscriptionDirectoryMatchingTest {
@Test
public void testMatchingDeepMulti_two_layer() {
ClientTopicCouple financeAnySub = clientSubOnTopic("FinanceSensor", "finance/stock/#");
Subscription financeAnySub = clientSubOnTopic("FinanceSensor", "finance/stock/#");
sut.add(financeAnySub);
// Verify
@@ -102,18 +99,18 @@ public class CTrieSubscriptionDirectoryMatchingTest {
@Test
public void testMatchSimpleSingle() {
ClientTopicCouple anySub = clientSubOnTopic("AnySensor", "+");
Subscription anySub = clientSubOnTopic("AnySensor", "+");
sut.add(anySub);
assertThat(sut.recursiveMatch(asTopic("finance"), sut.root)).containsExactly(anySub);
ClientTopicCouple financeOne = clientSubOnTopic("AnySensor", "finance/+");
Subscription financeOne = clientSubOnTopic("AnySensor", "finance/+");
sut.add(financeOne);
assertThat(sut.recursiveMatch(asTopic("finance/stock"), sut.root)).containsExactly(financeOne);
}
@Test
public void testMatchManySingle() {
ClientTopicCouple manySub = clientSubOnTopic("AnySensor", "+/+");
Subscription manySub = clientSubOnTopic("AnySensor", "+/+");
sut.add(manySub);
// verify
@@ -122,9 +119,9 @@ public class CTrieSubscriptionDirectoryMatchingTest {
@Test
public void testMatchSlashSingle() {
ClientTopicCouple slashPlusSub = clientSubOnTopic("AnySensor", "/+");
Subscription slashPlusSub = clientSubOnTopic("AnySensor", "/+");
sut.add(slashPlusSub);
ClientTopicCouple anySub = clientSubOnTopic("AnySensor", "+");
Subscription anySub = clientSubOnTopic("AnySensor", "+");
sut.add(anySub);
// Verify
@@ -134,9 +131,9 @@ public class CTrieSubscriptionDirectoryMatchingTest {
@Test
public void testMatchManyDeepSingle() {
ClientTopicCouple slashPlusSub = clientSubOnTopic("FinanceSensor1", "/finance/+/ibm");
Subscription slashPlusSub = clientSubOnTopic("FinanceSensor1", "/finance/+/ibm");
sut.add(slashPlusSub);
ClientTopicCouple slashPlusDeepSub = clientSubOnTopic("FinanceSensor2", "/+/stock/+");
Subscription slashPlusDeepSub = clientSubOnTopic("FinanceSensor2", "/+/stock/+");
sut.add(slashPlusDeepSub);
// Verify
@@ -146,7 +143,7 @@ public class CTrieSubscriptionDirectoryMatchingTest {
@Test
public void testMatchSimpleMulti_allTheTree() {
ClientTopicCouple sub = clientSubOnTopic("AnySensor1", "#");
Subscription sub = clientSubOnTopic("AnySensor1", "#");
sut.add(sub);
assertThat(sut.recursiveMatch(asTopic("finance"), sut.root)).isNotEmpty();
@@ -181,9 +178,10 @@ public class CTrieSubscriptionDirectoryMatchingTest {
sut = new CTrieSubscriptionDirectory();
MemoryStorageService memStore = new MemoryStorageService(null, null);
ISessionsStore aSessionsStore = memStore.sessionsStore();
sut.init(aSessionsStore);
SessionsRepository sessionsRepository = new SessionsRepository(aSessionsStore);
sut.init(sessionsRepository);
ClientTopicCouple sub = clientSubOnTopic("AnySensor1", s);
Subscription sub = clientSubOnTopic("AnySensor1", s);
sut.add(sub);
assertThat(sut.recursiveMatch(asTopic(t), sut.root)).isNotEmpty();
@@ -193,9 +191,10 @@ public class CTrieSubscriptionDirectoryMatchingTest {
sut = new CTrieSubscriptionDirectory();
MemoryStorageService memStore = new MemoryStorageService(null, null);
ISessionsStore aSessionsStore = memStore.sessionsStore();
sut.init(aSessionsStore);
SessionsRepository sessionsRepository = new SessionsRepository(aSessionsStore);
sut.init(sessionsRepository);
ClientTopicCouple sub = clientSubOnTopic("AnySensor1", subscription);
Subscription sub = clientSubOnTopic("AnySensor1", subscription);
sut.add(sub);
assertThat(sut.recursiveMatch(asTopic(topic), sut.root)).isEmpty();
@@ -203,47 +202,50 @@ public class CTrieSubscriptionDirectoryMatchingTest {
@Test
public void testOverlappingSubscriptions() {
Subscription sub = new Subscription("Sensor1", asTopic("a/+"), MqttQoS.AT_MOST_ONCE);
ClientTopicCouple genericSub = sub.asClientTopicCouple();
this.subscriptionsStore.addNewSubscription(sub);
storageService.sessionsStore().createNewSession("Sensor1", false);
Subscription genericSub = new Subscription("Sensor1", asTopic("a/+"), MqttQoS.AT_MOST_ONCE);
this.subscriptionsStore.addNewSubscription(genericSub);
final ClientSession sensorSession = this.sessionsRepository.createNewSession("Sensor1", false);
sensorSession.subscribe(genericSub);
sut.add(genericSub);
Subscription sub2 = new Subscription("Sensor1", asTopic("a/b"), MqttQoS.AT_MOST_ONCE);
ClientTopicCouple specificSub = sub2.asClientTopicCouple();
this.subscriptionsStore.addNewSubscription(sub2);
Subscription specificSub = new Subscription("Sensor1", asTopic("a/b"), MqttQoS.AT_MOST_ONCE);
sensorSession.subscribe(specificSub);
this.subscriptionsStore.addNewSubscription(specificSub);
sut.add(specificSub);
//Exercise
final Set<Subscription> matchingForSpecific = sut.match(asTopic("a/b"));
// Verify
assertThat(sut.match(asTopic("a/b")).size()).isEqualTo(1);
assertThat(matchingForSpecific.size()).isEqualTo(1);
}
@Test
public void removeSubscription_withDifferentClients_subscribedSameTopic() {
ClientTopicCouple slashSub = clientSubOnTopic("Sensor1", "/topic");
Subscription slashSub = clientSubOnTopic("Sensor1", "/topic");
sut.add(slashSub);
ClientTopicCouple slashSub2 = clientSubOnTopic("Sensor2", "/topic");
Subscription slashSub2 = clientSubOnTopic("Sensor2", "/topic");
sut.add(slashSub2);
// Exercise
sut.removeSubscription(asTopic("/topic"), slashSub2.clientID);
sut.removeSubscription(asTopic("/topic"), slashSub2.clientId);
// Verify
ClientTopicCouple remainedSubscription = sut.recursiveMatch(asTopic("/topic"), sut.root).iterator().next();
assertThat(remainedSubscription.clientID).isEqualTo(slashSub.clientID);
assertEquals(slashSub.clientID, remainedSubscription.clientID);
Subscription remainedSubscription = sut.recursiveMatch(asTopic("/topic"), sut.root).iterator().next();
assertThat(remainedSubscription.clientId).isEqualTo(slashSub.clientId);
assertEquals(slashSub.clientId, remainedSubscription.clientId);
}
@Test
public void removeSubscription_sameClients_subscribedSameTopic() {
ClientTopicCouple slashSub = clientSubOnTopic("Sensor1", "/topic");
Subscription slashSub = clientSubOnTopic("Sensor1", "/topic");
sut.add(slashSub);
// Exercise
sut.removeSubscription(asTopic("/topic"), slashSub.clientID);
sut.removeSubscription(asTopic("/topic"), slashSub.clientId);
// Verify
final Set<ClientTopicCouple> matchingSubscriptions = sut.recursiveMatch(asTopic("/topic"), sut.root);
final Set<Subscription> matchingSubscriptions = sut.recursiveMatch(asTopic("/topic"), sut.root);
assertThat(matchingSubscriptions).isEmpty();
}
@@ -252,18 +254,18 @@ public class CTrieSubscriptionDirectoryMatchingTest {
*/
@Test
public void duplicatedSubscriptionsWithDifferentQos() {
ClientSession session2 = sessionsStore.createNewSession("client2", true);
ClientSession session2 = this.sessionsRepository.createNewSession("client2", true);
Subscription client2Sub = new Subscription("client2", asTopic("client/test/b"), MqttQoS.AT_MOST_ONCE);
session2.subscribe(client2Sub);
this.sut.add(client2Sub.asClientTopicCouple());
ClientSession session1 = sessionsStore.createNewSession("client1", true);
this.sut.add(client2Sub);
ClientSession session1 = this.sessionsRepository.createNewSession("client1", true);
Subscription client1SubQoS0 = new Subscription("client1", asTopic("client/test/b"), MqttQoS.AT_MOST_ONCE);
session1.subscribe(client1SubQoS0);
this.sut.add(client1SubQoS0.asClientTopicCouple());
this.sut.add(client1SubQoS0);
Subscription client1SubQoS2 = new Subscription("client1", asTopic("client/test/b"), MqttQoS.EXACTLY_ONCE);
session1.subscribe(client1SubQoS2);
this.sut.add(client1SubQoS2.asClientTopicCouple());
this.sut.add(client1SubQoS2);
// Verify
Set<Subscription> subscriptions = this.sut.match(asTopic("client/test/b"));
@@ -16,7 +16,7 @@
import io.moquette.persistence.MemoryStorageService;
import io.moquette.spi.ISessionsStore;
import io.moquette.spi.ISubscriptionsStore.ClientTopicCouple;
import io.moquette.spi.impl.SessionsRepository;
import org.junit.Before;
import org.junit.Test;
@@ -24,8 +24,9 @@ import java.util.Optional;
import java.util.Set;
import static io.moquette.spi.impl.subscriptions.Topic.asTopic;
import static org.junit.Assert.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class CTrieSubscriptionDirectoryTest {
@@ -36,7 +37,8 @@ public class CTrieSubscriptionDirectoryTest {
sut = new CTrieSubscriptionDirectory();
MemoryStorageService memStore = new MemoryStorageService(null, null);
ISessionsStore aSessionsStore = memStore.sessionsStore();
sut.init(aSessionsStore);
SessionsRepository sessionsRepository = new SessionsRepository(aSessionsStore);
sut.init(sessionsRepository);
}
@Test
@@ -73,7 +75,7 @@ public class CTrieSubscriptionDirectoryTest {
@Test
public void testLookup() {
final ClientTopicCouple existingSubscription = clientSubOnTopic("TempSensor1", "/temp");
final Subscription existingSubscription = clientSubOnTopic("TempSensor1", "/temp");
sut.add(existingSubscription);
//Exercise
@@ -85,17 +87,17 @@ public class CTrieSubscriptionDirectoryTest {
@Test
public void testAddNewSubscriptionOnExistingNode() {
final ClientTopicCouple existingSubscription = clientSubOnTopic("TempSensor1", "/temp");
final Subscription existingSubscription = clientSubOnTopic("TempSensor1", "/temp");
sut.add(existingSubscription);
//Exercise
final ClientTopicCouple newSubscription = clientSubOnTopic("TempSensor2", "/temp");
final Subscription newSubscription = clientSubOnTopic("TempSensor2", "/temp");
sut.add(newSubscription);
//Verify
final Optional<CNode> matchedNode = sut.lookup(asTopic("/temp"));
assertTrue("Node on path /temp must be present", matchedNode.isPresent());
final Set<ClientTopicCouple> subscriptions = matchedNode.get().subscriptions;
final Set<Subscription> subscriptions = matchedNode.get().subscriptions;
assertTrue(subscriptions.contains(newSubscription));
}
@@ -104,18 +106,18 @@ public class CTrieSubscriptionDirectoryTest {
sut.add(clientSubOnTopic("TempSensorRM", "/italy/roma/temp"));
sut.add(clientSubOnTopic("TempSensorFI", "/italy/firenze/temp"));
sut.add(clientSubOnTopic("HumSensorFI", "/italy/roma/humidity"));
final ClientTopicCouple happinessSensor = clientSubOnTopic("HappinessSensor", "/italy/happiness");
final Subscription happinessSensor = clientSubOnTopic("HappinessSensor", "/italy/happiness");
sut.add(happinessSensor);
//Verify
final Optional<CNode> matchedNode = sut.lookup(asTopic("/italy/happiness"));
assertTrue("Node on path /italy/happiness must be present", matchedNode.isPresent());
final Set<ClientTopicCouple> subscriptions = matchedNode.get().subscriptions;
final Set<Subscription> subscriptions = matchedNode.get().subscriptions;
assertTrue(subscriptions.contains(happinessSensor));
}
static ClientTopicCouple clientSubOnTopic(String clientID, String topicName) {
return new ClientTopicCouple(clientID, asTopic(topicName));
static Subscription clientSubOnTopic(String clientID, String topicName) {
return new Subscription(clientID, asTopic(topicName), null);
}
@Test
@@ -135,10 +137,10 @@ public class CTrieSubscriptionDirectoryTest {
sut.add(clientSubOnTopic("TempSensor1", "/temp"));
//Exercise
final Set<ClientTopicCouple> matchingSubs = sut.recursiveMatch(asTopic("/temp"), sut.root);
final Set<Subscription> matchingSubs = sut.recursiveMatch(asTopic("/temp"), sut.root);
//Verify
final ClientTopicCouple expectedMatchingsub = new ClientTopicCouple("TempSensor1", asTopic("/temp"));
final Subscription expectedMatchingsub = new Subscription("TempSensor1", asTopic("/temp"));
assertThat(matchingSubs).contains(expectedMatchingsub);
}
}
@@ -1,332 +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.persistence.MemoryStorageService;
import io.moquette.spi.ClientSession;
import io.moquette.spi.ISessionsStore;
import io.moquette.spi.ISubscriptionsStore;
import io.moquette.spi.ISubscriptionsStore.ClientTopicCouple;
import io.netty.handler.codec.mqtt.MqttQoS;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class SubscriptionsDirectoryTest {
private SubscriptionsDirectory store;
private ISessionsStore sessionsStore;
private ISubscriptionsStore subscriptionsStore;
public SubscriptionsDirectoryTest() {
}
@Before
public void setUp() throws IOException {
store = new SubscriptionsDirectory();
MemoryStorageService storageService = new MemoryStorageService(null, null);
this.sessionsStore = storageService.sessionsStore();
store.init(sessionsStore);
this.subscriptionsStore = this.sessionsStore.subscriptionStore();
storageService.sessionsStore().createNewSession("FAKE_CLI_ID_1", false);
storageService.sessionsStore().createNewSession("FAKE_CLI_ID_2", false);
}
@Test
public void testMatchSimple() {
Subscription slashSub = new Subscription("FAKE_CLI_ID_1", new Topic("/"), MqttQoS.AT_MOST_ONCE);
this.subscriptionsStore.addNewSubscription(slashSub);
store.add(slashSub.asClientTopicCouple());
assertThat(store.matches(new Topic("finance"))).isEmpty();
Subscription slashFinanceSub = new Subscription("FAKE_CLI_ID_1", new Topic("/finance"), MqttQoS.AT_MOST_ONCE);
this.subscriptionsStore.addNewSubscription(slashFinanceSub);
store.add(slashFinanceSub.asClientTopicCouple());
assertThat(store.matches(new Topic("finance"))).isEmpty();
assertThat(store.matches(new Topic("/finance"))).contains(slashFinanceSub);
assertThat(store.matches(new Topic("/"))).contains(slashSub);
}
@Test
public void testMatchSimpleMulti() {
Subscription anySub = new Subscription("FAKE_CLI_ID_1", new Topic("#"), MqttQoS.AT_MOST_ONCE);
this.subscriptionsStore.addNewSubscription(anySub);
store.add(anySub.asClientTopicCouple());
assertThat(store.matches(new Topic("finance"))).contains(anySub);
Subscription financeAnySub = new Subscription("FAKE_CLI_ID_2", new Topic("finance/#"), MqttQoS.AT_MOST_ONCE);
this.subscriptionsStore.addNewSubscription(financeAnySub);
store.add(financeAnySub.asClientTopicCouple());
assertThat(store.matches(new Topic("finance"))).containsExactlyInAnyOrder(financeAnySub, anySub);
}
@Test
public void testMatchingDeepMulti_one_layer() {
Subscription anySub = new Subscription("FAKE_CLI_ID_1", new Topic("#"), MqttQoS.AT_MOST_ONCE);
Subscription financeAnySub = new Subscription("FAKE_CLI_ID_2", new Topic("finance/#"), MqttQoS.AT_MOST_ONCE);
this.subscriptionsStore.addNewSubscription(anySub);
this.subscriptionsStore.addNewSubscription(financeAnySub);
store.add(anySub.asClientTopicCouple());
store.add(financeAnySub.asClientTopicCouple());
// Verify
assertThat(store.matches(new Topic("finance/stock"))).containsExactlyInAnyOrder(financeAnySub, anySub);
assertThat(store.matches(new Topic("finance/stock/ibm"))).containsExactlyInAnyOrder(financeAnySub, anySub);
}
@Test
public void testMatchingDeepMulti_two_layer() {
Subscription financeAnySub = new Subscription(
"FAKE_CLI_ID_1",
new Topic("finance/stock/#"),
MqttQoS.AT_MOST_ONCE);
this.subscriptionsStore.addNewSubscription(financeAnySub);
store.add(financeAnySub.asClientTopicCouple());
// Verify
assertThat(store.matches(new Topic("finance/stock/ibm"))).containsExactly(financeAnySub);
}
@Test
public void testMatchSimpleSingle() {
Subscription anySub = new Subscription("FAKE_CLI_ID_1", new Topic("+"), MqttQoS.AT_MOST_ONCE);
this.subscriptionsStore.addNewSubscription(anySub);
store.add(anySub.asClientTopicCouple());
assertThat(store.matches(new Topic("finance"))).containsExactly(anySub);
Subscription financeOne = new Subscription("FAKE_CLI_ID_1", new Topic("finance/+"), MqttQoS.AT_MOST_ONCE);
this.subscriptionsStore.addNewSubscription(financeOne);
store.add(financeOne.asClientTopicCouple());
assertThat(store.matches(new Topic("finance/stock"))).containsExactly(financeOne);
}
@Test
public void testMatchManySingle() {
Subscription manySub = new Subscription("FAKE_CLI_ID_1", new Topic("+/+"), MqttQoS.AT_MOST_ONCE);
this.subscriptionsStore.addNewSubscription(manySub);
store.add(manySub.asClientTopicCouple());
// verify
assertThat(store.matches(new Topic("/finance"))).contains(manySub);
}
@Test
public void testMatchSlashSingle() {
Subscription slashPlusSub = new Subscription("FAKE_CLI_ID_1", new Topic("/+"), MqttQoS.AT_MOST_ONCE);
this.subscriptionsStore.addNewSubscription(slashPlusSub);
store.add(slashPlusSub.asClientTopicCouple());
Subscription anySub = new Subscription("FAKE_CLI_ID_1", new Topic("+"), MqttQoS.AT_MOST_ONCE);
this.subscriptionsStore.addNewSubscription(anySub);
store.add(anySub.asClientTopicCouple());
// Verify
assertThat(store.matches(new Topic("/finance"))).containsOnly(slashPlusSub);
assertThat(store.matches(new Topic("/finance"))).doesNotContain(anySub);
}
@Test
public void testMatchManyDeepSingle() {
Subscription slashPlusSub = new Subscription(
"FAKE_CLI_ID_1",
new Topic("/finance/+/ibm"),
MqttQoS.AT_MOST_ONCE);
this.subscriptionsStore.addNewSubscription(slashPlusSub);
store.add(slashPlusSub.asClientTopicCouple());
Subscription slashPlusDeepSub = new Subscription(
"FAKE_CLI_ID_2",
new Topic("/+/stock/+"),
MqttQoS.AT_MOST_ONCE);
this.subscriptionsStore.addNewSubscription(slashPlusDeepSub);
store.add(slashPlusDeepSub.asClientTopicCouple());
// Verify
assertThat(store.matches(new Topic("/finance/stock/ibm")))
.containsExactlyInAnyOrder(slashPlusSub, slashPlusDeepSub);
}
@Test
public void testMatchSimpleMulti_allTheTree() {
Subscription sub = new Subscription("FAKE_CLI_ID_1", new Topic("#"), MqttQoS.AT_MOST_ONCE);
this.subscriptionsStore.addNewSubscription(sub);
store.add(sub.asClientTopicCouple());
assertThat(store.matches(new Topic("finance"))).isNotEmpty();
assertThat(store.matches(new Topic("finance/ibm"))).isNotEmpty();
}
@Test
public void testMatchSimpleMulti_zeroLevel() {
// check MULTI in case of zero level match
Subscription sub = new Subscription("FAKE_CLI_ID_1", new Topic("finance/#"), MqttQoS.AT_MOST_ONCE);
this.subscriptionsStore.addNewSubscription(sub);
store.add(sub.asClientTopicCouple());
assertThat(store.matches(new Topic("finance"))).isNotEmpty();
}
@Test
public void rogerLightTopicMatches() {
assertMatch("foo/bar", "foo/bar");
assertMatch("foo/bar", "foo/bar");
assertMatch("foo/+", "foo/bar");
assertMatch("foo/+/baz", "foo/bar/baz");
assertMatch("foo/+/#", "foo/bar/baz");
assertMatch("#", "foo/bar/baz");
assertNotMatch("foo/bar", "foo");
assertNotMatch("foo/+", "foo/bar/baz");
assertNotMatch("foo/+/baz", "foo/bar/bar");
assertNotMatch("foo/+/#", "fo2/bar/baz");
assertMatch("#", "/foo/bar");
assertMatch("/#", "/foo/bar");
assertNotMatch("/#", "foo/bar");
assertMatch("foo//bar", "foo//bar");
assertMatch("foo//+", "foo//bar");
assertMatch("foo/+/+/baz", "foo///baz");
assertMatch("foo/bar/+", "foo/bar/");
}
private void assertMatch(String s, String t) {
Topic subscription = new Topic(s);
Topic topic = new Topic(t);
store = new SubscriptionsDirectory();
MemoryStorageService memStore = new MemoryStorageService(null, null);
ISessionsStore aSessionsStore = memStore.sessionsStore();
aSessionsStore.createNewSession("FAKE_CLI_ID_1", false);
store.init(aSessionsStore);
Subscription sub = new Subscription("FAKE_CLI_ID_1", subscription, MqttQoS.AT_MOST_ONCE);
aSessionsStore.subscriptionStore().addNewSubscription(sub);
store.add(sub.asClientTopicCouple());
assertThat(store.matches(topic)).isNotEmpty();
}
private void assertNotMatch(String s, String t) {
Topic subscription = new Topic(s);
Topic topic = new Topic(t);
store = new SubscriptionsDirectory();
MemoryStorageService memStore = new MemoryStorageService(null, null);
store.init(memStore.sessionsStore());
Subscription sub = new Subscription("FAKE_CLI_ID_1", subscription, MqttQoS.AT_MOST_ONCE);
this.subscriptionsStore.addNewSubscription(sub);
store.add(sub.asClientTopicCouple());
assertThat(store.matches(topic)).isEmpty();
}
@Test
public void testOverlappingSubscriptions() {
Subscription genericSub = new Subscription("FAKE_CLI_ID_1", new Topic("a/+"), MqttQoS.EXACTLY_ONCE);
this.subscriptionsStore.addNewSubscription(genericSub);
store.add(genericSub.asClientTopicCouple());
Subscription specificSub = new Subscription("FAKE_CLI_ID_1", new Topic("a/b"), MqttQoS.AT_LEAST_ONCE);
this.subscriptionsStore.addNewSubscription(specificSub);
store.add(specificSub.asClientTopicCouple());
// Verify
assertThat(store.matches(new Topic("a/b")).size()).isEqualTo(1);
}
@Test
public void removeSubscription_withDifferentClients_subscribedSameTopic() {
ISubscriptionsDirectory aStore = new SubscriptionsDirectory();
MemoryStorageService memStore = new MemoryStorageService(null, null);
ISessionsStore sessionsStore = memStore.sessionsStore();
aStore.init(sessionsStore);
// subscribe a not active clientID1 to /topic
sessionsStore.createNewSession("FAKE_CLI_ID_1", true);
Subscription slashSub = new Subscription("FAKE_CLI_ID_1", new Topic("/topic"), MqttQoS.AT_MOST_ONCE);
sessionsStore.subscriptionStore().addNewSubscription(slashSub);
aStore.add(slashSub.asClientTopicCouple());
// subscribe an active clientID2 to /topic
Subscription slashSub2 = new Subscription("FAKE_CLI_ID_2", new Topic("/topic"), MqttQoS.AT_MOST_ONCE);
sessionsStore.subscriptionStore().addNewSubscription(slashSub2);
aStore.add(new ClientTopicCouple("FAKE_CLI_ID_2", new Topic("/topic")));
// Exercise
aStore.removeSubscription(new Topic("/topic"), slashSub2.getClientId());
// Verify
Subscription remainedSubscription = aStore.matches(new Topic("/topic")).get(0);
assertThat(remainedSubscription.getClientId()).isEqualTo(slashSub.getClientId());
}
/*
* Test for Issue #49
*/
@Test
public void duplicatedSubscriptionsWithDifferentQos() {
ClientSession session2 = sessionsStore.createNewSession("client2", true);
Subscription client2Sub = new Subscription("client2", new Topic("client/test/b"), MqttQoS.AT_MOST_ONCE);
session2.subscribe(client2Sub);
store.add(client2Sub.asClientTopicCouple());
ClientSession session1 = sessionsStore.createNewSession("client1", true);
Subscription client1SubQoS0 = new Subscription("client1", new Topic("client/test/b"), MqttQoS.AT_MOST_ONCE);
session1.subscribe(client1SubQoS0);
store.add(client1SubQoS0.asClientTopicCouple());
Subscription client1SubQoS2 = new Subscription("client1", new Topic("client/test/b"), MqttQoS.EXACTLY_ONCE);
session1.subscribe(client1SubQoS2);
store.add(client1SubQoS2.asClientTopicCouple());
System.out.println(store.dumpTree());
// Verify
List<Subscription> subscriptions = store.matches(new Topic("client/test/b"));
assertThat(subscriptions).contains(client1SubQoS2);
assertThat(subscriptions).contains(client2Sub);
Subscription client1Sub = subscriptions.get(subscriptions.indexOf(client1SubQoS0));
assertThat(client1SubQoS0.getRequestedQos()).isNotEqualTo(client1Sub.getRequestedQos());
// client1SubQoS2 should override client1SubQoS0
assertThat(client1Sub.getRequestedQos()).isEqualTo(client1SubQoS2.getRequestedQos());
}
@Test
public void testRecreatePath_emptyRoot() {
TreeNode oldRoot = new TreeNode();
final SubscriptionsDirectory.NodeCouple resp = store.recreatePath(new Topic("/finance"), oldRoot);
// Verify
assertThat(resp.root).isNotNull();
assertThat(resp.root.m_token).isNull();
assertThat(resp.root.m_children).hasSize(1);
assertThat(resp.root.m_children.get(0).m_children).contains(resp.createdNode);
}
@Test
public void testRecreatePath_1layer_tree() {
TreeNode oldRoot = new TreeNode();
final SubscriptionsDirectory.NodeCouple respFinance = store.recreatePath(new Topic("/finance"), oldRoot);
final SubscriptionsDirectory.NodeCouple respPlus = store.recreatePath(new Topic("/+"), respFinance.root);
// Verify
assertThat(respPlus.root).isNotNull();
assertThat(respPlus.root.m_token).isNull();
assertThat(respPlus.root.m_children.size()).isEqualTo(1);
assertThat(respPlus.root.m_children.get(0).m_children).contains(respPlus.createdNode);
assertThat(respPlus.root.m_children.get(0).m_children).contains(respFinance.createdNode);
}
}
@@ -1,76 +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.junit.Test;
import static org.junit.Assert.*;
public class TreeNodeTest {
@Test
public void testRemoveTreeNodesAfterEmptySubscriptions() {
TreeNode root = new TreeNode();
TreeNode subNode1 = new TreeNode();
subNode1.addSubscription(new ClientTopicCouple("Client1", new Topic("/topic1")));
root.addChild(subNode1);
TreeNode updatedRoot = root.removeClientSubscriptions("Client1");
assertTrue("Empty subscriptions node should be removed", updatedRoot.m_children.isEmpty());
}
@Test
public void testRemoveTreeNodesAfterEmptySubscriptions_multiLayer() {
TreeNode root = new TreeNode();
TreeNode intermediateEmptyNode = new TreeNode();
TreeNode subNode1 = new TreeNode();
subNode1.addSubscription(new ClientTopicCouple("Client1", new Topic("/sub/topic1")));
intermediateEmptyNode.addChild(subNode1);
root.addChild(intermediateEmptyNode);
TreeNode updatedRoot = root.removeClientSubscriptions("Client1");
assertTrue("Empty subscriptions node should be removed", updatedRoot.m_children.isEmpty());
}
@Test
public void testRemoveTreeNodesAfterEmptySubscriptions_multiLayer_doesntRemoveIntermediateNodes() {
TreeNode root = new TreeNode();
TreeNode intermediateEmptyNode = new TreeNode();
TreeNode subNode1 = new TreeNode();
subNode1.addSubscription(new ClientTopicCouple("Client1", new Topic("/sub/topic1")));
intermediateEmptyNode.addChild(subNode1);
TreeNode subNode2 = new TreeNode();
subNode2.addSubscription(new ClientTopicCouple("Client2", new Topic("/sub/topic2")));
intermediateEmptyNode.addChild(subNode2);
root.addChild(intermediateEmptyNode);
TreeNode updatedRoot = root.removeClientSubscriptions("Client1");
assertFalse("Remove a subscription shouldn't remove intermediate empty nodes",
updatedRoot.m_children.isEmpty());
}
}