First refactoring using the concept of ClientSession also for publish
This commit is contained in:
@@ -17,13 +17,7 @@ package org.eclipse.moquette.server.netty;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
@@ -36,30 +30,26 @@ import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
import io.netty.handler.timeout.IdleStateHandler;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.security.*;
|
||||
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.List;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
import io.netty.util.concurrent.Future;
|
||||
import org.eclipse.moquette.commons.Constants;
|
||||
import org.eclipse.moquette.server.config.IConfig;
|
||||
import org.eclipse.moquette.spi.IMessaging;
|
||||
import org.eclipse.moquette.parser.netty.MQTTDecoder;
|
||||
import org.eclipse.moquette.parser.netty.MQTTEncoder;
|
||||
import org.eclipse.moquette.server.ServerAcceptor;
|
||||
import org.eclipse.moquette.server.config.IConfig;
|
||||
import org.eclipse.moquette.server.netty.metrics.*;
|
||||
import org.eclipse.moquette.spi.impl.ProtocolProcessor;
|
||||
import org.eclipse.moquette.spi.impl.SimpleMessaging;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.security.*;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author andrea
|
||||
|
||||
@@ -79,7 +79,7 @@ public class NettyMQTTHandler extends ChannelInboundHandlerAdapter {
|
||||
m_processor.processPubRel(new NettyChannel(ctx), (PubRelMessage) msg);
|
||||
break;
|
||||
case DISCONNECT:
|
||||
m_processor.processDisconnect(new NettyChannel(ctx), (DisconnectMessage) msg);
|
||||
m_processor.processDisconnect(new NettyChannel(ctx));
|
||||
break;
|
||||
case PUBACK:
|
||||
m_processor.processPubAck(new NettyChannel(ctx), (PubAckMessage) msg);
|
||||
|
||||
@@ -22,11 +22,25 @@ import org.eclipse.moquette.spi.impl.subscriptions.SubscriptionsStore;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Model a Session like describe on page 25 of MQTT 3.1.1 specification:
|
||||
* The Session state in the Server consists of:
|
||||
* <ul>
|
||||
* <li>The existence of a Session, even if the rest of the Session state is empty.</li>
|
||||
* <li>The Client’s subscriptions.</li>
|
||||
* <li>QoS 1 and QoS 2 messages which have been sent to the Client, but have not been
|
||||
* completely acknowledged.</li>
|
||||
* <li>QoS 1 and QoS 2 messages pending transmission to the Client.</li>
|
||||
* <li>QoS 2 messages which have been received from the Client, but have not been
|
||||
* completely acknowledged.</li>
|
||||
* <li>Optionally, QoS 0 messages pending transmission to the Client.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class ClientSession {
|
||||
@@ -60,21 +74,21 @@ public class ClientSession {
|
||||
* @return the list of messages to be delivered for client related to the session.
|
||||
* */
|
||||
public List<PublishEvent> storedMessages() {
|
||||
return messagesStore.listMessagesInSession(clientID);
|
||||
//read all messages from enqueued store
|
||||
Collection<String> guids = this.m_sessionsStore.enqueued(clientID);
|
||||
return messagesStore.listMessagesInSession(guids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a message previously stored for delivery.
|
||||
* */
|
||||
public void removeDelivered(int messageID) {
|
||||
messagesStore.removeMessage(clientID, messageID);
|
||||
* Remove the messages stored in a cleanSession false.
|
||||
*/
|
||||
public void removeEnqueued(String guid) {
|
||||
this.m_sessionsStore.removeEnqueued(this.clientID, guid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ClientSession{" +
|
||||
"clientID='" + clientID + '\'' +
|
||||
'}';
|
||||
return "ClientSession{clientID='" + clientID + '\'' +"}";
|
||||
}
|
||||
|
||||
public boolean subscribe(String topicFilter, Subscription newSubscription) {
|
||||
@@ -133,4 +147,28 @@ public class ClientSession {
|
||||
public boolean isActive() {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
public int nextPacketId() {
|
||||
return this.messagesStore.nextPacketID(this.clientID);
|
||||
}
|
||||
|
||||
public void inFlightAcknowledged(int messageID) {
|
||||
m_sessionsStore.inFlightAck(this.clientID, messageID);
|
||||
}
|
||||
|
||||
public void inFlightAckWaiting(String guid, int messageID) {
|
||||
m_sessionsStore.inFlight(this.clientID, messageID, guid);
|
||||
}
|
||||
|
||||
public void secondPhaseAcknowledged(int messageID) {
|
||||
m_sessionsStore.secondPhaseAcknowledged(clientID, messageID);
|
||||
}
|
||||
|
||||
public void secondPhaseAckWaiting(int messageID) {
|
||||
m_sessionsStore.secondPhaseAckWaiting(clientID, messageID);
|
||||
}
|
||||
|
||||
public void enqueueToDeliver(String guid) {
|
||||
this.m_sessionsStore.bindToDeliver(guid, this.clientID);
|
||||
}
|
||||
}
|
||||
@@ -68,22 +68,26 @@ public interface IMessagesStore {
|
||||
*/
|
||||
Collection<StoredMessage> searchMatching(IMatchingCondition condition);
|
||||
|
||||
void storePublishForFuture(PublishEvent evt);
|
||||
/**
|
||||
* Persist the message.
|
||||
* @return the unique id in the storage (guid).
|
||||
* */
|
||||
String storePublishForFuture(PublishEvent evt);
|
||||
|
||||
/**
|
||||
* Return the list of persisted publishes for the given clientID.
|
||||
* For QoS1 and QoS2 with clean session flag, this method return the list of
|
||||
* missed publish events while the client was disconnected.
|
||||
*/
|
||||
List<PublishEvent> listMessagesInSession(String clientID);
|
||||
List<PublishEvent> listMessagesInSession(Collection<String> guids);
|
||||
|
||||
void removeMessage(String clientID, Integer packetID);
|
||||
// void removeMessage(String clientID, Integer packetID);
|
||||
|
||||
void dropMessagesInSession(String clientID);
|
||||
|
||||
void cleanTemporaryPublish(String clientID, int packetID);
|
||||
// void cleanTemporaryPublish(String clientID, int packetID);
|
||||
|
||||
void storeTemporaryPublish(PublishEvent evt, String clientID, int packetID);
|
||||
// void storeTemporaryPublish(PublishEvent evt, String clientID, int packetID);
|
||||
|
||||
/**
|
||||
* Return the next valid packetIdentifer for the given client session.
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.eclipse.moquette.spi;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -64,4 +65,30 @@ public interface ISessionsStore {
|
||||
void activate(String clientID);
|
||||
|
||||
void deactivate(String clientID);
|
||||
|
||||
void inFlightAck(String clientID, int messageID);
|
||||
|
||||
/**
|
||||
* Save the binding messageID, clientID <-> guid
|
||||
* */
|
||||
void inFlight(String clientID, int messageID, String guid);
|
||||
|
||||
/**
|
||||
* Store the guid to be later published.
|
||||
* */
|
||||
void bindToDeliver(String guid, String clientID);
|
||||
|
||||
/**
|
||||
* List the guids for retained messages for the session
|
||||
* */
|
||||
Collection<String> enqueued(String clientID);
|
||||
|
||||
/**
|
||||
* Remove form the queue of stored messages for session.
|
||||
* */
|
||||
void removeEnqueued(String clientID, String guid);
|
||||
|
||||
void secondPhaseAcknowledged(String clientID, int messageID);
|
||||
|
||||
void secondPhaseAckWaiting(String clientID, int messageID);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ import org.eclipse.moquette.proto.messages.AbstractMessage;
|
||||
import org.eclipse.moquette.proto.messages.AbstractMessage.QOSType;
|
||||
import org.eclipse.moquette.proto.messages.ConnAckMessage;
|
||||
import org.eclipse.moquette.proto.messages.ConnectMessage;
|
||||
import org.eclipse.moquette.proto.messages.DisconnectMessage;
|
||||
import org.eclipse.moquette.proto.messages.PubAckMessage;
|
||||
import org.eclipse.moquette.proto.messages.PubCompMessage;
|
||||
import org.eclipse.moquette.proto.messages.PubRecMessage;
|
||||
@@ -231,6 +230,7 @@ public class ProtocolProcessor {
|
||||
//force the republish of stored QoS1 and QoS2
|
||||
republishStoredInSession(clientSession);
|
||||
}
|
||||
LOG.info("CONNECT processed");
|
||||
}
|
||||
|
||||
private void failedCredentials(ServerChannel session) {
|
||||
@@ -253,9 +253,10 @@ public class ProtocolProcessor {
|
||||
|
||||
LOG.info("republishing stored messages to client <{}>", clientSession.clientID);
|
||||
for (PublishEvent pubEvt : publishedEvents) {
|
||||
sendPublish(pubEvt.getClientID(), pubEvt.getTopic(), pubEvt.getQos(),
|
||||
pubEvt.getMessage(), false, pubEvt.getMessageID());
|
||||
clientSession.removeDelivered(pubEvt.getMessageID());
|
||||
//TODO put in flight zone
|
||||
directSend(pubEvt.getClientID(), pubEvt.getTopic(), pubEvt.getQos(),
|
||||
pubEvt.getMessage(), false, pubEvt.getMessageID());
|
||||
clientSession.removeEnqueued(pubEvt.getGuid());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,7 +264,8 @@ public class ProtocolProcessor {
|
||||
String clientID = (String) session.getAttribute(NettyChannel.ATTR_KEY_CLIENTID);
|
||||
int messageID = msg.getMessageID();
|
||||
//Remove the message from message store
|
||||
m_messagesStore.removeMessage(clientID, messageID);
|
||||
ClientSession targetSession = m_sessionsStore.sessionForClient(clientID);
|
||||
targetSession.inFlightAcknowledged(messageID);
|
||||
}
|
||||
|
||||
public void processPublish(ServerChannel session, PublishMessage msg) {
|
||||
@@ -292,10 +294,11 @@ public class ProtocolProcessor {
|
||||
if (qos == AbstractMessage.QOSType.MOST_ONE) { //QoS0
|
||||
forward2Subscribers(publishEvt);
|
||||
} else if (qos == AbstractMessage.QOSType.LEAST_ONE) { //QoS1
|
||||
//TODO implement inFlight!!
|
||||
m_messagesStore.storeTemporaryPublish(publishEvt, clientID, messageID);
|
||||
//TODO implement inFlight!!boolean retain = pubEvt.isRetain();
|
||||
// final Integer messageID = pubEvt.getMessageID();
|
||||
// m_messagesStore.storeTemporaryPublish(publishEvt, clientID, messageID);
|
||||
forward2Subscribers(publishEvt);
|
||||
m_messagesStore.cleanTemporaryPublish(clientID, messageID);
|
||||
// m_messagesStore.cleanTemporaryPublish(clientID, messageID);
|
||||
//NB the PUB_ACK could be sent also after the addInFlight
|
||||
sendPubAck(new PubAckEvent(messageID, clientID));
|
||||
LOG.debug("replying with PubAck to MSG ID {}", messageID);
|
||||
@@ -338,17 +341,24 @@ public class ProtocolProcessor {
|
||||
/**
|
||||
* Flood the subscribers with the message to notify. MessageID is optional and should only used for QoS 1 and 2
|
||||
* */
|
||||
//TODO rename to route2Subscribers
|
||||
void forward2Subscribers(PublishEvent pubEvt) {
|
||||
final String topic = pubEvt.getTopic();
|
||||
final AbstractMessage.QOSType publishingQos = pubEvt.getQos();
|
||||
final ByteBuffer origMessage = pubEvt.getMessage();
|
||||
boolean retain = pubEvt.isRetain();
|
||||
final Integer messageID = pubEvt.getMessageID();
|
||||
// boolean retain = pubEvt.isRetain();
|
||||
// final Integer messageID = pubEvt.getMessageID();
|
||||
LOG.debug("forward2Subscribers republishing to existing subscribers that matches the topic {}", topic);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("content <{}>", DebugUtils.payload2Str(origMessage));
|
||||
LOG.debug("subscription tree {}", subscriptions.dumpTree());
|
||||
}
|
||||
//if QoS 1 or 2 store the message
|
||||
String guid = null;
|
||||
if (publishingQos == QOSType.EXACTLY_ONCE || publishingQos == QOSType.LEAST_ONE) {
|
||||
guid = m_messagesStore.storePublishForFuture(pubEvt);
|
||||
}
|
||||
|
||||
for (final Subscription sub : subscriptions.matches(topic)) {
|
||||
AbstractMessage.QOSType qos = publishingQos;
|
||||
if (qos.byteValue() > sub.getRequestedQos().byteValue()) {
|
||||
@@ -361,33 +371,34 @@ public class ProtocolProcessor {
|
||||
ByteBuffer message = origMessage.duplicate();
|
||||
if (qos == AbstractMessage.QOSType.MOST_ONE && targetSession.isActive()) {
|
||||
//QoS 0
|
||||
sendPublish(sub.getClientId(), topic, qos, message, false, null);
|
||||
directSend(sub.getClientId(), topic, qos, message, false, null);
|
||||
} else {
|
||||
//QoS 1 or 2
|
||||
//if the target subscription is not clean session and is not connected => store it
|
||||
if (!sub.isCleanSession() && !targetSession.isActive()) {
|
||||
//clone the event with matching clientID
|
||||
PublishEvent newPublishEvt = new PublishEvent(topic, qos, message, retain, sub.getClientId(), messageID != null ? messageID : 0);
|
||||
m_messagesStore.storePublishForFuture(newPublishEvt);
|
||||
//store the message in targetSession queue to deliver
|
||||
targetSession.enqueueToDeliver(guid);
|
||||
} else {
|
||||
//TODO also QoS 1 has to be stored in Flight Zone
|
||||
//if QoS 2 then store it in temp memory
|
||||
if (qos == AbstractMessage.QOSType.EXACTLY_ONCE) {
|
||||
PublishEvent newPublishEvt = new PublishEvent(topic, qos, message, retain, sub.getClientId(), messageID != null ? messageID : 0);
|
||||
m_messagesStore.storeTemporaryPublish(newPublishEvt, sub.getClientId(), messageID);
|
||||
}
|
||||
// if (qos == AbstractMessage.QOSType.EXACTLY_ONCE) {
|
||||
// PublishEvent newPublishEvt = new PublishEvent(topic, qos, message, retain, sub.getClientId(), messageID != null ? messageID : 0);
|
||||
// m_messagesStore.storeTemporaryPublish(newPublishEvt, sub.getClientId(), messageID);
|
||||
// }
|
||||
//publish
|
||||
if (targetSession.isActive()) {
|
||||
int messageId = m_messagesStore.nextPacketID(sub.getClientId());
|
||||
sendPublish(sub.getClientId(), topic, qos, message, false, messageId);
|
||||
// int messageId = m_messagesStore.nextPacketID(sub.getClientId());
|
||||
int messageId = targetSession.nextPacketId();
|
||||
targetSession.inFlightAckWaiting(guid, messageId);
|
||||
directSend(sub.getClientId(), topic, qos, message, false, messageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void sendPublish(String clientId, String topic, AbstractMessage.QOSType qos, ByteBuffer message, boolean retained, Integer messageID) {
|
||||
LOG.debug("sendPublish invoked clientId <{}> on topic <{}> QoS {} retained {} messageID {}", clientId, topic, qos, retained, messageID);
|
||||
protected void directSend(String clientId, String topic, AbstractMessage.QOSType qos, ByteBuffer message, boolean retained, Integer messageID) {
|
||||
LOG.debug("directSend invoked clientId <{}> on topic <{}> QoS {} retained {} messageID {}", clientId, topic, qos, retained, messageID);
|
||||
PublishMessage pubMessage = new PublishMessage();
|
||||
pubMessage.setRetainFlag(retained);
|
||||
pubMessage.setTopicName(topic);
|
||||
@@ -487,6 +498,10 @@ public class ProtocolProcessor {
|
||||
public void processPubRec(ServerChannel session, PubRecMessage msg) {
|
||||
String clientID = (String) session.getAttribute(NettyChannel.ATTR_KEY_CLIENTID);
|
||||
int messageID = msg.getMessageID();
|
||||
ClientSession targetSession = m_sessionsStore.sessionForClient(clientID);
|
||||
//remove from the inflight and move to the QoS2 second phase queue
|
||||
targetSession.inFlightAcknowledged(messageID);
|
||||
targetSession.secondPhaseAckWaiting(messageID);
|
||||
//once received a PUBREC reply with a PUBREL(messageID)
|
||||
LOG.debug("\t\tSRV <--PUBREC-- SUB processPubRec invoked for clientID {} ad messageID {}", clientID, messageID);
|
||||
PubRelMessage pubRelMessage = new PubRelMessage();
|
||||
@@ -501,10 +516,12 @@ public class ProtocolProcessor {
|
||||
int messageID = msg.getMessageID();
|
||||
LOG.debug("\t\tSRV <--PUBCOMP-- SUB processPubComp invoked for clientID {} ad messageID {}", clientID, messageID);
|
||||
//once received the PUBCOMP then remove the message from the temp memory
|
||||
m_messagesStore.cleanTemporaryPublish(clientID, messageID);
|
||||
// m_messagesStore.cleanTemporaryPublish(clientID, messageID);
|
||||
ClientSession targetSession = m_sessionsStore.sessionForClient(clientID);
|
||||
targetSession.secondPhaseAcknowledged(messageID);
|
||||
}
|
||||
|
||||
public void processDisconnect(ServerChannel session, DisconnectMessage msg) throws InterruptedException {
|
||||
public void processDisconnect(ServerChannel session) throws InterruptedException {
|
||||
String clientID = (String) session.getAttribute(NettyChannel.ATTR_KEY_CLIENTID);
|
||||
boolean cleanSession = (Boolean) session.getAttribute(NettyChannel.ATTR_KEY_CLEANSESSION);
|
||||
LOG.info("DISCONNECT client <{}> with clean session {}", clientID, cleanSession);
|
||||
@@ -620,7 +637,7 @@ public class ProtocolProcessor {
|
||||
//forwardPublishQoS0(newSubscription.getClientId(), storedMsg.getTopic(), storedMsg.getQos(), storedMsg.getPayload(), true);
|
||||
Integer packetID = storedMsg.getQos() == QOSType.MOST_ONE ? null :
|
||||
m_messagesStore.nextPacketID(newSubscription.getClientId());
|
||||
sendPublish(newSubscription.getClientId(), storedMsg.getTopic(), storedMsg.getQos(), storedMsg.getPayload(), true, packetID);
|
||||
directSend(newSubscription.getClientId(), storedMsg.getTopic(), storedMsg.getQos(), storedMsg.getPayload(), true, packetID);
|
||||
}
|
||||
|
||||
//notify the Observables
|
||||
|
||||
@@ -33,6 +33,7 @@ public class PublishEvent extends MessagingEvent {
|
||||
String m_clientID;
|
||||
//Optional attribute, available only fo QoS 1 and 2
|
||||
Integer m_msgID;
|
||||
private String guid;
|
||||
|
||||
public PublishEvent(String topic, QOSType qos, ByteBuffer message, boolean retain,
|
||||
String clientID, Integer msgID) {
|
||||
@@ -81,6 +82,14 @@ public class PublishEvent extends MessagingEvent {
|
||||
return m_msgID;
|
||||
}
|
||||
|
||||
public void setGuid(String guid) {
|
||||
this.guid = guid;
|
||||
}
|
||||
|
||||
public String getGuid() {
|
||||
return guid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PublishEvent{" +
|
||||
|
||||
+129
-63
@@ -17,6 +17,7 @@
|
||||
package org.eclipse.moquette.spi.persistence;
|
||||
|
||||
import org.eclipse.moquette.proto.MQTTException;
|
||||
import org.eclipse.moquette.proto.messages.AbstractMessage;
|
||||
import org.eclipse.moquette.spi.ClientSession;
|
||||
import org.eclipse.moquette.spi.IMatchingCondition;
|
||||
import org.eclipse.moquette.spi.IMessagesStore;
|
||||
@@ -24,10 +25,6 @@ import org.eclipse.moquette.spi.ISessionsStore;
|
||||
import org.eclipse.moquette.spi.impl.events.PublishEvent;
|
||||
import org.eclipse.moquette.spi.impl.storage.StoredPublishEvent;
|
||||
import org.eclipse.moquette.spi.impl.subscriptions.Subscription;
|
||||
import org.eclipse.moquette.proto.messages.AbstractMessage;
|
||||
|
||||
import static org.eclipse.moquette.spi.impl.Utils.defaultGet;
|
||||
|
||||
import org.mapdb.DB;
|
||||
import org.mapdb.DBMaker;
|
||||
import org.slf4j.Logger;
|
||||
@@ -38,7 +35,12 @@ import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.eclipse.moquette.spi.impl.Utils.defaultGet;
|
||||
|
||||
/**
|
||||
* MapDB main persistence implementation
|
||||
@@ -62,10 +64,10 @@ public class MapDBPersistentStore implements IMessagesStore, ISessionsStore {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MapDBPersistentStore.class);
|
||||
|
||||
private ConcurrentMap<String, StoredMessage> m_retainedStore;
|
||||
//maps clientID to the list of pending messages stored
|
||||
private ConcurrentMap<String, List<StoredPublishEvent>> m_persistentMessageStore;
|
||||
//bind clientID+MsgID -> evt message published
|
||||
private ConcurrentMap<String, StoredPublishEvent> m_inflightStore;
|
||||
//maps guid to message, it's message store
|
||||
private ConcurrentMap<String, StoredPublishEvent> m_persistentMessageStore;
|
||||
//maps clientID->[MessageId -> guid]
|
||||
private ConcurrentMap<String, Map<Integer, String>> m_inflightStore;
|
||||
//map clientID <-> set of currently in flight packet identifiers
|
||||
Map<String, Set<Integer>> m_inFlightIds;
|
||||
//bind clientID+MsgID -> evt message published
|
||||
@@ -73,6 +75,12 @@ public class MapDBPersistentStore implements IMessagesStore, ISessionsStore {
|
||||
//persistent Map of clientID, set of Subscriptions
|
||||
private ConcurrentMap<String, Set<Subscription>> m_persistentSubscriptions;
|
||||
private ConcurrentMap<String, PersistentSession> m_persistentSessions;
|
||||
|
||||
//maps clientID->[guid*]
|
||||
private ConcurrentMap<String, Set<String>> m_enqueuedStore;
|
||||
//maps clientID->[messageID*]
|
||||
private ConcurrentMap<String, Set<Integer>> m_secondPhaseStore;
|
||||
|
||||
private DB m_db;
|
||||
private String m_storePath;
|
||||
|
||||
@@ -113,6 +121,8 @@ public class MapDBPersistentStore implements IMessagesStore, ISessionsStore {
|
||||
m_persistentSubscriptions = m_db.getHashMap("subscriptions");
|
||||
m_qos2Store = m_db.getHashMap("qos2Store");
|
||||
m_persistentSessions = m_db.getHashMap("sessions");
|
||||
m_enqueuedStore = m_db.getHashMap("sessionQueue");
|
||||
m_secondPhaseStore = m_db.getHashMap("secondPhase");
|
||||
m_scheduler.scheduleWithFixedDelay(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -155,72 +165,75 @@ public class MapDBPersistentStore implements IMessagesStore, ISessionsStore {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storePublishForFuture(PublishEvent evt) {
|
||||
List<StoredPublishEvent> storedEvents;
|
||||
String clientID = evt.getClientID();
|
||||
if (!m_persistentMessageStore.containsKey(clientID)) {
|
||||
storedEvents = new ArrayList<>();
|
||||
} else {
|
||||
storedEvents = m_persistentMessageStore.get(clientID);
|
||||
}
|
||||
storedEvents.add(convertToStored(evt));
|
||||
m_persistentMessageStore.put(clientID, storedEvents);
|
||||
//NB rewind the evt message content
|
||||
LOG.debug("Stored published message for client <{}> on topic <{}>", clientID, evt.getTopic());
|
||||
public String storePublishForFuture(PublishEvent evt) {
|
||||
LOG.debug("storePublishForFuture store evt {}", evt);
|
||||
String guid = UUID.randomUUID().toString();
|
||||
evt.setGuid(guid);
|
||||
m_persistentMessageStore.put(guid, convertToStored(evt));
|
||||
return guid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PublishEvent> listMessagesInSession(String clientID) {
|
||||
List<PublishEvent> liveEvts = new ArrayList<>();
|
||||
List<StoredPublishEvent> storedEvts = defaultGet(m_persistentMessageStore, clientID, Collections.<StoredPublishEvent>emptyList());
|
||||
|
||||
for (StoredPublishEvent storedEvt : storedEvts) {
|
||||
liveEvts.add(convertFromStored(storedEvt));
|
||||
public List<PublishEvent> listMessagesInSession(Collection<String> guids) {
|
||||
List<PublishEvent> ret = new ArrayList<>();
|
||||
for (String guid : guids) {
|
||||
ret.add(convertFromStored(m_persistentMessageStore.get(guid))) ;
|
||||
}
|
||||
return liveEvts;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeMessage(String clientID, Integer messageID) {
|
||||
List<StoredPublishEvent> events = m_persistentMessageStore.get(clientID);
|
||||
if (events == null) {
|
||||
return;
|
||||
}
|
||||
StoredPublishEvent toRemoveEvt = null;
|
||||
for (StoredPublishEvent evt : events) {
|
||||
if (evt.getMessageID() == null && messageID == null) {
|
||||
//was a qos0 message (no ID)
|
||||
toRemoveEvt = evt;
|
||||
}
|
||||
if (evt.getMessageID().equals(messageID)) {
|
||||
toRemoveEvt = evt;
|
||||
}
|
||||
}
|
||||
events.remove(toRemoveEvt);
|
||||
m_persistentMessageStore.put(clientID, events);
|
||||
}
|
||||
// @Override
|
||||
// public List<PublishEvent> listMessagesInSession(String clientID) {
|
||||
// List<PublishEvent> liveEvts = new ArrayList<>();
|
||||
// List<StoredPublishEvent> storedEvts = defaultGet(m_persistentMessageStore, clientID, Collections.<StoredPublishEvent>emptyList());
|
||||
//
|
||||
// for (StoredPublishEvent storedEvt : storedEvts) {
|
||||
// liveEvts.add(convertFromStored(storedEvt));
|
||||
// }
|
||||
// return liveEvts;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void removeMessage(String clientID, Integer messageID) {
|
||||
// List<StoredPublishEvent> events = m_persistentMessageStore.get(clientID);
|
||||
// if (events == null) {
|
||||
// return;
|
||||
// }
|
||||
// StoredPublishEvent toRemoveEvt = null;
|
||||
// for (StoredPublishEvent evt : events) {
|
||||
// if (evt.getMessageID() == null && messageID == null) {
|
||||
// //was a qos0 message (no ID)
|
||||
// toRemoveEvt = evt;
|
||||
// }
|
||||
// if (evt.getMessageID().equals(messageID)) {
|
||||
// toRemoveEvt = evt;
|
||||
// }
|
||||
// }
|
||||
// events.remove(toRemoveEvt);
|
||||
// m_persistentMessageStore.put(clientID, events);
|
||||
// }
|
||||
|
||||
public void dropMessagesInSession(String clientID) {
|
||||
m_persistentMessageStore.remove(clientID);
|
||||
}
|
||||
|
||||
//----------------- In flight methods -----------------
|
||||
@Override
|
||||
public void cleanTemporaryPublish(String clientID, int packetID) {
|
||||
String publishKey = String.format("%s%d", clientID, packetID);
|
||||
m_inflightStore.remove(publishKey);
|
||||
Set<Integer> inFlightForClient = this.m_inFlightIds.get(clientID);
|
||||
if (inFlightForClient != null) {
|
||||
inFlightForClient.remove(packetID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeTemporaryPublish(PublishEvent evt, String clientID, int packetID) {
|
||||
String publishKey = String.format("%s%d", clientID, packetID);
|
||||
StoredPublishEvent storedEvt = convertToStored(evt);
|
||||
m_inflightStore.put(publishKey, storedEvt);
|
||||
}
|
||||
// @Override
|
||||
// public void cleanTemporaryPublish(String clientID, int packetID) {
|
||||
// String publishKey = String.format("%s%d", clientID, packetID);
|
||||
// m_inflightStore.remove(publishKey);
|
||||
// Set<Integer> inFlightForClient = this.m_inFlightIds.get(clientID);
|
||||
// if (inFlightForClient != null) {
|
||||
// inFlightForClient.remove(packetID);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void storeTemporaryPublish(PublishEvent evt, String clientID, int packetID) {
|
||||
// String publishKey = String.format("%s%d", clientID, packetID);
|
||||
// StoredPublishEvent storedEvt = convertToStored(evt);
|
||||
// m_inflightStore.put(publishKey, storedEvt);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Return the next valid packetIdentifier for the given client session.
|
||||
@@ -356,6 +369,59 @@ public class MapDBPersistentStore implements IMessagesStore, ISessionsStore {
|
||||
activationHelper(clientID, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inFlightAck(String clientID, int messageID) {
|
||||
Map<Integer, String> m = this.m_inflightStore.get(clientID);
|
||||
if (m == null) {
|
||||
LOG.error("Can't find the inFlight record for client <{}>", clientID);
|
||||
return;
|
||||
}
|
||||
m.remove(messageID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inFlight(String clientID, int messageID, String guid) {
|
||||
Map<Integer, String> m = this.m_inflightStore.get(clientID);
|
||||
if (m == null) {
|
||||
m = new HashMap<>();
|
||||
}
|
||||
m.put(messageID, guid);
|
||||
this.m_inflightStore.put(clientID, m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindToDeliver(String guid, String clientID) {
|
||||
Set<String> guids = defaultGet(m_enqueuedStore, clientID, new HashSet<String>());
|
||||
guids.add(guid);
|
||||
m_enqueuedStore.put(clientID, guids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> enqueued(String clientID) {
|
||||
return defaultGet(m_enqueuedStore, clientID, new HashSet<String>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEnqueued(String clientID, String guid) {
|
||||
Set<String> guids = defaultGet(m_enqueuedStore, clientID, new HashSet<String>());
|
||||
guids.remove(guid);
|
||||
m_enqueuedStore.put(clientID, guids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void secondPhaseAcknowledged(String clientID, int messageID) {
|
||||
Set<Integer> messageIDs = defaultGet(m_secondPhaseStore, clientID, new HashSet<Integer>());
|
||||
messageIDs.remove(messageID);
|
||||
m_secondPhaseStore.put(clientID, messageIDs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void secondPhaseAckWaiting(String clientID, int messageID) {
|
||||
Set<Integer> messageIDs = defaultGet(m_secondPhaseStore, clientID, new HashSet<Integer>());
|
||||
messageIDs.add(messageID);
|
||||
m_secondPhaseStore.put(clientID, messageIDs);
|
||||
}
|
||||
|
||||
private void activationHelper(String clientID, boolean activation) {
|
||||
PersistentSession storedSession = m_persistentSessions.get(clientID);
|
||||
if (storedSession == null) {
|
||||
|
||||
Reference in New Issue
Block a user