Merged topic subscription search and publish into single MessagePublisher method

First step on implementing H2 storage

Minor, fixed not imported class

Cleaned MessageStore interface to avoid duplication of methods used in couple. Switched from guid + message to move messages

Separated ISubscriptionsStore from ISessionStore

Implemented H2 queue
This commit is contained in:
andsel
2017-06-02 10:13:34 +02:00
parent c18c915dd2
commit d9ff81131c
39 changed files with 1585 additions and 735 deletions
@@ -18,12 +18,15 @@ package io.moquette.persistence.mapdb;
import io.moquette.spi.IMatchingCondition;
import io.moquette.spi.IMessagesStore;
import io.moquette.spi.MessageGUID;
import io.moquette.spi.impl.subscriptions.Topic;
import org.mapdb.DB;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
/**
@@ -35,10 +38,7 @@ class MapDBMessagesStore implements IMessagesStore {
private DB m_db;
// maps clientID -> guid
private ConcurrentMap<Topic, MessageGUID> m_retainedStore;
// maps guid to message, it's message store
private ConcurrentMap<MessageGUID, IMessagesStore.StoredMessage> m_persistentMessageStore;
private ConcurrentMap<Topic, StoredMessage> m_retainedStore;
MapDBMessagesStore(DB db) {
m_db = db;
@@ -47,66 +47,39 @@ class MapDBMessagesStore implements IMessagesStore {
@Override
public void initStore() {
m_retainedStore = m_db.getHashMap("retained");
m_persistentMessageStore = m_db.getHashMap("persistedMessages");
LOG.info("Initialized store");
}
@Override
public void storeRetained(Topic topic, MessageGUID guid) {
LOG.debug("Storing retained messages. Topic={}, guid={}", topic, guid);
m_retainedStore.put(topic, guid);
}
@Override
public Collection<StoredMessage> searchMatching(IMatchingCondition condition) {
LOG.debug("Scanning retained messages...");
LOG.debug("Scanning retained messages");
List<StoredMessage> results = new ArrayList<>();
for (Map.Entry<Topic, MessageGUID> entry : m_retainedStore.entrySet()) {
final MessageGUID guid = entry.getValue();
StoredMessage storedMsg = m_persistentMessageStore.get(guid);
for (Map.Entry<Topic, StoredMessage> entry : m_retainedStore.entrySet()) {
StoredMessage storedMsg = entry.getValue();
if (condition.match(entry.getKey())) {
results.add(storedMsg);
}
}
if (LOG.isTraceEnabled()) {
LOG.trace("The retained messages have been scanned. MatchingMessages={}", results);
LOG.trace("Retained messages have been scanned matchingMessages={}", results);
}
return results;
}
@Override
public MessageGUID storePublishForFuture(StoredMessage storedMessage) {
assert storedMessage.getClientID() != null : "Message to be persisted must have a valid client ID";
MessageGUID guid = new MessageGUID(UUID.randomUUID().toString());
storedMessage.setGuid(guid);
LOG.debug("Storing publish event. CId={}, guid={}, topic={}", storedMessage.getClientID(), guid,
storedMessage.getTopic());
m_persistentMessageStore.put(guid, storedMessage);
return guid;
}
@Override
public void dropInFlightMessagesInSession(Collection<MessageGUID> pendingAckMessages) {
//remove all guids from retained
Collection<MessageGUID> messagesToRemove = new HashSet<>(pendingAckMessages);
messagesToRemove.removeAll(m_retainedStore.values());
for (MessageGUID guid : messagesToRemove) {
m_persistentMessageStore.remove(guid);
}
}
@Override
public StoredMessage getMessageByGuid(MessageGUID guid) {
LOG.debug("Retrieving stored message. Guid={}", guid);
return m_persistentMessageStore.get(guid);
}
@Override
public void cleanRetained(Topic topic) {
LOG.debug("Cleaning retained messages. Topic={}", topic);
m_retainedStore.remove(topic);
}
@Override
public void storeRetained(Topic topic, StoredMessage storedMessage) {
LOG.debug("Store retained message for topic={}, CId={}", topic, storedMessage.getClientID());
if (storedMessage.getClientID() == null) {
throw new IllegalArgumentException( "Message to be persisted must have a not null client ID");
}
m_retainedStore.put(topic, storedMessage);
}
}
@@ -52,11 +52,6 @@ public class MapDBPersistentStore implements IStore {
this.m_scheduler = scheduler;
}
/**
* Factory method to create message store backed by MapDB
*
* @return the message store instance.
*/
@Override
public IMessagesStore messagesStore() {
return m_messageStore;
@@ -67,36 +62,32 @@ public class MapDBPersistentStore implements IStore {
return m_sessionsStore;
}
@Override
public void initStore() {
LOG.info("Initializing MapDB store...");
LOG.info("Initializing MapDB store");
if (m_storePath == null || m_storePath.isEmpty()) {
LOG.warn("The MapDB store file path is empty. Using in-memory store.");
LOG.warn("MapDB store file path is empty, using in-memory store");
m_db = DBMaker.newMemoryDB().make();
} else {
File tmpFile;
try {
LOG.info("Using user-defined MapDB store file. Path = {}.", m_storePath);
LOG.info("Using user-defined MapDB store file. Path={}", m_storePath);
tmpFile = new File(m_storePath);
boolean fileNewlyCreated = tmpFile.createNewFile();
LOG.warn("Using {} MapDB store file. Path = {}.", fileNewlyCreated ? "fresh" : "existing", m_storePath);
LOG.warn("Using {} MapDB store file. Path={}", fileNewlyCreated ? "fresh" : "existing", m_storePath);
} catch (IOException ex) {
LOG.error(
"Unable to open MapDB store file. Path = {}, cause = {}, errorMessage = {}.",
m_storePath,
ex.getCause(),
ex.getMessage());
throw new RuntimeException(
"Can't create temp file for subscriptions storage [" + m_storePath + "]",
ex);
LOG.error("Unable to open MapDB store file. Path={}, cause={}, errorMessage={}", m_storePath,
ex.getCause(), ex.getMessage());
throw new RuntimeException("Can't create temp subscriptions file storage [" + m_storePath + "]", ex);
}
m_db = DBMaker.newFileDB(tmpFile).make();
}
LOG.info("Scheduling MapDB commit task...");
LOG.info("Scheduling MapDB commit task");
m_scheduler.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
LOG.debug("Committing to MapDB...");
LOG.debug("Committing to MapDB");
m_db.commit();
}
}, this.m_autosaveInterval, this.m_autosaveInterval, TimeUnit.SECONDS);
@@ -105,21 +96,21 @@ public class MapDBPersistentStore implements IStore {
m_messageStore = new MapDBMessagesStore(m_db);
m_messageStore.initStore();
m_sessionsStore = new MapDBSessionsStore(m_db, m_messageStore);
m_sessionsStore = new MapDBSessionsStore(m_db);
m_sessionsStore.initStore();
}
@Override
public void close() {
if (this.m_db.isClosed()) {
LOG.warn("The MapDB store is already closed. Nothing will be done.");
LOG.warn("MapDB store is already closed. Nothing will be done");
return;
}
LOG.info("Performing last commit to MapDB...");
LOG.info("Performing last commit to MapDB");
this.m_db.commit();
LOG.info("Closing MapDB store...");
LOG.info("Closing MapDB store");
this.m_db.close();
LOG.info("Stopping MapDB commit tasks...");
LOG.info("Stopping MapDB commit tasks");
//TODO th scheduler must be stopped by the owning (the instance of Server)
//invalidate the added task
@@ -129,9 +120,9 @@ public class MapDBPersistentStore implements IStore {
} catch (InterruptedException e) {
}
if (!m_scheduler.isTerminated()) {
LOG.warn("Forcing shutdown of MapDB commit tasks...");
LOG.warn("Forcing shutdown of MapDB commit tasks");
m_scheduler.shutdown();
}
LOG.info("The MapDB store has been closed successfully.");
LOG.info("MapDB store has been closed successfully");
}
}
@@ -18,11 +18,9 @@ package io.moquette.persistence.mapdb;
import io.moquette.persistence.PersistentSession;
import io.moquette.spi.ClientSession;
import io.moquette.spi.IMessagesStore;
import io.moquette.spi.IMessagesStore.StoredMessage;
import io.moquette.spi.ISessionsStore;
import io.moquette.spi.MessageGUID;
import io.moquette.spi.impl.Utils;
import io.moquette.spi.ISubscriptionsStore;
import io.moquette.spi.impl.subscriptions.Subscription;
import io.moquette.spi.impl.subscriptions.Topic;
import org.mapdb.DB;
@@ -37,34 +35,37 @@ import java.util.concurrent.ConcurrentMap;
/**
* ISessionsStore implementation backed by MapDB.
*/
class MapDBSessionsStore implements ISessionsStore {
class MapDBSessionsStore implements ISessionsStore, ISubscriptionsStore {
private static final Logger LOG = LoggerFactory.getLogger(MapDBSessionsStore.class);
// maps clientID->[MessageId -> guid]
private ConcurrentMap<String, ConcurrentMap<Integer, MessageGUID>> m_inflightStore;
// maps clientID->[MessageId -> msg]
private ConcurrentMap<String, ConcurrentMap<Integer, StoredMessage>> outboundFlightMessages;
// map clientID <-> set of currently in flight packet identifiers
private Map<String, Set<Integer>> m_inFlightIds;
private ConcurrentMap<String, PersistentSession> m_persistentSessions;
// maps clientID->[MessageId -> guid]
private ConcurrentMap<String, Map<Integer, MessageGUID>> m_secondPhaseStore;
private ConcurrentMap<String, ConcurrentMap<Integer, StoredMessage>> m_secondPhaseStore;
private final DB m_db;
private final IMessagesStore m_messagesStore;
MapDBSessionsStore(DB db, IMessagesStore messagesStore) {
MapDBSessionsStore(DB db) {
m_db = db;
m_messagesStore = messagesStore;
}
@Override
public void initStore() {
m_inflightStore = m_db.getHashMap("inflight");
outboundFlightMessages = m_db.getHashMap("outboundFlight");
m_inFlightIds = m_db.getHashMap("inflightPacketIDs");
m_persistentSessions = m_db.getHashMap("sessions");
m_secondPhaseStore = m_db.getHashMap("secondPhase");
}
@Override
public ISubscriptionsStore subscriptionStore() {
return this;
}
@Override
public void addNewSubscription(Subscription newSubscription) {
LOG.info("Adding new subscription. ClientId={}, topics={}", newSubscription.getClientId(),
@@ -94,7 +95,7 @@ class MapDBSessionsStore implements ISessionsStore {
@Override
public void wipeSubscriptions(String clientID) {
LOG.info("Wiping subscriptions. ClientId={}", clientID);
LOG.info("Wiping subscriptions. CId={}", clientID);
m_db.delete("subscriptions_" + clientID);
if (LOG.isDebugEnabled()) {
LOG.debug("Subscriptions have been removed. ClientId={}, clientSubscriptions={}", clientID,
@@ -104,7 +105,7 @@ class MapDBSessionsStore implements ISessionsStore {
@Override
public List<ClientTopicCouple> listAllSubscriptions() {
LOG.info("Retrieving existing subscriptions...");
LOG.debug("Retrieving existing subscriptions");
final List<ClientTopicCouple> allSubscriptions = new ArrayList<>();
for (String clientID : m_persistentSessions.keySet()) {
ConcurrentMap<Topic, Subscription> clientSubscriptions = m_db.getHashMap("subscriptions_" + clientID);
@@ -121,19 +122,19 @@ class MapDBSessionsStore implements ISessionsStore {
@Override
public Subscription getSubscription(ClientTopicCouple couple) {
ConcurrentMap<Topic, Subscription> clientSubscriptions = m_db.getHashMap("subscriptions_" + couple.clientID);
LOG.info("Retrieving subscriptions. ClientId={}, subscriptions={}", couple.clientID, clientSubscriptions);
LOG.debug("Retrieving subscriptions. CId={}, subscriptions={}", couple.clientID, clientSubscriptions);
return clientSubscriptions.get(couple.topicFilter);
}
@Override
public List<Subscription> getSubscriptions() {
LOG.info("Retrieving existing subscriptions...");
LOG.debug("Retrieving existing subscriptions...");
List<Subscription> subscriptions = new ArrayList<>();
for (String clientID : m_persistentSessions.keySet()) {
ConcurrentMap<Topic, Subscription> clientSubscriptions = m_db.getHashMap("subscriptions_" + clientID);
subscriptions.addAll(clientSubscriptions.values());
}
LOG.debug("The existing subscriptions have been retrieved. Result={}", subscriptions);
LOG.debug("Existing subscriptions has been retrieved Result={}", subscriptions);
return subscriptions;
}
@@ -149,35 +150,35 @@ class MapDBSessionsStore implements ISessionsStore {
clientID, cleanSession);
throw new IllegalArgumentException("Can't create a session with the ID of an already existing" + clientID);
}
LOG.info("Creating new session. ClientId={}, cleanSession={}", clientID, cleanSession);
LOG.debug("Creating new session. CId={}, cleanSession={}", clientID, cleanSession);
m_persistentSessions.putIfAbsent(clientID, new PersistentSession(cleanSession));
return new ClientSession(clientID, m_messagesStore, this, cleanSession);
return new ClientSession(clientID, this, this, cleanSession);
}
@Override
public ClientSession sessionForClient(String clientID) {
LOG.info("Retrieving session. ClientId={}", clientID);
LOG.debug("Retrieving session CId={}", clientID);
if (!m_persistentSessions.containsKey(clientID)) {
LOG.warn("The session does not exist. ClientId={}", clientID);
LOG.warn("Session does not exist CId={}", clientID);
return null;
}
PersistentSession storedSession = m_persistentSessions.get(clientID);
return new ClientSession(clientID, m_messagesStore, this, storedSession.cleanSession);
return new ClientSession(clientID, this, this, storedSession.cleanSession);
}
@Override
public Collection<ClientSession> getAllSessions() {
Collection<ClientSession> result = new ArrayList<>();
for (Map.Entry<String, PersistentSession> entry : m_persistentSessions.entrySet()) {
result.add(new ClientSession(entry.getKey(), m_messagesStore, this, entry.getValue().cleanSession));
result.add(new ClientSession(entry.getKey(), this, this, entry.getValue().cleanSession));
}
return result;
}
@Override
public void updateCleanStatus(String clientID, boolean cleanSession) {
LOG.info("Updating cleanSession flag. ClientId={}, cleanSession={}", clientID, cleanSession);
LOG.info("Updating cleanSession flag. CId={}, cleanSession={}", clientID, cleanSession);
m_persistentSessions.put(clientID, new PersistentSession(cleanSession));
}
@@ -186,9 +187,7 @@ class MapDBSessionsStore implements ISessionsStore {
*/
@Override
public int nextPacketID(String clientID) {
if (LOG.isDebugEnabled()) {
LOG.debug("Generating next packet ID. ClientId={}", clientID);
}
LOG.debug("Generating next packet ID CId={}", clientID);
Set<Integer> inFlightForClient = this.m_inFlightIds.get(clientID);
if (inFlightForClient == null) {
int nextPacketId = 1;
@@ -202,39 +201,37 @@ class MapDBSessionsStore implements ISessionsStore {
int maxId = inFlightForClient.isEmpty() ? 0 : Collections.max(inFlightForClient);
int nextPacketId = (maxId % 0xFFFF) + 1;
inFlightForClient.add(nextPacketId);
if (LOG.isDebugEnabled()) {
LOG.debug("The next packet ID has been generated. ClientId={}, result={}", clientID, nextPacketId);
}
LOG.debug("Next packet ID has been generated CId={}, result={}", clientID, nextPacketId);
return nextPacketId;
}
@Override
public void inFlightAck(String clientID, int messageID) {
LOG.debug("Acknowledging inflight message. ClientId={}, messageId={}", clientID, messageID);
Map<Integer, MessageGUID> m = this.m_inflightStore.get(clientID);
public StoredMessage inFlightAck(String clientID, int messageID) {
LOG.debug("Acknowledging inflight message CId={}, messageId={}", clientID, messageID);
ConcurrentMap<Integer, StoredMessage> m = this.outboundFlightMessages.get(clientID);
if (m == null) {
LOG.warn("Unable to retrieve inflight message record. ClientId={}, messageId={}", clientID, messageID);
return;
LOG.error("Can't find the inFlight record for client <{}>", clientID);
throw new RuntimeException("Can't find the inFlight record for client <" + clientID + ">");
}
m.remove(messageID);
StoredMessage msg = m.remove(messageID);
this.outboundFlightMessages.put(clientID, m);
// remove from the ids store
Set<Integer> inFlightForClient = this.m_inFlightIds.get(clientID);
if (inFlightForClient != null) {
inFlightForClient.remove(messageID);
}
return msg;
}
@Override
public void inFlight(String clientID, int messageID, MessageGUID guid) {
LOG.debug("Storing inflight message. ClientId={}, messageId={}, guid={}", clientID, messageID, guid);
ConcurrentMap<Integer, MessageGUID> m = this.m_inflightStore.get(clientID);
if (m == null) {
m = new ConcurrentHashMap<>();
public void inFlight(String clientID, int messageID, StoredMessage msg) {
ConcurrentMap<Integer, StoredMessage> messages = outboundFlightMessages.get(clientID);
if (messages == null) {
messages = new ConcurrentHashMap<>();
}
m.put(messageID, guid);
LOG.info("storing inflight clientID <{}> messageID {} guid <{}>", clientID, messageID, guid);
this.m_inflightStore.put(clientID, m);
messages.put(messageID, msg);
outboundFlightMessages.put(clientID, messages);
}
@Override
@@ -250,78 +247,67 @@ class MapDBSessionsStore implements ISessionsStore {
}
@Override
public void moveInFlightToSecondPhaseAckWaiting(String clientID, int messageID) {
if (LOG.isDebugEnabled()) {
LOG.debug("Moving inflight message to 2nd phase ack state. ClientId={}, messageID={}", clientID, messageID);
}
Map<Integer, MessageGUID> m = this.m_inflightStore.get(clientID);
public void moveInFlightToSecondPhaseAckWaiting(String clientID, int messageID, StoredMessage msg) {
LOG.debug("Moving inflight message to 2nd phase ack state. ClientId={}, messageID={}", clientID, messageID);
ConcurrentMap<Integer, StoredMessage> m = this.m_secondPhaseStore.get(clientID);
if (m == null) {
LOG.warn("Unable to retrieve inflight message record. ClientId={}, messageId={}", clientID, messageID);
return;
String error = String.format("Can't find the inFlight record for client <%s> during the second phase of " +
"QoS2 pub", clientID);
LOG.error(error);
throw new RuntimeException(error);
}
MessageGUID guid = m.remove(messageID);
// remove from the ids store
Set<Integer> inFlightForClient = this.m_inFlightIds.get(clientID);
if (inFlightForClient != null) {
inFlightForClient.remove(messageID);
}
final HashMap<Integer, MessageGUID> emptyGuids = new HashMap<>();
Map<Integer, MessageGUID> messageIDs = Utils.defaultGet(m_secondPhaseStore, clientID, emptyGuids);
messageIDs.put(messageID, guid);
m_secondPhaseStore.put(clientID, messageIDs);
m.put(messageID, msg);
this.outboundFlightMessages.put(clientID, m);
}
@Override
public MessageGUID secondPhaseAcknowledged(String clientID, int messageID) {
if (LOG.isDebugEnabled()) {
LOG.debug("Processing second phase ACK CId={}, messageId={}", clientID, messageID);
public StoredMessage secondPhaseAcknowledged(String clientID, int messageID) {
LOG.debug("Processing second phase ACK CId={}, messageId={}", clientID, messageID);
final ConcurrentMap<Integer, StoredMessage> m = this.m_secondPhaseStore.get(clientID);
if (m == null) {
String error = String.format("Can't find the inFlight record for client <%s> during the second phase " +
"acking of QoS2 pub", clientID);
LOG.error(error);
throw new RuntimeException(error);
}
final HashMap<Integer, MessageGUID> emptyGuids = new HashMap<>();
Map<Integer, MessageGUID> messageIDs = Utils.defaultGet(m_secondPhaseStore, clientID, emptyGuids);
MessageGUID guid = messageIDs.remove(messageID);
m_secondPhaseStore.put(clientID, messageIDs);
return guid;
}
@Override
public StoredMessage getInflightMessage(String clientID, int messageID) {
LOG.info("Retrieving inflight message CId={}, messageId={}", clientID, messageID);
Map<Integer, MessageGUID> clientEntries = m_inflightStore.get(clientID);
if (clientEntries == null) {
LOG.warn("The client has no inflight messages CId={}, messageId={}", clientID, messageID);
return null;
}
MessageGUID guid = clientEntries.get(messageID);
if (guid == null) {
LOG.warn("The message ID does not have an associated GUID. ClientId={}, messageId={}", clientID, messageID);
return null;
}
return m_messagesStore.getMessageByGuid(guid);
StoredMessage msg = m.remove(messageID);
m_secondPhaseStore.put(clientID, m);
return msg;
}
@Override
public int getInflightMessagesNo(String clientID) {
if (!m_inflightStore.containsKey(clientID))
return 0;
else
return m_inflightStore.get(clientID).size();
int totalInflight = 0;
ConcurrentMap<Integer, StoredMessage> inflightPerClient = m_db.getHashMap(inboundMessageId2MessagesMapName(clientID));
if (inflightPerClient != null) {
totalInflight += inflightPerClient.size();
}
Map<Integer, StoredMessage> secondPhaseInFlight = this.m_secondPhaseStore.get(clientID);
if (secondPhaseInFlight != null) {
totalInflight += secondPhaseInFlight.size();
}
Map<Integer, StoredMessage> outboundPerClient = outboundFlightMessages.get(clientID);
if (outboundPerClient != null) {
totalInflight += outboundPerClient.size();
}
return totalInflight;
}
@Override
public StoredMessage inboundInflight(String clientID, int messageID) {
LOG.debug("Mapping inbound message ID to GUID CId={}, messageId={}", clientID, messageID);
ConcurrentMap<Integer, MessageGUID> messageIdToGuid = m_db.getHashMap(inboundMessageId2GuidsMapName(clientID));
final MessageGUID guid = messageIdToGuid.get(messageID);
LOG.debug("Inbound message ID has been mapped CId={}, messageId={}, guid={}", clientID, messageID, guid);
return m_messagesStore.getMessageByGuid(guid);
ConcurrentMap<Integer, StoredMessage> messageIdToGuid = m_db.getHashMap(inboundMessageId2MessagesMapName(clientID));
return messageIdToGuid.get(messageID);
}
@Override
public void markAsInboundInflight(String clientID, int messageID, MessageGUID guid) {
ConcurrentMap<Integer, MessageGUID> messageIdToGuid = m_db.getHashMap(inboundMessageId2GuidsMapName(clientID));
messageIdToGuid.put(messageID, guid);
public void markAsInboundInflight(String clientID, int messageID, StoredMessage msg) {
ConcurrentMap<Integer, StoredMessage> messageIdToGuid = m_db.getHashMap(inboundMessageId2MessagesMapName(clientID));
messageIdToGuid.put(messageID, msg);
}
@Override
@@ -333,25 +319,16 @@ class MapDBSessionsStore implements ISessionsStore {
public int getSecondPhaseAckPendingMessages(String clientID) {
if (!m_secondPhaseStore.containsKey(clientID))
return 0;
else
return m_secondPhaseStore.get(clientID).size();
return m_secondPhaseStore.get(clientID).size();
}
@Override
public Collection<MessageGUID> pendingAck(String clientID) {
ConcurrentMap<Integer, MessageGUID> messageGUIDMap = m_db.getHashMap(messageId2GuidsMapName(clientID));
if (messageGUIDMap == null || messageGUIDMap.isEmpty()) {
return Collections.emptyList();
}
return new ArrayList<>(messageGUIDMap.values());
public void dropInFlightMessagesInSession(String clientID) {
m_secondPhaseStore.remove(clientID);
outboundFlightMessages.remove(clientID);
}
static String messageId2GuidsMapName(String clientID) {
return "guidsMapping_" + clientID;
}
static String inboundMessageId2GuidsMapName(String clientID) {
return "inboundInflightGuidsMapping_" + clientID;
static String inboundMessageId2MessagesMapName(String clientID) {
return "inboundInflight_" + clientID;
}
}
@@ -17,40 +17,30 @@
package io.moquette.persistence.mapdb;
import io.moquette.BrokerConstants;
import io.moquette.persistence.MessageStoreTCK;
import io.moquette.server.config.IConfig;
import io.moquette.server.config.MemoryConfig;
import io.moquette.spi.ClientSession;
import io.moquette.spi.IMessagesStore;
import io.moquette.spi.ISessionsStore;
import io.moquette.spi.ISessionsStore.ClientTopicCouple;
import io.moquette.spi.MessageGUID;
import io.moquette.spi.impl.subscriptions.Subscription;
import io.moquette.spi.impl.subscriptions.Topic;
import static io.netty.handler.codec.mqtt.MqttQoS.*;
import io.moquette.spi.IMessagesStore.StoredMessage;
import io.netty.handler.codec.mqtt.MqttQoS;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static java.util.Collections.singletonList;
import static org.junit.Assert.*;
/**
*
* @author andrea
*/
public class MapDBPersistentStoreTest {
public class MapDBPersistentStoreTest extends MessageStoreTCK {
public static final String TEST_CLIENT = "TestClient";
MapDBPersistentStore m_storageService;
ISessionsStore m_sessionsStore;
IMessagesStore m_messagesStore;
private ScheduledExecutorService scheduler;
@@ -64,8 +54,8 @@ public class MapDBPersistentStoreTest {
IConfig conf = new MemoryConfig(props);
m_storageService = new MapDBPersistentStore(conf, scheduler);
m_storageService.initStore();
m_messagesStore = m_storageService.messagesStore();
m_sessionsStore = m_storageService.sessionsStore();
messagesStore = m_storageService.messagesStore();
sessionsStore = m_storageService.sessionsStore();
}
@After
@@ -88,56 +78,38 @@ public class MapDBPersistentStoreTest {
assertFalse(dbFile.exists());
}
@Test
public void overridingSubscriptions() {
ClientSession session1 = m_sessionsStore.createNewSession("SESSION_ID_1", true);
// Subscribe on /topic with QOSType.MOST_ONE
Subscription oldSubscription = new Subscription(session1.clientID, new Topic("/topic"), AT_MOST_ONCE);
session1.subscribe(oldSubscription);
// Subscribe on /topic again that overrides the previous subscription.
Subscription overridingSubscription = new Subscription(session1.clientID, new Topic("/topic"), EXACTLY_ONCE);
session1.subscribe(overridingSubscription);
// Verify
List<ClientTopicCouple> subscriptions = m_sessionsStore.listAllSubscriptions();
assertEquals(1, subscriptions.size());
Subscription sub = m_sessionsStore.getSubscription(subscriptions.get(0));
assertEquals(overridingSubscription.getRequestedQos(), sub.getRequestedQos());
}
@Test
public void testNextPacketID_notExistingClientSession() {
int packetId = m_sessionsStore.nextPacketID("NOT_EXISTING_CLI");
int packetId = sessionsStore.nextPacketID("NOT_EXISTING_CLI");
assertEquals(1, packetId);
}
@Test
public void testNextPacketID_existingClientSession() {
// Force creation of inflight map for the CLIENT session
int packetId = m_sessionsStore.nextPacketID("CLIENT");
int packetId = sessionsStore.nextPacketID("CLIENT");
assertEquals(1, packetId);
// request a second packetID
packetId = m_sessionsStore.nextPacketID("CLIENT");
packetId = sessionsStore.nextPacketID("CLIENT");
assertEquals(2, packetId);
}
@Test
public void testNextPacketID() {
// request a first ID
StoredMessage msgStored = new StoredMessage("Hello".getBytes(), MqttQoS.AT_LEAST_ONCE, "/topic");
msgStored.setClientID(TEST_CLIENT);
int packetId = m_sessionsStore.nextPacketID("CLIENT");
m_sessionsStore.inFlight("CLIENT", packetId, new MessageGUID("ABCDE")); // simulate an
// inflight
// request a first ID
int packetId = sessionsStore.nextPacketID("CLIENT");
sessionsStore.inFlight("CLIENT", packetId, msgStored); // simulate an inflight
assertEquals(1, packetId);
// release the ID
m_sessionsStore.inFlightAck("CLIENT", packetId);
sessionsStore.inFlightAck("CLIENT", packetId);
// request a second packetID, counter restarts from 0
packetId = m_sessionsStore.nextPacketID("CLIENT");
packetId = sessionsStore.nextPacketID("CLIENT");
assertEquals(1, packetId);
}
@@ -146,44 +118,8 @@ public class MapDBPersistentStoreTest {
m_storageService.close();
// verify the executor is shutdown
assertTrue(
"Storage service scheduler can't be stopped in 3 seconds",
assertTrue("Storage service scheduler can't be stopped in 3 seconds",
m_storageService.m_scheduler.awaitTermination(3, TimeUnit.SECONDS));
assertTrue(m_storageService.m_scheduler.isTerminated());
}
@Test
public void testDropMessagesInSessionCleanAllNotRetainedStoredMessages() {
m_sessionsStore.createNewSession(TEST_CLIENT, true);
IMessagesStore.StoredMessage publishToStore = new IMessagesStore.StoredMessage("Hello".getBytes(), EXACTLY_ONCE,
"/topic");
publishToStore.setClientID(TEST_CLIENT);
publishToStore.setRetained(false);
MessageGUID guid = m_messagesStore.storePublishForFuture(publishToStore);
// Exercise
m_messagesStore.dropInFlightMessagesInSession(singletonList(guid));
// Verify the message store for session is empty.
IMessagesStore.StoredMessage storedPublish = m_messagesStore.getMessageByGuid(guid);
assertNull("The stored message must'n be present anymore", storedPublish);
}
@Test
public void testDropMessagesInSessionDoesntCleanAnyRetainedStoredMessages() {
m_sessionsStore.createNewSession(TEST_CLIENT, true);
IMessagesStore.StoredMessage publishToStore = new IMessagesStore.StoredMessage("Hello".getBytes(), EXACTLY_ONCE,
"/topic");
publishToStore.setClientID(TEST_CLIENT);
publishToStore.setRetained(true);
MessageGUID guid = m_messagesStore.storePublishForFuture(publishToStore);
m_messagesStore.storeRetained(new Topic("/topic"), guid);
// Exercise
m_messagesStore.dropInFlightMessagesInSession(singletonList(guid));
// Verify the message store for session is empty.
IMessagesStore.StoredMessage storedPublish = m_messagesStore.getMessageByGuid(guid);
assertNotNull("The stored retained message must be present after client's session drop", storedPublish);
}
}