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:
@@ -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
|
||||
|
||||
+2
-2
@@ -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);
|
||||
|
||||
+53
-51
@@ -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"));
|
||||
|
||||
+15
-13
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
-332
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user