Added onMessageConsumed logic.
Related to: https://github.com/andsel/moquette/issues/156
This commit is contained in:
@@ -1,41 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 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.interception;
|
||||
|
||||
import io.moquette.interception.messages.*;
|
||||
|
||||
/**
|
||||
* Basic abstract class usefull to avoid empty methods creation in subclasses.
|
||||
*
|
||||
* Created by andrea on 08/12/15.
|
||||
*/
|
||||
public abstract class AbstractInterceptHandler implements InterceptHandler {
|
||||
|
||||
@Override
|
||||
public void onConnect(InterceptConnectMessage msg) {}
|
||||
|
||||
@Override
|
||||
public void onDisconnect(InterceptDisconnectMessage msg) {}
|
||||
|
||||
@Override
|
||||
public void onPublish(InterceptPublishMessage msg) {}
|
||||
|
||||
@Override
|
||||
public void onSubscribe(InterceptSubscribeMessage msg) {}
|
||||
|
||||
@Override
|
||||
public void onUnsubscribe(InterceptUnsubscribeMessage msg) {}
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2012-2015 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.interception;
|
||||
|
||||
import io.moquette.interception.messages.*;
|
||||
|
||||
/**
|
||||
* Basic abstract class usefull to avoid empty methods creation in subclasses.
|
||||
*
|
||||
* Created by andrea on 08/12/15.
|
||||
*/
|
||||
public abstract class AbstractInterceptHandler implements InterceptHandler {
|
||||
|
||||
@Override
|
||||
public void onConnect(InterceptConnectMessage msg) {}
|
||||
|
||||
@Override
|
||||
public void onDisconnect(InterceptDisconnectMessage msg) {}
|
||||
|
||||
@Override
|
||||
public void onPublish(InterceptPublishMessage msg) {}
|
||||
|
||||
@Override
|
||||
public void onSubscribe(InterceptSubscribeMessage msg) {}
|
||||
|
||||
@Override
|
||||
public void onUnsubscribe(InterceptUnsubscribeMessage msg) {}
|
||||
|
||||
@Override
|
||||
public void onMessageConsumed(InterceptConsumedMessage msg) {}
|
||||
}
|
||||
|
||||
@@ -1,49 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 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.interception;
|
||||
|
||||
import io.moquette.parser.proto.messages.AbstractMessage;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
import io.moquette.interception.messages.InterceptConnectMessage;
|
||||
import io.moquette.interception.messages.InterceptDisconnectMessage;
|
||||
import io.moquette.interception.messages.InterceptPublishMessage;
|
||||
import io.moquette.interception.messages.InterceptSubscribeMessage;
|
||||
import io.moquette.interception.messages.InterceptUnsubscribeMessage;
|
||||
|
||||
/**
|
||||
* This interface is used to inject code for intercepting broker events.
|
||||
* <p>
|
||||
* The events can act only as observers.
|
||||
* <p>
|
||||
* Almost every method receives a subclass of {@link AbstractMessage}, except
|
||||
* <code>onDisconnect</code> that receives the client id string and
|
||||
* <code>onSubscribe</code> and <code>onUnsubscribe</code> that receive a
|
||||
* {@link Subscription} object.
|
||||
*
|
||||
* @author Wagner Macedo
|
||||
*/
|
||||
public interface InterceptHandler {
|
||||
|
||||
void onConnect(InterceptConnectMessage msg);
|
||||
|
||||
void onDisconnect(InterceptDisconnectMessage msg);
|
||||
|
||||
void onPublish(InterceptPublishMessage msg);
|
||||
|
||||
void onSubscribe(InterceptSubscribeMessage msg);
|
||||
|
||||
void onUnsubscribe(InterceptUnsubscribeMessage msg);
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2012-2015 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.interception;
|
||||
|
||||
import io.moquette.parser.proto.messages.AbstractMessage;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
import io.moquette.interception.messages.InterceptConnectMessage;
|
||||
import io.moquette.interception.messages.InterceptConsumedMessage;
|
||||
import io.moquette.interception.messages.InterceptDisconnectMessage;
|
||||
import io.moquette.interception.messages.InterceptPublishMessage;
|
||||
import io.moquette.interception.messages.InterceptSubscribeMessage;
|
||||
import io.moquette.interception.messages.InterceptUnsubscribeMessage;
|
||||
|
||||
/**
|
||||
* This interface is used to inject code for intercepting broker events.
|
||||
* <p>
|
||||
* The events can act only as observers.
|
||||
* <p>
|
||||
* Almost every method receives a subclass of {@link AbstractMessage}, except
|
||||
* <code>onDisconnect</code> that receives the client id string and
|
||||
* <code>onSubscribe</code> and <code>onUnsubscribe</code> that receive a
|
||||
* {@link Subscription} object.
|
||||
*
|
||||
* @author Wagner Macedo
|
||||
*/
|
||||
public interface InterceptHandler {
|
||||
|
||||
void onConnect(InterceptConnectMessage msg);
|
||||
|
||||
void onDisconnect(InterceptDisconnectMessage msg);
|
||||
|
||||
void onPublish(InterceptPublishMessage msg);
|
||||
|
||||
void onSubscribe(InterceptSubscribeMessage msg);
|
||||
|
||||
void onUnsubscribe(InterceptUnsubscribeMessage msg);
|
||||
|
||||
void onMessageConsumed(InterceptConsumedMessage msg);
|
||||
}
|
||||
|
||||
@@ -1,47 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 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.interception;
|
||||
|
||||
import io.moquette.parser.proto.messages.ConnectMessage;
|
||||
import io.moquette.parser.proto.messages.PublishMessage;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
|
||||
/**
|
||||
* This interface is to be used internally by the broker components.
|
||||
* <p>
|
||||
* An interface is used instead of a class to allow more flexibility in changing
|
||||
* an implementation.
|
||||
* <p>
|
||||
* Interceptor implementations forward notifications to a <code>InterceptHandler</code>,
|
||||
* that is normally a field. So, the implementations should act as a proxy to a custom
|
||||
* intercept handler.
|
||||
*
|
||||
* @see InterceptHandler
|
||||
* @author Wagner Macedo
|
||||
*/
|
||||
public interface Interceptor {
|
||||
|
||||
void notifyClientConnected(ConnectMessage msg);
|
||||
|
||||
void notifyClientDisconnected(String clientID);
|
||||
|
||||
void notifyTopicPublished(PublishMessage msg, String clientID);
|
||||
|
||||
void notifyTopicSubscribed(Subscription sub);
|
||||
|
||||
void notifyTopicUnsubscribed(String topic, String clientID);
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2012-2015 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.interception;
|
||||
|
||||
import io.moquette.interception.messages.InterceptConsumedMessage;
|
||||
import io.moquette.parser.proto.messages.ConnectMessage;
|
||||
import io.moquette.parser.proto.messages.PublishMessage;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
|
||||
/**
|
||||
* This interface is to be used internally by the broker components.
|
||||
* <p>
|
||||
* An interface is used instead of a class to allow more flexibility in changing
|
||||
* an implementation.
|
||||
* <p>
|
||||
* Interceptor implementations forward notifications to a <code>InterceptHandler</code>,
|
||||
* that is normally a field. So, the implementations should act as a proxy to a custom
|
||||
* intercept handler.
|
||||
*
|
||||
* @see InterceptHandler
|
||||
* @author Wagner Macedo
|
||||
*/
|
||||
public interface Interceptor {
|
||||
|
||||
void notifyClientConnected(ConnectMessage msg);
|
||||
|
||||
void notifyClientDisconnected(String clientID);
|
||||
|
||||
void notifyTopicPublished(PublishMessage msg, String clientID);
|
||||
|
||||
void notifyTopicSubscribed(Subscription sub);
|
||||
|
||||
void notifyTopicUnsubscribed(String topic, String clientID);
|
||||
|
||||
void notifyMessageConsumed(InterceptConsumedMessage msg);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.moquette.interception.messages;
|
||||
|
||||
import static io.moquette.spi.IMessagesStore.StoredMessage;
|
||||
|
||||
public class InterceptConsumedMessage {
|
||||
final private StoredMessage msg;
|
||||
|
||||
public InterceptConsumedMessage( final StoredMessage msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public StoredMessage getMsg() {
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
@@ -1,141 +1,144 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 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;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
|
||||
/**
|
||||
* Store used to handle the persistence of the subscriptions tree.
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public interface ISessionsStore {
|
||||
|
||||
void updateCleanStatus(String clientID, boolean cleanSession);
|
||||
|
||||
class ClientTopicCouple {
|
||||
public final String topicFilter;
|
||||
public final String clientID;
|
||||
|
||||
public ClientTopicCouple(String clientID, String topicFilter) {
|
||||
this.clientID = clientID;
|
||||
this.topicFilter = topicFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
ClientTopicCouple that = (ClientTopicCouple) o;
|
||||
|
||||
if (topicFilter != null ? !topicFilter.equals(that.topicFilter) : that.topicFilter != null) return false;
|
||||
return !(clientID != null ? !clientID.equals(that.clientID) : that.clientID != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = topicFilter != null ? topicFilter.hashCode() : 0;
|
||||
result = 31 * result + (clientID != null ? clientID.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ClientTopicCouple{" +
|
||||
"topicFilter='" + topicFilter + '\'' +
|
||||
", clientID='" + clientID + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void initStore();
|
||||
|
||||
/**
|
||||
* Add a new subscription to the session
|
||||
*/
|
||||
void addNewSubscription(Subscription newSubscription);
|
||||
|
||||
/**
|
||||
* Removed a specific subscription
|
||||
* */
|
||||
void removeSubscription(String topic, String clientID);
|
||||
|
||||
/**
|
||||
* Remove all the subscriptions of the session
|
||||
*/
|
||||
void wipeSubscriptions(String sessionID);
|
||||
|
||||
/**
|
||||
* Return all topic filters to recreate the subscription tree.
|
||||
* */
|
||||
List<ClientTopicCouple> listAllSubscriptions();
|
||||
|
||||
/**
|
||||
* @return the subscription stored by clientID and topicFilter, if any else null;
|
||||
* */
|
||||
Subscription getSubscription(ClientTopicCouple couple);
|
||||
|
||||
/**
|
||||
* @return true iff there are subscriptions persisted with clientID
|
||||
*/
|
||||
boolean contains(String clientID);
|
||||
|
||||
ClientSession createNewSession(String clientID, boolean cleanSession);
|
||||
|
||||
/**
|
||||
* @param clientID the client owning the session.
|
||||
* @return the session for the given clientID, null if not found.
|
||||
* */
|
||||
ClientSession sessionForClient(String clientID);
|
||||
|
||||
void inFlightAck(String clientID, int messageID);
|
||||
|
||||
/**
|
||||
* Save the binding messageID, clientID <-> guid
|
||||
* */
|
||||
void inFlight(String clientID, int messageID, String guid);
|
||||
|
||||
/**
|
||||
* Return the next valid packetIdentifier for the given client session.
|
||||
* */
|
||||
int nextPacketID(String clientID);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
String mapToGuid(String clientID, int messageID);
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2012-2015 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;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import io.moquette.spi.IMessagesStore.StoredMessage;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
|
||||
/**
|
||||
* Store used to handle the persistence of the subscriptions tree.
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public interface ISessionsStore {
|
||||
|
||||
void updateCleanStatus(String clientID, boolean cleanSession);
|
||||
|
||||
class ClientTopicCouple {
|
||||
public final String topicFilter;
|
||||
public final String clientID;
|
||||
|
||||
public ClientTopicCouple(String clientID, String topicFilter) {
|
||||
this.clientID = clientID;
|
||||
this.topicFilter = topicFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
ClientTopicCouple that = (ClientTopicCouple) o;
|
||||
|
||||
if (topicFilter != null ? !topicFilter.equals(that.topicFilter) : that.topicFilter != null) return false;
|
||||
return !(clientID != null ? !clientID.equals(that.clientID) : that.clientID != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = topicFilter != null ? topicFilter.hashCode() : 0;
|
||||
result = 31 * result + (clientID != null ? clientID.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ClientTopicCouple{" +
|
||||
"topicFilter='" + topicFilter + '\'' +
|
||||
", clientID='" + clientID + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void initStore();
|
||||
|
||||
/**
|
||||
* Add a new subscription to the session
|
||||
*/
|
||||
void addNewSubscription(Subscription newSubscription);
|
||||
|
||||
/**
|
||||
* Removed a specific subscription
|
||||
* */
|
||||
void removeSubscription(String topic, String clientID);
|
||||
|
||||
/**
|
||||
* Remove all the subscriptions of the session
|
||||
*/
|
||||
void wipeSubscriptions(String sessionID);
|
||||
|
||||
/**
|
||||
* Return all topic filters to recreate the subscription tree.
|
||||
* */
|
||||
List<ClientTopicCouple> listAllSubscriptions();
|
||||
|
||||
/**
|
||||
* @return the subscription stored by clientID and topicFilter, if any else null;
|
||||
* */
|
||||
Subscription getSubscription(ClientTopicCouple couple);
|
||||
|
||||
/**
|
||||
* @return true iff there are subscriptions persisted with clientID
|
||||
*/
|
||||
boolean contains(String clientID);
|
||||
|
||||
ClientSession createNewSession(String clientID, boolean cleanSession);
|
||||
|
||||
/**
|
||||
* @param clientID the client owning the session.
|
||||
* @return the session for the given clientID, null if not found.
|
||||
* */
|
||||
ClientSession sessionForClient(String clientID);
|
||||
|
||||
void inFlightAck(String clientID, int messageID);
|
||||
|
||||
/**
|
||||
* Save the binding messageID, clientID <-> guid
|
||||
* */
|
||||
void inFlight(String clientID, int messageID, String guid);
|
||||
|
||||
/**
|
||||
* Return the next valid packetIdentifier for the given client session.
|
||||
* */
|
||||
int nextPacketID(String clientID);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
String mapToGuid(String clientID, int messageID);
|
||||
|
||||
public StoredMessage getInflightMessage(String clientID, int messageID);
|
||||
}
|
||||
|
||||
@@ -1,109 +1,121 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 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;
|
||||
|
||||
import io.moquette.interception.InterceptHandler;
|
||||
import io.moquette.interception.Interceptor;
|
||||
import io.moquette.interception.messages.*;
|
||||
import io.moquette.parser.proto.messages.ConnectMessage;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
import io.moquette.parser.proto.messages.PublishMessage;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* An interceptor that execute the interception tasks asynchronously.
|
||||
*
|
||||
* @author Wagner Macedo
|
||||
*/
|
||||
final class BrokerInterceptor implements Interceptor {
|
||||
private final List<InterceptHandler> handlers;
|
||||
private final ExecutorService executor;
|
||||
|
||||
BrokerInterceptor(List<InterceptHandler> handlers) {
|
||||
this.handlers = handlers;
|
||||
executor = Executors.newFixedThreadPool(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown graciously the executor service
|
||||
*/
|
||||
void stop() {
|
||||
executor.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyClientConnected(final ConnectMessage msg) {
|
||||
for (final InterceptHandler handler : this.handlers) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handler.onConnect(new InterceptConnectMessage(msg));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyClientDisconnected(final String clientID) {
|
||||
for (final InterceptHandler handler : this.handlers) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handler.onDisconnect(new InterceptDisconnectMessage(clientID));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyTopicPublished(final PublishMessage msg, final String clientID) {
|
||||
for (final InterceptHandler handler : this.handlers) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handler.onPublish(new InterceptPublishMessage(msg, clientID));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyTopicSubscribed(final Subscription sub) {
|
||||
for (final InterceptHandler handler : this.handlers) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handler.onSubscribe(new InterceptSubscribeMessage(sub));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyTopicUnsubscribed(final String topic, final String clientID) {
|
||||
for (final InterceptHandler handler : this.handlers) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handler.onUnsubscribe(new InterceptUnsubscribeMessage(topic, clientID));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2012-2015 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;
|
||||
|
||||
import io.moquette.interception.InterceptHandler;
|
||||
import io.moquette.interception.Interceptor;
|
||||
import io.moquette.interception.messages.*;
|
||||
import io.moquette.parser.proto.messages.ConnectMessage;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
import io.moquette.parser.proto.messages.PublishMessage;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* An interceptor that execute the interception tasks asynchronously.
|
||||
*
|
||||
* @author Wagner Macedo
|
||||
*/
|
||||
final class BrokerInterceptor implements Interceptor {
|
||||
private final List<InterceptHandler> handlers;
|
||||
private final ExecutorService executor;
|
||||
|
||||
BrokerInterceptor(List<InterceptHandler> handlers) {
|
||||
this.handlers = handlers;
|
||||
executor = Executors.newFixedThreadPool(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown graciously the executor service
|
||||
*/
|
||||
void stop() {
|
||||
executor.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyClientConnected(final ConnectMessage msg) {
|
||||
for (final InterceptHandler handler : this.handlers) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handler.onConnect(new InterceptConnectMessage(msg));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyClientDisconnected(final String clientID) {
|
||||
for (final InterceptHandler handler : this.handlers) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handler.onDisconnect(new InterceptDisconnectMessage(clientID));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyTopicPublished(final PublishMessage msg, final String clientID) {
|
||||
for (final InterceptHandler handler : this.handlers) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handler.onPublish(new InterceptPublishMessage(msg, clientID));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyTopicSubscribed(final Subscription sub) {
|
||||
for (final InterceptHandler handler : this.handlers) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handler.onSubscribe(new InterceptSubscribeMessage(sub));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyTopicUnsubscribed(final String topic, final String clientID) {
|
||||
for (final InterceptHandler handler : this.handlers) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handler.onUnsubscribe(new InterceptUnsubscribeMessage(topic, clientID));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyMessageConsumed( final InterceptConsumedMessage msg ) {
|
||||
for (final InterceptHandler handler : this.handlers) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handler.onMessageConsumed(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,240 +1,252 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 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.persistence;
|
||||
|
||||
import io.moquette.spi.ClientSession;
|
||||
import io.moquette.spi.IMessagesStore;
|
||||
import io.moquette.spi.ISessionsStore;
|
||||
import io.moquette.spi.impl.Utils;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
import io.moquette.spi.persistence.MapDBPersistentStore.PersistentSession;
|
||||
import org.mapdb.DB;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* ISessionsStore implementation backed by MapDB.
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
class MapDBSessionsStore implements ISessionsStore {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MapDBSessionsStore.class);
|
||||
|
||||
//maps clientID->[MessageId -> guid]
|
||||
private ConcurrentMap<String, Map<Integer, String>> m_inflightStore;
|
||||
//map clientID <-> set of currently in flight packet identifiers
|
||||
private Map<String, Set<Integer>> m_inFlightIds;
|
||||
private ConcurrentMap<String, PersistentSession> m_persistentSessions;
|
||||
//maps clientID->[guid*], insertion order cares, it's queue
|
||||
private ConcurrentMap<String, List<String>> m_enqueuedStore;
|
||||
//maps clientID->[messageID*]
|
||||
private ConcurrentMap<String, Set<Integer>> m_secondPhaseStore;
|
||||
|
||||
private final DB m_db;
|
||||
private final IMessagesStore m_messagesStore;
|
||||
|
||||
MapDBSessionsStore(DB db, IMessagesStore messagesStore) {
|
||||
m_db = db;
|
||||
m_messagesStore = messagesStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initStore() {
|
||||
m_inflightStore = m_db.getHashMap("inflight");
|
||||
m_inFlightIds = m_db.getHashMap("inflightPacketIDs");
|
||||
m_persistentSessions = m_db.getHashMap("sessions");
|
||||
m_enqueuedStore = m_db.getHashMap("sessionQueue");
|
||||
m_secondPhaseStore = m_db.getHashMap("secondPhase");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNewSubscription(Subscription newSubscription) {
|
||||
LOG.debug("addNewSubscription invoked with subscription {}", newSubscription);
|
||||
final String clientID = newSubscription.getClientId();
|
||||
m_db.getHashMap("subscriptions_" + clientID).put(newSubscription.getTopicFilter(), newSubscription);
|
||||
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("subscriptions_{}: {}", clientID, m_db.getHashMap("subscriptions_" + clientID));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSubscription(String topicFilter, String clientID) {
|
||||
LOG.debug("removeSubscription topic filter: {} for clientID: {}", topicFilter, clientID);
|
||||
if (!m_db.exists("subscriptions_" + clientID)) {
|
||||
return;
|
||||
}
|
||||
m_db.getHashMap("subscriptions_" + clientID).remove(topicFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void wipeSubscriptions(String clientID) {
|
||||
LOG.debug("wipeSubscriptions");
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("Subscription pre wipe: subscriptions_{}: {}", clientID, m_db.getHashMap("subscriptions_" + clientID));
|
||||
}
|
||||
m_db.delete("subscriptions_" + clientID);
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("Subscription post wipe: subscriptions_{}: {}", clientID, m_db.getHashMap("subscriptions_" + clientID));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ClientTopicCouple> listAllSubscriptions() {
|
||||
final List<ClientTopicCouple> allSubscriptions = new ArrayList<>();
|
||||
for (String clientID : m_persistentSessions.keySet()) {
|
||||
ConcurrentMap<String, Subscription> clientSubscriptions = m_db.getHashMap("subscriptions_" + clientID);
|
||||
for (String topicFilter : clientSubscriptions.keySet()) {
|
||||
allSubscriptions.add(new ClientTopicCouple(clientID, topicFilter));
|
||||
}
|
||||
}
|
||||
LOG.debug("retrieveAllSubscriptions returning subs {}", allSubscriptions);
|
||||
return allSubscriptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Subscription getSubscription(ClientTopicCouple couple) {
|
||||
ConcurrentMap<String, Subscription> clientSubscriptions = m_db.getHashMap("subscriptions_" + couple.clientID);
|
||||
LOG.debug("subscriptions_{}: {}", couple.clientID, clientSubscriptions);
|
||||
return clientSubscriptions.get(couple.topicFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(String clientID) {
|
||||
return m_db.exists("subscriptions_" + clientID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSession createNewSession(String clientID, boolean cleanSession) {
|
||||
LOG.debug("createNewSession for client <{}> with clean flag <{}>", clientID, cleanSession);
|
||||
if (m_persistentSessions.containsKey(clientID)) {
|
||||
LOG.error("already exists a session for client <{}>, bad condition", clientID);
|
||||
throw new IllegalArgumentException("Can't create a session with the ID of an already existing" + clientID);
|
||||
}
|
||||
LOG.debug("clientID {} is a newcome, creating it's empty subscriptions set", clientID);
|
||||
m_persistentSessions.putIfAbsent(clientID, new PersistentSession(cleanSession));
|
||||
return new ClientSession(clientID, m_messagesStore, this, cleanSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSession sessionForClient(String clientID) {
|
||||
if (!m_persistentSessions.containsKey(clientID)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PersistentSession storedSession = m_persistentSessions.get(clientID);
|
||||
return new ClientSession(clientID, m_messagesStore, this, storedSession.cleanSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCleanStatus(String clientID, boolean cleanSession) {
|
||||
m_persistentSessions.put(clientID, new MapDBPersistentStore.PersistentSession(cleanSession));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next valid packetIdentifier for the given client session.
|
||||
* */
|
||||
@Override
|
||||
public int nextPacketID(String clientID) {
|
||||
Set<Integer> inFlightForClient = this.m_inFlightIds.get(clientID);
|
||||
if (inFlightForClient == null) {
|
||||
int nextPacketId = 1;
|
||||
inFlightForClient = new HashSet<>();
|
||||
inFlightForClient.add(nextPacketId);
|
||||
this.m_inFlightIds.put(clientID, inFlightForClient);
|
||||
return nextPacketId;
|
||||
|
||||
}
|
||||
|
||||
int maxId = inFlightForClient.isEmpty() ? 0 : Collections.max(inFlightForClient);
|
||||
int nextPacketId = (maxId + 1) % 0xFFFF;
|
||||
inFlightForClient.add(nextPacketId);
|
||||
return nextPacketId;
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
//remove from the ids store
|
||||
Set<Integer> inFlightForClient = this.m_inFlightIds.get(clientID);
|
||||
if (inFlightForClient != null) {
|
||||
inFlightForClient.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) {
|
||||
List<String> guids = Utils.defaultGet(m_enqueuedStore, clientID, new ArrayList<String>());
|
||||
guids.add(guid);
|
||||
m_enqueuedStore.put(clientID, guids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> enqueued(String clientID) {
|
||||
return Utils.defaultGet(m_enqueuedStore, clientID, new ArrayList<String>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEnqueued(String clientID, String guid) {
|
||||
List<String> guids = Utils.defaultGet(m_enqueuedStore, clientID, new ArrayList<String>());
|
||||
guids.remove(guid);
|
||||
m_enqueuedStore.put(clientID, guids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void secondPhaseAcknowledged(String clientID, int messageID) {
|
||||
Set<Integer> messageIDs = Utils.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 = Utils.defaultGet(m_secondPhaseStore, clientID, new HashSet<Integer>());
|
||||
messageIDs.add(messageID);
|
||||
m_secondPhaseStore.put(clientID, messageIDs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String mapToGuid(String clientID, int messageID) {
|
||||
ConcurrentMap<Integer, String> messageIdToGuid = m_db.getHashMap(messageId2GuidsMapName(clientID));
|
||||
return messageIdToGuid.get(messageID);
|
||||
}
|
||||
|
||||
static String messageId2GuidsMapName(String clientID) {
|
||||
return "guidsMapping_" + clientID;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2012-2015 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.persistence;
|
||||
|
||||
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.impl.Utils;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
import io.moquette.spi.persistence.MapDBPersistentStore.PersistentSession;
|
||||
import org.mapdb.DB;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* ISessionsStore implementation backed by MapDB.
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
class MapDBSessionsStore implements ISessionsStore {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MapDBSessionsStore.class);
|
||||
|
||||
//maps clientID->[MessageId -> guid]
|
||||
private ConcurrentMap<String, Map<Integer, String>> m_inflightStore;
|
||||
//map clientID <-> set of currently in flight packet identifiers
|
||||
private Map<String, Set<Integer>> m_inFlightIds;
|
||||
private ConcurrentMap<String, PersistentSession> m_persistentSessions;
|
||||
//maps clientID->[guid*], insertion order cares, it's queue
|
||||
private ConcurrentMap<String, List<String>> m_enqueuedStore;
|
||||
//maps clientID->[messageID*]
|
||||
private ConcurrentMap<String, Set<Integer>> m_secondPhaseStore;
|
||||
|
||||
private final DB m_db;
|
||||
private final IMessagesStore m_messagesStore;
|
||||
|
||||
MapDBSessionsStore(DB db, IMessagesStore messagesStore) {
|
||||
m_db = db;
|
||||
m_messagesStore = messagesStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initStore() {
|
||||
m_inflightStore = m_db.getHashMap("inflight");
|
||||
m_inFlightIds = m_db.getHashMap("inflightPacketIDs");
|
||||
m_persistentSessions = m_db.getHashMap("sessions");
|
||||
m_enqueuedStore = m_db.getHashMap("sessionQueue");
|
||||
m_secondPhaseStore = m_db.getHashMap("secondPhase");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNewSubscription(Subscription newSubscription) {
|
||||
LOG.debug("addNewSubscription invoked with subscription {}", newSubscription);
|
||||
final String clientID = newSubscription.getClientId();
|
||||
m_db.getHashMap("subscriptions_" + clientID).put(newSubscription.getTopicFilter(), newSubscription);
|
||||
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("subscriptions_{}: {}", clientID, m_db.getHashMap("subscriptions_" + clientID));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSubscription(String topicFilter, String clientID) {
|
||||
LOG.debug("removeSubscription topic filter: {} for clientID: {}", topicFilter, clientID);
|
||||
if (!m_db.exists("subscriptions_" + clientID)) {
|
||||
return;
|
||||
}
|
||||
m_db.getHashMap("subscriptions_" + clientID).remove(topicFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void wipeSubscriptions(String clientID) {
|
||||
LOG.debug("wipeSubscriptions");
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("Subscription pre wipe: subscriptions_{}: {}", clientID, m_db.getHashMap("subscriptions_" + clientID));
|
||||
}
|
||||
m_db.delete("subscriptions_" + clientID);
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("Subscription post wipe: subscriptions_{}: {}", clientID, m_db.getHashMap("subscriptions_" + clientID));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ClientTopicCouple> listAllSubscriptions() {
|
||||
final List<ClientTopicCouple> allSubscriptions = new ArrayList<>();
|
||||
for (String clientID : m_persistentSessions.keySet()) {
|
||||
ConcurrentMap<String, Subscription> clientSubscriptions = m_db.getHashMap("subscriptions_" + clientID);
|
||||
for (String topicFilter : clientSubscriptions.keySet()) {
|
||||
allSubscriptions.add(new ClientTopicCouple(clientID, topicFilter));
|
||||
}
|
||||
}
|
||||
LOG.debug("retrieveAllSubscriptions returning subs {}", allSubscriptions);
|
||||
return allSubscriptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Subscription getSubscription(ClientTopicCouple couple) {
|
||||
ConcurrentMap<String, Subscription> clientSubscriptions = m_db.getHashMap("subscriptions_" + couple.clientID);
|
||||
LOG.debug("subscriptions_{}: {}", couple.clientID, clientSubscriptions);
|
||||
return clientSubscriptions.get(couple.topicFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(String clientID) {
|
||||
return m_db.exists("subscriptions_" + clientID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSession createNewSession(String clientID, boolean cleanSession) {
|
||||
LOG.debug("createNewSession for client <{}> with clean flag <{}>", clientID, cleanSession);
|
||||
if (m_persistentSessions.containsKey(clientID)) {
|
||||
LOG.error("already exists a session for client <{}>, bad condition", clientID);
|
||||
throw new IllegalArgumentException("Can't create a session with the ID of an already existing" + clientID);
|
||||
}
|
||||
LOG.debug("clientID {} is a newcome, creating it's empty subscriptions set", clientID);
|
||||
m_persistentSessions.putIfAbsent(clientID, new PersistentSession(cleanSession));
|
||||
return new ClientSession(clientID, m_messagesStore, this, cleanSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSession sessionForClient(String clientID) {
|
||||
if (!m_persistentSessions.containsKey(clientID)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PersistentSession storedSession = m_persistentSessions.get(clientID);
|
||||
return new ClientSession(clientID, m_messagesStore, this, storedSession.cleanSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCleanStatus(String clientID, boolean cleanSession) {
|
||||
m_persistentSessions.put(clientID, new MapDBPersistentStore.PersistentSession(cleanSession));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next valid packetIdentifier for the given client session.
|
||||
* */
|
||||
@Override
|
||||
public int nextPacketID(String clientID) {
|
||||
Set<Integer> inFlightForClient = this.m_inFlightIds.get(clientID);
|
||||
if (inFlightForClient == null) {
|
||||
int nextPacketId = 1;
|
||||
inFlightForClient = new HashSet<>();
|
||||
inFlightForClient.add(nextPacketId);
|
||||
this.m_inFlightIds.put(clientID, inFlightForClient);
|
||||
return nextPacketId;
|
||||
|
||||
}
|
||||
|
||||
int maxId = inFlightForClient.isEmpty() ? 0 : Collections.max(inFlightForClient);
|
||||
int nextPacketId = (maxId + 1) % 0xFFFF;
|
||||
inFlightForClient.add(nextPacketId);
|
||||
return nextPacketId;
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
//remove from the ids store
|
||||
Set<Integer> inFlightForClient = this.m_inFlightIds.get(clientID);
|
||||
if (inFlightForClient != null) {
|
||||
inFlightForClient.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) {
|
||||
List<String> guids = Utils.defaultGet(m_enqueuedStore, clientID, new ArrayList<String>());
|
||||
guids.add(guid);
|
||||
m_enqueuedStore.put(clientID, guids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> enqueued(String clientID) {
|
||||
return Utils.defaultGet(m_enqueuedStore, clientID, new ArrayList<String>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEnqueued(String clientID, String guid) {
|
||||
List<String> guids = Utils.defaultGet(m_enqueuedStore, clientID, new ArrayList<String>());
|
||||
guids.remove(guid);
|
||||
m_enqueuedStore.put(clientID, guids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void secondPhaseAcknowledged(String clientID, int messageID) {
|
||||
Set<Integer> messageIDs = Utils.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 = Utils.defaultGet(m_secondPhaseStore, clientID, new HashSet<Integer>());
|
||||
messageIDs.add(messageID);
|
||||
m_secondPhaseStore.put(clientID, messageIDs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String mapToGuid(String clientID, int messageID) {
|
||||
ConcurrentMap<Integer, String> messageIdToGuid = m_db.getHashMap(messageId2GuidsMapName(clientID));
|
||||
return messageIdToGuid.get(messageID);
|
||||
}
|
||||
|
||||
static String messageId2GuidsMapName(String clientID) {
|
||||
return "guidsMapping_" + clientID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoredMessage getInflightMessage( String clientID, int messageID ) {
|
||||
Map<Integer, String> clientEntries = m_inflightStore.get( clientID );
|
||||
if( clientEntries == null )
|
||||
return null;
|
||||
String guid = clientEntries.get( messageID );
|
||||
if( guid == null )
|
||||
return null;
|
||||
return m_messagesStore.getMessageByGuid( guid );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,120 +1,125 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 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;
|
||||
|
||||
import io.moquette.interception.messages.*;
|
||||
import io.moquette.parser.proto.messages.ConnectMessage;
|
||||
import io.moquette.interception.InterceptHandler;
|
||||
import io.moquette.parser.proto.messages.AbstractMessage;
|
||||
import io.moquette.parser.proto.messages.PublishMessage;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Wagner Macedo
|
||||
*/
|
||||
public class BrokerInterceptorTest {
|
||||
// value to check for changes after every notification
|
||||
private static final AtomicInteger n = new AtomicInteger(0);
|
||||
|
||||
// Interceptor loaded with a custom InterceptHandler special for the tests
|
||||
private static final class MockObserver implements InterceptHandler {
|
||||
@Override
|
||||
public void onConnect(InterceptConnectMessage msg) {
|
||||
n.set(40);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnect(InterceptDisconnectMessage msg) {
|
||||
n.set(50);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPublish(InterceptPublishMessage msg) {
|
||||
n.set(60);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSubscribe(InterceptSubscribeMessage msg) {
|
||||
n.set(70);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnsubscribe(InterceptUnsubscribeMessage msg) {
|
||||
n.set(80);
|
||||
}
|
||||
}
|
||||
|
||||
private static final BrokerInterceptor interceptor = new BrokerInterceptor(Arrays.<InterceptHandler>asList(new MockObserver()));
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeAllTests() {
|
||||
// check if any of the handler methods was called before notifications
|
||||
assertEquals(0, n.get());
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterAllTests() {
|
||||
interceptor.stop();
|
||||
}
|
||||
|
||||
/* Used to wait handler notification by the interceptor internal thread */
|
||||
private static void interval() throws InterruptedException {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyClientConnected() throws Exception {
|
||||
interceptor.notifyClientConnected(new ConnectMessage());
|
||||
interval();
|
||||
assertEquals(40, n.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyClientDisconnected() throws Exception {
|
||||
interceptor.notifyClientDisconnected("cli1234");
|
||||
interval();
|
||||
assertEquals(50, n.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyTopicPublished() throws Exception {
|
||||
interceptor.notifyTopicPublished(new PublishMessage(), "cli1234");
|
||||
interval();
|
||||
assertEquals(60, n.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyTopicSubscribed() throws Exception {
|
||||
interceptor.notifyTopicSubscribed(new Subscription("cli1", "o2", AbstractMessage.QOSType.MOST_ONE));
|
||||
interval();
|
||||
assertEquals(70, n.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyTopicUnsubscribed() throws Exception {
|
||||
interceptor.notifyTopicUnsubscribed("o2", "cli1234");
|
||||
interval();
|
||||
assertEquals(80, n.get());
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2012-2015 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;
|
||||
|
||||
import io.moquette.interception.messages.*;
|
||||
import io.moquette.parser.proto.messages.ConnectMessage;
|
||||
import io.moquette.interception.InterceptHandler;
|
||||
import io.moquette.parser.proto.messages.AbstractMessage;
|
||||
import io.moquette.parser.proto.messages.PublishMessage;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Wagner Macedo
|
||||
*/
|
||||
public class BrokerInterceptorTest {
|
||||
// value to check for changes after every notification
|
||||
private static final AtomicInteger n = new AtomicInteger(0);
|
||||
|
||||
// Interceptor loaded with a custom InterceptHandler special for the tests
|
||||
private static final class MockObserver implements InterceptHandler {
|
||||
@Override
|
||||
public void onConnect(InterceptConnectMessage msg) {
|
||||
n.set(40);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnect(InterceptDisconnectMessage msg) {
|
||||
n.set(50);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPublish(InterceptPublishMessage msg) {
|
||||
n.set(60);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSubscribe(InterceptSubscribeMessage msg) {
|
||||
n.set(70);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnsubscribe(InterceptUnsubscribeMessage msg) {
|
||||
n.set(80);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessageConsumed( InterceptConsumedMessage msg ) {
|
||||
n.set(90);
|
||||
}
|
||||
}
|
||||
|
||||
private static final BrokerInterceptor interceptor = new BrokerInterceptor(Arrays.<InterceptHandler>asList(new MockObserver()));
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeAllTests() {
|
||||
// check if any of the handler methods was called before notifications
|
||||
assertEquals(0, n.get());
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterAllTests() {
|
||||
interceptor.stop();
|
||||
}
|
||||
|
||||
/* Used to wait handler notification by the interceptor internal thread */
|
||||
private static void interval() throws InterruptedException {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyClientConnected() throws Exception {
|
||||
interceptor.notifyClientConnected(new ConnectMessage());
|
||||
interval();
|
||||
assertEquals(40, n.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyClientDisconnected() throws Exception {
|
||||
interceptor.notifyClientDisconnected("cli1234");
|
||||
interval();
|
||||
assertEquals(50, n.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyTopicPublished() throws Exception {
|
||||
interceptor.notifyTopicPublished(new PublishMessage(), "cli1234");
|
||||
interval();
|
||||
assertEquals(60, n.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyTopicSubscribed() throws Exception {
|
||||
interceptor.notifyTopicSubscribed(new Subscription("cli1", "o2", AbstractMessage.QOSType.MOST_ONE));
|
||||
interval();
|
||||
assertEquals(70, n.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyTopicUnsubscribed() throws Exception {
|
||||
interceptor.notifyTopicUnsubscribed("o2", "cli1234");
|
||||
interval();
|
||||
assertEquals(80, n.get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,235 +1,241 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2015 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.persistence;
|
||||
|
||||
import io.moquette.spi.ClientSession;
|
||||
import io.moquette.spi.IMessagesStore;
|
||||
import io.moquette.spi.ISessionsStore;
|
||||
import io.moquette.spi.impl.Utils;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class MemorySessionStore implements ISessionsStore {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MemorySessionStore.class);
|
||||
|
||||
private Map<String, Set<Subscription>> m_persistentSubscriptions = new HashMap<>();
|
||||
|
||||
private Map<String, MapDBPersistentStore.PersistentSession> m_persistentSessions = new HashMap<>();
|
||||
|
||||
//maps clientID->[MessageId -> guid]
|
||||
private Map<String, Map<Integer, String>> m_inflightStore = new HashMap<>();
|
||||
// private Map<String, Set<Integer>> m_inflightIDs = new HashMap<>();
|
||||
//maps clientID->[guid*]
|
||||
private Map<String, Set<String>> m_enqueuedStore = new HashMap<>();
|
||||
//maps clientID->[messageID*]
|
||||
private Map<String, Set<Integer>> m_secondPhaseStore = new HashMap<>();
|
||||
|
||||
private Map<String, Map<Integer, String>> m_messageToGuids;
|
||||
private final IMessagesStore m_messagesStore;
|
||||
|
||||
public MemorySessionStore(IMessagesStore messagesStore, Map<String, Map<Integer, String>> messageToGuids) {
|
||||
m_messageToGuids = messageToGuids;
|
||||
this.m_messagesStore = messagesStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSubscription(String topic, String clientID) {
|
||||
LOG.debug("removeSubscription topic filter: {} for clientID: {}", topic, clientID);
|
||||
if (!m_persistentSubscriptions.containsKey(clientID)) {
|
||||
return;
|
||||
}
|
||||
Set<Subscription> clientSubscriptions = m_persistentSubscriptions.get(clientID);
|
||||
//search for the subscription to remove
|
||||
Subscription toBeRemoved = null;
|
||||
for (Subscription sub : clientSubscriptions) {
|
||||
if (sub.getTopicFilter().equals(topic)) {
|
||||
toBeRemoved = sub;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (toBeRemoved != null) {
|
||||
clientSubscriptions.remove(toBeRemoved);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initStore() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNewSubscription(Subscription newSubscription) {
|
||||
final String clientID = newSubscription.getClientId();
|
||||
if (!m_persistentSubscriptions.containsKey(clientID)) {
|
||||
m_persistentSubscriptions.put(clientID, new HashSet<Subscription>());
|
||||
}
|
||||
|
||||
Set<Subscription> subs = m_persistentSubscriptions.get(clientID);
|
||||
subs.remove(newSubscription); //same topic and clientID
|
||||
subs.add(newSubscription);
|
||||
m_persistentSubscriptions.put(clientID, subs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void wipeSubscriptions(String clientID) {
|
||||
m_persistentSubscriptions.remove(clientID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(String clientID) {
|
||||
return m_persistentSubscriptions.containsKey(clientID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSession createNewSession(String clientID, boolean cleanSession) {
|
||||
LOG.debug("createNewSession for client <{}>", clientID);
|
||||
if (m_persistentSessions.containsKey(clientID)) {
|
||||
LOG.error("already exists a session for client <{}>, bad condition", clientID);
|
||||
throw new IllegalArgumentException("Can't create a session with the ID of an already existing" + clientID);
|
||||
}
|
||||
LOG.debug("clientID {} is a newcome, creating it's empty subscriptions set", clientID);
|
||||
m_persistentSubscriptions.put(clientID, new HashSet<Subscription>());
|
||||
m_persistentSessions.put(clientID, new MapDBPersistentStore.PersistentSession(cleanSession));
|
||||
return new ClientSession(clientID, m_messagesStore, this, cleanSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSession sessionForClient(String clientID) {
|
||||
if (!m_persistentSessions.containsKey(clientID)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MapDBPersistentStore.PersistentSession storedSession = m_persistentSessions.get(clientID);
|
||||
return new ClientSession(clientID, m_messagesStore, this, storedSession.cleanSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCleanStatus(String clientID, boolean cleanSession) {
|
||||
m_persistentSessions.put(clientID, new MapDBPersistentStore.PersistentSession(cleanSession));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ClientTopicCouple> listAllSubscriptions() {
|
||||
List<ClientTopicCouple> allSubscriptions = new ArrayList<>();
|
||||
for (Map.Entry<String, Set<Subscription>> entry : m_persistentSubscriptions.entrySet()) {
|
||||
for (Subscription sub : entry.getValue()) {
|
||||
allSubscriptions.add(sub.asClientTopicCouple());
|
||||
}
|
||||
}
|
||||
return allSubscriptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Subscription getSubscription(ClientTopicCouple couple) {
|
||||
Set<Subscription> subscriptions = m_persistentSubscriptions.get(couple.clientID);
|
||||
if (subscriptions == null || subscriptions.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
for (Subscription sub : subscriptions) {
|
||||
if (sub.getTopicFilter().equals(couple.topicFilter)) {
|
||||
return sub;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next valid packetIdentifier for the given client session.
|
||||
* */
|
||||
@Override
|
||||
public int nextPacketID(String clientID) {
|
||||
Map<Integer, String> m = this.m_inflightStore.get(clientID);
|
||||
if (m == null) {
|
||||
m = new HashMap<>();
|
||||
int nextPacketId = 1;
|
||||
m.put(nextPacketId, null);
|
||||
return nextPacketId;
|
||||
}
|
||||
int maxId = Collections.max(m.keySet());
|
||||
int nextPacketId = (maxId + 1) % 0xFFFF;
|
||||
m.put(nextPacketId, null);
|
||||
return nextPacketId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindToDeliver(String guid, String clientID) {
|
||||
Set<String> guids = Utils.defaultGet(m_enqueuedStore, clientID, new HashSet<String>());
|
||||
guids.add(guid);
|
||||
m_enqueuedStore.put(clientID, guids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> enqueued(String clientID) {
|
||||
return Utils.defaultGet(m_enqueuedStore, clientID, new HashSet<String>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEnqueued(String clientID, String guid) {
|
||||
Set<String> guids = Utils.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 = Utils.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 = Utils.defaultGet(m_secondPhaseStore, clientID, new HashSet<Integer>());
|
||||
messageIDs.add(messageID);
|
||||
m_secondPhaseStore.put(clientID, messageIDs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String mapToGuid(String clientID, int messageID) {
|
||||
HashMap<Integer, String> guids = (HashMap<Integer, String>) Utils.defaultGet(m_messageToGuids,
|
||||
clientID, new HashMap<Integer, String>());
|
||||
return guids.get(messageID);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2012-2015 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.persistence;
|
||||
|
||||
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.impl.Utils;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class MemorySessionStore implements ISessionsStore {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MemorySessionStore.class);
|
||||
|
||||
private Map<String, Set<Subscription>> m_persistentSubscriptions = new HashMap<>();
|
||||
|
||||
private Map<String, MapDBPersistentStore.PersistentSession> m_persistentSessions = new HashMap<>();
|
||||
|
||||
//maps clientID->[MessageId -> guid]
|
||||
private Map<String, Map<Integer, String>> m_inflightStore = new HashMap<>();
|
||||
// private Map<String, Set<Integer>> m_inflightIDs = new HashMap<>();
|
||||
//maps clientID->[guid*]
|
||||
private Map<String, Set<String>> m_enqueuedStore = new HashMap<>();
|
||||
//maps clientID->[messageID*]
|
||||
private Map<String, Set<Integer>> m_secondPhaseStore = new HashMap<>();
|
||||
|
||||
private Map<String, Map<Integer, String>> m_messageToGuids;
|
||||
private final IMessagesStore m_messagesStore;
|
||||
|
||||
public MemorySessionStore(IMessagesStore messagesStore, Map<String, Map<Integer, String>> messageToGuids) {
|
||||
m_messageToGuids = messageToGuids;
|
||||
this.m_messagesStore = messagesStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSubscription(String topic, String clientID) {
|
||||
LOG.debug("removeSubscription topic filter: {} for clientID: {}", topic, clientID);
|
||||
if (!m_persistentSubscriptions.containsKey(clientID)) {
|
||||
return;
|
||||
}
|
||||
Set<Subscription> clientSubscriptions = m_persistentSubscriptions.get(clientID);
|
||||
//search for the subscription to remove
|
||||
Subscription toBeRemoved = null;
|
||||
for (Subscription sub : clientSubscriptions) {
|
||||
if (sub.getTopicFilter().equals(topic)) {
|
||||
toBeRemoved = sub;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (toBeRemoved != null) {
|
||||
clientSubscriptions.remove(toBeRemoved);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initStore() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNewSubscription(Subscription newSubscription) {
|
||||
final String clientID = newSubscription.getClientId();
|
||||
if (!m_persistentSubscriptions.containsKey(clientID)) {
|
||||
m_persistentSubscriptions.put(clientID, new HashSet<Subscription>());
|
||||
}
|
||||
|
||||
Set<Subscription> subs = m_persistentSubscriptions.get(clientID);
|
||||
subs.remove(newSubscription); //same topic and clientID
|
||||
subs.add(newSubscription);
|
||||
m_persistentSubscriptions.put(clientID, subs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void wipeSubscriptions(String clientID) {
|
||||
m_persistentSubscriptions.remove(clientID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(String clientID) {
|
||||
return m_persistentSubscriptions.containsKey(clientID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSession createNewSession(String clientID, boolean cleanSession) {
|
||||
LOG.debug("createNewSession for client <{}>", clientID);
|
||||
if (m_persistentSessions.containsKey(clientID)) {
|
||||
LOG.error("already exists a session for client <{}>, bad condition", clientID);
|
||||
throw new IllegalArgumentException("Can't create a session with the ID of an already existing" + clientID);
|
||||
}
|
||||
LOG.debug("clientID {} is a newcome, creating it's empty subscriptions set", clientID);
|
||||
m_persistentSubscriptions.put(clientID, new HashSet<Subscription>());
|
||||
m_persistentSessions.put(clientID, new MapDBPersistentStore.PersistentSession(cleanSession));
|
||||
return new ClientSession(clientID, m_messagesStore, this, cleanSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSession sessionForClient(String clientID) {
|
||||
if (!m_persistentSessions.containsKey(clientID)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MapDBPersistentStore.PersistentSession storedSession = m_persistentSessions.get(clientID);
|
||||
return new ClientSession(clientID, m_messagesStore, this, storedSession.cleanSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCleanStatus(String clientID, boolean cleanSession) {
|
||||
m_persistentSessions.put(clientID, new MapDBPersistentStore.PersistentSession(cleanSession));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ClientTopicCouple> listAllSubscriptions() {
|
||||
List<ClientTopicCouple> allSubscriptions = new ArrayList<>();
|
||||
for (Map.Entry<String, Set<Subscription>> entry : m_persistentSubscriptions.entrySet()) {
|
||||
for (Subscription sub : entry.getValue()) {
|
||||
allSubscriptions.add(sub.asClientTopicCouple());
|
||||
}
|
||||
}
|
||||
return allSubscriptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Subscription getSubscription(ClientTopicCouple couple) {
|
||||
Set<Subscription> subscriptions = m_persistentSubscriptions.get(couple.clientID);
|
||||
if (subscriptions == null || subscriptions.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
for (Subscription sub : subscriptions) {
|
||||
if (sub.getTopicFilter().equals(couple.topicFilter)) {
|
||||
return sub;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next valid packetIdentifier for the given client session.
|
||||
* */
|
||||
@Override
|
||||
public int nextPacketID(String clientID) {
|
||||
Map<Integer, String> m = this.m_inflightStore.get(clientID);
|
||||
if (m == null) {
|
||||
m = new HashMap<>();
|
||||
int nextPacketId = 1;
|
||||
m.put(nextPacketId, null);
|
||||
return nextPacketId;
|
||||
}
|
||||
int maxId = Collections.max(m.keySet());
|
||||
int nextPacketId = (maxId + 1) % 0xFFFF;
|
||||
m.put(nextPacketId, null);
|
||||
return nextPacketId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindToDeliver(String guid, String clientID) {
|
||||
Set<String> guids = Utils.defaultGet(m_enqueuedStore, clientID, new HashSet<String>());
|
||||
guids.add(guid);
|
||||
m_enqueuedStore.put(clientID, guids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> enqueued(String clientID) {
|
||||
return Utils.defaultGet(m_enqueuedStore, clientID, new HashSet<String>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEnqueued(String clientID, String guid) {
|
||||
Set<String> guids = Utils.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 = Utils.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 = Utils.defaultGet(m_secondPhaseStore, clientID, new HashSet<Integer>());
|
||||
messageIDs.add(messageID);
|
||||
m_secondPhaseStore.put(clientID, messageIDs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String mapToGuid(String clientID, int messageID) {
|
||||
HashMap<Integer, String> guids = (HashMap<Integer, String>) Utils.defaultGet(m_messageToGuids,
|
||||
clientID, new HashMap<Integer, String>());
|
||||
return guids.get(messageID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoredMessage getInflightMessage( String clientID, int messageID ) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user