2015-10-24 16:26:43 +02:00
|
|
|
|
/*
|
2017-03-19 22:01:23 +01:00
|
|
|
|
* Copyright (c) 2012-2017 The original author or authors
|
2015-10-24 16:26:43 +02:00
|
|
|
|
* ------------------------------------------------------
|
|
|
|
|
|
* 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.
|
|
|
|
|
|
*/
|
2017-02-01 15:45:06 +01:00
|
|
|
|
|
2015-11-20 20:54:27 +01:00
|
|
|
|
package io.moquette.spi;
|
2015-10-24 16:26:43 +02:00
|
|
|
|
|
2017-01-09 21:53:26 +01:00
|
|
|
|
import io.moquette.spi.ISessionsStore.ClientTopicCouple;
|
2015-11-20 20:54:27 +01:00
|
|
|
|
import io.moquette.spi.impl.subscriptions.Subscription;
|
2017-02-14 17:23:04 +01:00
|
|
|
|
import io.moquette.spi.impl.subscriptions.Topic;
|
2015-10-31 11:14:40 +01:00
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
|
|
import java.util.HashSet;
|
|
|
|
|
|
import java.util.Set;
|
2016-03-06 19:50:26 +01:00
|
|
|
|
import java.util.concurrent.BlockingQueue;
|
2015-10-24 16:26:43 +02:00
|
|
|
|
|
|
|
|
|
|
/**
|
2015-11-15 11:16:18 +01:00
|
|
|
|
* 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>
|
|
|
|
|
|
*
|
2015-10-24 16:26:43 +02:00
|
|
|
|
* @author andrea
|
|
|
|
|
|
*/
|
|
|
|
|
|
public class ClientSession {
|
|
|
|
|
|
|
2015-10-31 11:14:40 +01:00
|
|
|
|
private final static Logger LOG = LoggerFactory.getLogger(ClientSession.class);
|
|
|
|
|
|
|
2015-10-24 16:26:43 +02:00
|
|
|
|
public final String clientID;
|
|
|
|
|
|
|
|
|
|
|
|
private final IMessagesStore messagesStore;
|
|
|
|
|
|
|
2015-10-31 11:14:40 +01:00
|
|
|
|
private final ISessionsStore m_sessionsStore;
|
|
|
|
|
|
|
|
|
|
|
|
private Set<Subscription> subscriptions = new HashSet<>();
|
|
|
|
|
|
|
2016-02-02 22:39:11 +01:00
|
|
|
|
private volatile boolean cleanSession;
|
2015-11-08 18:25:55 +01:00
|
|
|
|
|
2017-02-01 15:45:06 +01:00
|
|
|
|
// private BlockingQueue<AbstractMessage> m_queueToPublish = new
|
|
|
|
|
|
// ArrayBlockingQueue<>(Constants.MAX_MESSAGE_QUEUE);
|
2016-03-06 19:50:26 +01:00
|
|
|
|
|
2015-11-08 18:25:55 +01:00
|
|
|
|
public ClientSession(String clientID, IMessagesStore messagesStore, ISessionsStore sessionsStore,
|
2017-02-01 15:45:06 +01:00
|
|
|
|
boolean cleanSession) {
|
2015-10-24 16:26:43 +02:00
|
|
|
|
this.clientID = clientID;
|
|
|
|
|
|
this.messagesStore = messagesStore;
|
2015-10-31 11:14:40 +01:00
|
|
|
|
this.m_sessionsStore = sessionsStore;
|
2015-11-08 18:25:55 +01:00
|
|
|
|
this.cleanSession = cleanSession;
|
2015-10-24 16:26:43 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2017-02-01 15:45:06 +01:00
|
|
|
|
* 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.
|
2017-01-09 21:53:26 +01:00
|
|
|
|
*
|
2015-10-24 16:26:43 +02:00
|
|
|
|
* @return the list of messages to be delivered for client related to the session.
|
2017-02-01 15:45:06 +01:00
|
|
|
|
*/
|
2017-01-09 21:53:26 +01:00
|
|
|
|
public BlockingQueue<IMessagesStore.StoredMessage> queue() {
|
2017-01-12 13:11:08 +01:00
|
|
|
|
LOG.info("Retrieving stored messages. MqttClientId = {}.", clientID);
|
2017-01-09 21:53:26 +01:00
|
|
|
|
return this.m_sessionsStore.queue(clientID);
|
2015-10-24 16:26:43 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public String toString() {
|
2017-02-01 15:45:06 +01:00
|
|
|
|
return "ClientSession{clientID='" + clientID + '\'' + "}";
|
2015-10-24 16:26:43 +02:00
|
|
|
|
}
|
2015-10-31 11:14:40 +01:00
|
|
|
|
|
2016-07-17 14:24:19 +02:00
|
|
|
|
public boolean subscribe(Subscription newSubscription) {
|
2017-02-01 15:45:06 +01:00
|
|
|
|
LOG.info(
|
|
|
|
|
|
"Adding new subscription. MqttClientId = {}, topics = {}, qos = {}.",
|
|
|
|
|
|
newSubscription.getClientId(),
|
|
|
|
|
|
newSubscription.getTopicFilter(),
|
|
|
|
|
|
newSubscription.getRequestedQos());
|
2017-02-14 17:23:04 +01:00
|
|
|
|
boolean validTopic = newSubscription.getTopicFilter().isValid();
|
2015-10-31 11:14:40 +01:00
|
|
|
|
if (!validTopic) {
|
2017-02-01 15:45:06 +01:00
|
|
|
|
LOG.warn(
|
|
|
|
|
|
"The topic filter is not valid. MqttClientId = {}, topics = {}.",
|
|
|
|
|
|
newSubscription.getClientId(),
|
|
|
|
|
|
newSubscription.getTopicFilter());
|
|
|
|
|
|
// send SUBACK with 0x80 for this topic filter
|
2015-10-31 11:14:40 +01:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2015-12-06 19:31:36 +01:00
|
|
|
|
ClientTopicCouple matchingCouple = new ClientTopicCouple(this.clientID, newSubscription.getTopicFilter());
|
2015-12-06 11:43:29 +01:00
|
|
|
|
Subscription existingSub = m_sessionsStore.getSubscription(matchingCouple);
|
2017-02-01 15:45:06 +01:00
|
|
|
|
// update the selected subscriptions if not present or if has a greater qos
|
2017-01-28 10:06:30 +01:00
|
|
|
|
if (existingSub == null || existingSub.getRequestedQos().value() < newSubscription.getRequestedQos().value()) {
|
2015-12-06 11:43:29 +01:00
|
|
|
|
if (existingSub != null) {
|
2017-02-01 15:45:06 +01:00
|
|
|
|
LOG.info(
|
|
|
|
|
|
"The subscription already existed with a lower QoS value. It will be updated. "
|
|
|
|
|
|
+ "MqttClientId = {}, topics = {}, existingQos = {}, newQos = {}.",
|
|
|
|
|
|
newSubscription.getClientId(),
|
|
|
|
|
|
newSubscription.getTopicFilter(),
|
|
|
|
|
|
existingSub.getRequestedQos(),
|
2017-01-12 13:11:08 +01:00
|
|
|
|
newSubscription.getRequestedQos());
|
2015-12-06 11:43:29 +01:00
|
|
|
|
subscriptions.remove(newSubscription);
|
|
|
|
|
|
}
|
|
|
|
|
|
subscriptions.add(newSubscription);
|
|
|
|
|
|
m_sessionsStore.addNewSubscription(newSubscription);
|
|
|
|
|
|
}
|
2015-10-31 11:14:40 +01:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2015-10-31 11:34:15 +01:00
|
|
|
|
|
2017-02-14 17:23:04 +01:00
|
|
|
|
public void unsubscribeFrom(Topic topicFilter) {
|
2017-01-12 13:11:08 +01:00
|
|
|
|
LOG.info("Removing subscription. MqttClientID = {}, topics = {}.", clientID, topicFilter);
|
2015-10-31 11:34:15 +01:00
|
|
|
|
m_sessionsStore.removeSubscription(topicFilter, clientID);
|
|
|
|
|
|
Set<Subscription> subscriptionsToRemove = new HashSet<>();
|
|
|
|
|
|
for (Subscription sub : this.subscriptions) {
|
|
|
|
|
|
if (sub.getTopicFilter().equals(topicFilter)) {
|
|
|
|
|
|
subscriptionsToRemove.add(sub);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
subscriptions.removeAll(subscriptionsToRemove);
|
|
|
|
|
|
}
|
2015-11-08 18:25:55 +01:00
|
|
|
|
|
|
|
|
|
|
public void disconnect() {
|
|
|
|
|
|
if (this.cleanSession) {
|
2017-01-12 13:11:08 +01:00
|
|
|
|
LOG.info("The client has disconnected. Removing its subscriptions. MqttClientId = {}.", clientID);
|
2017-02-01 15:45:06 +01:00
|
|
|
|
// cleanup topic subscriptions
|
2015-11-08 18:25:55 +01:00
|
|
|
|
cleanSession();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void cleanSession() {
|
2017-01-12 13:11:08 +01:00
|
|
|
|
LOG.info("Wiping existing subscriptions. MqttClientId = {}.", this.clientID);
|
2015-11-08 18:25:55 +01:00
|
|
|
|
m_sessionsStore.wipeSubscriptions(this.clientID);
|
|
|
|
|
|
|
2017-02-01 15:45:06 +01:00
|
|
|
|
// remove also the messages stored of type QoS1/2
|
2017-01-12 13:11:08 +01:00
|
|
|
|
LOG.info("Removing stored messages with QoS 1 and 2. MqttClientId = {}.", this.clientID);
|
2015-11-08 18:25:55 +01:00
|
|
|
|
messagesStore.dropMessagesInSession(this.clientID);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-12-06 19:55:04 +01:00
|
|
|
|
public boolean isCleanSession() {
|
|
|
|
|
|
return this.cleanSession;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-02-02 22:39:11 +01:00
|
|
|
|
public void cleanSession(boolean cleanSession) {
|
|
|
|
|
|
this.cleanSession = cleanSession;
|
|
|
|
|
|
this.m_sessionsStore.updateCleanStatus(this.clientID, cleanSession);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-11-15 11:16:18 +01:00
|
|
|
|
public int nextPacketId() {
|
2016-01-17 13:00:36 +01:00
|
|
|
|
return this.m_sessionsStore.nextPacketID(this.clientID);
|
2015-11-15 11:16:18 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void inFlightAcknowledged(int messageID) {
|
2017-02-01 15:45:06 +01:00
|
|
|
|
if (LOG.isTraceEnabled())
|
2017-01-12 13:11:08 +01:00
|
|
|
|
LOG.trace("Acknowledging inflight, clientID <{}> messageID {}", this.clientID, messageID);
|
2015-11-15 11:16:18 +01:00
|
|
|
|
m_sessionsStore.inFlightAck(this.clientID, messageID);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-09-23 11:51:25 +02:00
|
|
|
|
public void inFlightAckWaiting(MessageGUID guid, int messageID) {
|
2017-01-12 13:11:08 +01:00
|
|
|
|
if (LOG.isTraceEnabled())
|
|
|
|
|
|
LOG.trace("Adding to inflight {}, guid <{}>", messageID, guid);
|
2015-11-15 11:16:18 +01:00
|
|
|
|
m_sessionsStore.inFlight(this.clientID, messageID, guid);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-16 23:01:48 +02:00
|
|
|
|
public IMessagesStore.StoredMessage secondPhaseAcknowledged(int messageID) {
|
2016-09-23 11:51:25 +02:00
|
|
|
|
MessageGUID guid = m_sessionsStore.secondPhaseAcknowledged(clientID, messageID);
|
2016-06-16 23:01:48 +02:00
|
|
|
|
return messagesStore.getMessageByGuid(guid);
|
2015-11-15 11:16:18 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-09 21:53:26 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Enqueue a message to be sent to the client.
|
2017-02-01 15:45:06 +01:00
|
|
|
|
*
|
|
|
|
|
|
* @param message
|
|
|
|
|
|
* the message to enqueue.
|
|
|
|
|
|
*/
|
2017-01-09 21:53:26 +01:00
|
|
|
|
public void enqueue(IMessagesStore.StoredMessage message) {
|
|
|
|
|
|
this.m_sessionsStore.queue(this.clientID).add(message);
|
2015-11-15 11:16:18 +01:00
|
|
|
|
}
|
2015-11-16 23:45:47 +01:00
|
|
|
|
|
2015-11-18 22:19:43 +01:00
|
|
|
|
public IMessagesStore.StoredMessage storedMessage(int messageID) {
|
2017-01-31 18:06:03 +01:00
|
|
|
|
final MessageGUID guid = messagesStore.mapToGuid(clientID, messageID);
|
2015-11-16 23:45:47 +01:00
|
|
|
|
return messagesStore.getMessageByGuid(guid);
|
|
|
|
|
|
}
|
2016-03-06 19:50:26 +01:00
|
|
|
|
|
2016-06-16 23:01:48 +02:00
|
|
|
|
public void moveInFlightToSecondPhaseAckWaiting(int messageID) {
|
|
|
|
|
|
m_sessionsStore.moveInFlightToSecondPhaseAckWaiting(this.clientID, messageID);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public IMessagesStore.StoredMessage getInflightMessage(int messageID) {
|
|
|
|
|
|
return m_sessionsStore.getInflightMessage(clientID, messageID);
|
|
|
|
|
|
}
|
2017-01-12 17:00:18 +01:00
|
|
|
|
|
|
|
|
|
|
public Set<Subscription> getSubscriptions() {
|
|
|
|
|
|
return subscriptions;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public int getPendingPublishMessagesNo() {
|
|
|
|
|
|
return m_sessionsStore.getPendingPublishMessagesNo(clientID);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public int getSecondPhaseAckPendingMessages() {
|
|
|
|
|
|
return m_sessionsStore.getSecondPhaseAckPendingMessages(clientID);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public int getInflightMessagesNo() {
|
|
|
|
|
|
return m_sessionsStore.getInflightMessagesNo(clientID);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-02-01 15:45:06 +01:00
|
|
|
|
}
|