Added simple implementation for handle subscribe message
This commit is contained in:
@@ -1,11 +1,13 @@
|
|||||||
package org.dna.mqtt.moquette.messaging.spi;
|
package org.dna.mqtt.moquette.messaging.spi;
|
||||||
|
|
||||||
|
import org.dna.mqtt.moquette.proto.messages.AbstractMessage.QOSType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface to the underling messaging system used to publish, subscribe.
|
* Interface to the underling messaging system used to publish, subscribe.
|
||||||
*
|
*
|
||||||
* @author andrea
|
* @author andrea
|
||||||
*/
|
*/
|
||||||
public class IMessaging {
|
public interface IMessaging {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Publish a message on the messaging system. The qos is the level of quality
|
* Publish a message on the messaging system. The qos is the level of quality
|
||||||
@@ -13,8 +15,10 @@ public class IMessaging {
|
|||||||
* notification also after all the current subscriber to the given topic has
|
* notification also after all the current subscriber to the given topic has
|
||||||
* been notified.
|
* been notified.
|
||||||
*/
|
*/
|
||||||
public void publish(String topic, Object message, byte qos, boolean retain) {
|
void publish(String topic, Object message, byte qos, boolean retain) ;
|
||||||
throw new UnsupportedOperationException("Not yet implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe a client to a specified topic with a defined level
|
||||||
|
*/
|
||||||
|
void subscribe(String clientId, String topic, QOSType qos);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package org.dna.mqtt.moquette.messaging.spi.impl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.dna.mqtt.moquette.messaging.spi.IMessaging;
|
||||||
|
import org.dna.mqtt.moquette.proto.messages.AbstractMessage.QOSType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author andrea
|
||||||
|
*/
|
||||||
|
public class SimpleMessaging implements IMessaging {
|
||||||
|
|
||||||
|
private List<Subscription> subscriptions = new ArrayList<Subscription>();
|
||||||
|
|
||||||
|
public void publish(String topic, Object message, byte qos, boolean retain) {
|
||||||
|
throw new UnsupportedOperationException("Not supported yet.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void subscribe(String clientId, String topic, QOSType qos) {
|
||||||
|
subscriptions.add(new Subscription(clientId, topic, qos));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<Subscription> getSubscriptions() {
|
||||||
|
return subscriptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package org.dna.mqtt.moquette.messaging.spi.impl;
|
||||||
|
|
||||||
|
import org.dna.mqtt.moquette.proto.messages.AbstractMessage.QOSType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maintain the information about which Topic a certain ClientID is subscribed
|
||||||
|
* and at which QoS
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author andrea
|
||||||
|
*/
|
||||||
|
public class Subscription {
|
||||||
|
|
||||||
|
QOSType requestedQos;
|
||||||
|
String clientId;
|
||||||
|
String topic;
|
||||||
|
|
||||||
|
public Subscription(String clientId, String topic, QOSType requestedQos) {
|
||||||
|
this.requestedQos = requestedQos;
|
||||||
|
this.clientId = clientId;
|
||||||
|
this.topic = topic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientId() {
|
||||||
|
return clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public QOSType getRequestedQos() {
|
||||||
|
return requestedQos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTopic() {
|
||||||
|
return topic;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final Subscription other = (Subscription) obj;
|
||||||
|
if (this.requestedQos != other.requestedQos) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((this.clientId == null) ? (other.clientId != null) : !this.clientId.equals(other.clientId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((this.topic == null) ? (other.topic != null) : !this.topic.equals(other.topic)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int hash = 3;
|
||||||
|
hash = 37 * hash + (this.requestedQos != null ? this.requestedQos.hashCode() : 0);
|
||||||
|
hash = 37 * hash + (this.clientId != null ? this.clientId.hashCode() : 0);
|
||||||
|
hash = 37 * hash + (this.topic != null ? this.topic.hashCode() : 0);
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package org.dna.mqtt.moquette.server;
|
||||||
|
|
||||||
|
import org.apache.mina.core.session.IoSession;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maintains the information of single connection, like ClientID, IoSession,
|
||||||
|
* and other connection related flags.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author andrea
|
||||||
|
*/
|
||||||
|
public class ConnectionDescriptor {
|
||||||
|
|
||||||
|
private String m_clientID;
|
||||||
|
private IoSession m_session;
|
||||||
|
private boolean m_cleanSession;
|
||||||
|
|
||||||
|
public ConnectionDescriptor(String clientID, IoSession session, boolean cleanSession) {
|
||||||
|
this.m_clientID = clientID;
|
||||||
|
this.m_session = session;
|
||||||
|
this.m_cleanSession = cleanSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCleanSession() {
|
||||||
|
return m_cleanSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientID() {
|
||||||
|
return m_clientID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IoSession getSession() {
|
||||||
|
return m_session;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,8 @@ import org.apache.mina.core.session.IdleStatus;
|
|||||||
import org.apache.mina.core.session.IoSession;
|
import org.apache.mina.core.session.IoSession;
|
||||||
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
|
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
|
||||||
import org.dna.mqtt.moquette.proto.messages.ConnAckMessage;
|
import org.dna.mqtt.moquette.proto.messages.ConnAckMessage;
|
||||||
|
import org.dna.mqtt.moquette.proto.messages.SubAckMessage;
|
||||||
|
import org.dna.mqtt.moquette.proto.messages.SubscribeMessage;
|
||||||
import static org.dna.mqtt.moquette.proto.messages.AbstractMessage.*;
|
import static org.dna.mqtt.moquette.proto.messages.AbstractMessage.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,11 +19,12 @@ import static org.dna.mqtt.moquette.proto.messages.AbstractMessage.*;
|
|||||||
* @author andrea
|
* @author andrea
|
||||||
*/
|
*/
|
||||||
public class MQTTHandler extends IoHandlerAdapter {
|
public class MQTTHandler extends IoHandlerAdapter {
|
||||||
|
protected static final String ATTR_CLIENTID = "ClientID";
|
||||||
|
|
||||||
private static final Logger LOG = Logger.getLogger(MQTTHandler.class.getName());
|
private static final Logger LOG = Logger.getLogger(MQTTHandler.class.getName());
|
||||||
|
|
||||||
/** Maps CLIENT_ID to the IoSession that represents the connection*/
|
/** Maps CLIENT_ID to the IoSession that represents the connection*/
|
||||||
Map<String, IoSession> m_clientIDs = new HashMap<String, IoSession>();
|
Map<String, ConnectionDescriptor> m_clientIDs = new HashMap<String, ConnectionDescriptor>();
|
||||||
private IMessaging m_messaging;
|
private IMessaging m_messaging;
|
||||||
private IAuthenticator m_authenticator;
|
private IAuthenticator m_authenticator;
|
||||||
|
|
||||||
@@ -32,6 +35,8 @@ public class MQTTHandler extends IoHandlerAdapter {
|
|||||||
switch (msg.getMessageType()) {
|
switch (msg.getMessageType()) {
|
||||||
case CONNECT:
|
case CONNECT:
|
||||||
handleConnect(session, (ConnectMessage) msg);
|
handleConnect(session, (ConnectMessage) msg);
|
||||||
|
case SUBSCRIBE:
|
||||||
|
handleSubscribe(session, (SubscribeMessage) msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,13 +58,17 @@ public class MQTTHandler extends IoHandlerAdapter {
|
|||||||
|
|
||||||
//if an old client with the same ID already exists close its session.
|
//if an old client with the same ID already exists close its session.
|
||||||
if (m_clientIDs.containsKey(msg.getClientID())) {
|
if (m_clientIDs.containsKey(msg.getClientID())) {
|
||||||
m_clientIDs.get(msg.getClientID()).close(false);
|
m_clientIDs.get(msg.getClientID()).getSession().close(false);
|
||||||
}
|
}
|
||||||
m_clientIDs.put(msg.getClientID(), session);
|
|
||||||
|
ConnectionDescriptor connDescr = new ConnectionDescriptor(msg.getClientID(), session, msg.isCleanSession());
|
||||||
|
m_clientIDs.put(msg.getClientID(), connDescr);
|
||||||
|
|
||||||
int keepAlive = msg.getKeepAlive();
|
int keepAlive = msg.getKeepAlive();
|
||||||
session.setAttribute("keepAlive", keepAlive);
|
session.setAttribute("keepAlive", keepAlive);
|
||||||
session.setAttribute("cleanSession", msg.isCleanSession());
|
session.setAttribute("cleanSession", msg.isCleanSession());
|
||||||
|
//used to track the client in the subscription and publishing phases.
|
||||||
|
session.setAttribute(ATTR_CLIENTID, msg.getClientID());
|
||||||
|
|
||||||
session.getConfig().setIdleTime(IdleStatus.READER_IDLE, Math.round(keepAlive * 1.5f));
|
session.getConfig().setIdleTime(IdleStatus.READER_IDLE, Math.round(keepAlive * 1.5f));
|
||||||
|
|
||||||
@@ -89,6 +98,23 @@ public class MQTTHandler extends IoHandlerAdapter {
|
|||||||
session.write(okResp);
|
session.write(okResp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void handleSubscribe(IoSession session, SubscribeMessage msg) {
|
||||||
|
for(SubscribeMessage.Couple req : msg.subscriptions()) {
|
||||||
|
m_messaging.subscribe((String) session.getAttribute(ATTR_CLIENTID),
|
||||||
|
req.getTopic(), AbstractMessage.QOSType.values()[req.getQos()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
//ack the client
|
||||||
|
SubAckMessage ackMessage = new SubAckMessage();
|
||||||
|
ackMessage.setMessageID(msg.getMessageID());
|
||||||
|
|
||||||
|
//TODO by now it handles only QoS 0 messages
|
||||||
|
for (int i=0; i < msg.subscriptions().size(); i++) {
|
||||||
|
ackMessage.addType(QOSType.MOST_ONE);
|
||||||
|
}
|
||||||
|
session.write(ackMessage);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sessionIdle(IoSession session, IdleStatus status) {
|
public void sessionIdle(IoSession session, IdleStatus status) {
|
||||||
session.close(false);
|
session.close(false);
|
||||||
|
|||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
package org.dna.mqtt.moquette.messaging.spi.impl;
|
||||||
|
|
||||||
|
import org.dna.mqtt.moquette.proto.messages.AbstractMessage.QOSType;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author andrea
|
||||||
|
*/
|
||||||
|
public class SimpleMessagingTest {
|
||||||
|
|
||||||
|
// public SimpleMessagingTest() {
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @BeforeClass
|
||||||
|
// public static void setUpClass() throws Exception {
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @AfterClass
|
||||||
|
// public static void tearDownClass() throws Exception {
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Before
|
||||||
|
// public void setUp() {
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @After
|
||||||
|
// public void tearDown() {
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSubscribe() {
|
||||||
|
String clientID = "FAKE_123";
|
||||||
|
String topic = "/news";
|
||||||
|
SimpleMessaging messaging = new SimpleMessaging();
|
||||||
|
|
||||||
|
//Exercise
|
||||||
|
messaging.subscribe(clientID, topic, QOSType.MOST_ONE);
|
||||||
|
|
||||||
|
//Verify
|
||||||
|
assertTrue(messaging.getSubscriptions().contains(new Subscription(clientID, topic, QOSType.MOST_ONE)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package org.dna.mqtt.moquette.server;
|
package org.dna.mqtt.moquette.server;
|
||||||
|
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
|
||||||
import org.apache.mina.core.write.WriteRequest;
|
import org.apache.mina.core.write.WriteRequest;
|
||||||
import org.dna.mqtt.moquette.proto.messages.ConnAckMessage;
|
import org.dna.mqtt.moquette.proto.messages.ConnAckMessage;
|
||||||
import org.apache.mina.core.filterchain.IoFilterAdapter;
|
import org.apache.mina.core.filterchain.IoFilterAdapter;
|
||||||
@@ -7,6 +9,7 @@ import org.apache.mina.core.session.DummySession;
|
|||||||
import org.apache.mina.core.session.IoSession;
|
import org.apache.mina.core.session.IoSession;
|
||||||
import org.dna.mqtt.moquette.messaging.spi.IMessaging;
|
import org.dna.mqtt.moquette.messaging.spi.IMessaging;
|
||||||
import org.dna.mqtt.moquette.proto.messages.ConnectMessage;
|
import org.dna.mqtt.moquette.proto.messages.ConnectMessage;
|
||||||
|
import org.dna.mqtt.moquette.proto.messages.SubscribeMessage;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
@@ -84,4 +87,27 @@ public class MQTTHandlerTest {
|
|||||||
assertEquals(ConnAckMessage.CONNECTION_ACCEPTED, m_returnCode);
|
assertEquals(ConnAckMessage.CONNECTION_ACCEPTED, m_returnCode);
|
||||||
verify(mockedMessaging).publish(eq("topic"), eq("Topic message"), anyByte(), anyBoolean());
|
verify(mockedMessaging).publish(eq("topic"), eq("Topic message"), anyByte(), anyBoolean());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHandleSubscribe() {
|
||||||
|
String topicName = "/news";
|
||||||
|
SubscribeMessage subscribeMsg = new SubscribeMessage();
|
||||||
|
subscribeMsg.setMessageID(0x555);
|
||||||
|
subscribeMsg.addSubscription(new SubscribeMessage.Couple(
|
||||||
|
(byte)AbstractMessage.QOSType.EXACTLY_ONCE.ordinal(), topicName));
|
||||||
|
IMessaging mockedMessaging = mock(IMessaging.class);
|
||||||
|
|
||||||
|
m_session.setAttribute(MQTTHandler.ATTR_CLIENTID, "fakeID");
|
||||||
|
|
||||||
|
|
||||||
|
//Exercise
|
||||||
|
m_handler.setMessaging(mockedMessaging);
|
||||||
|
m_handler.handleSubscribe(m_session, subscribeMsg);
|
||||||
|
|
||||||
|
//Verify
|
||||||
|
ArgumentCaptor<AbstractMessage.QOSType> argument = ArgumentCaptor.forClass(AbstractMessage.QOSType.class);
|
||||||
|
verify(mockedMessaging).subscribe(eq("fakeID"), eq(topicName), argument.capture());
|
||||||
|
assertEquals(AbstractMessage.QOSType.EXACTLY_ONCE, argument.getValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user