mirror of https://github.com/apache/activemq.git
Adds a subscription strategy model where the default is the normal durable topic subscription based approach or a strategy that maps all subscriptions and publish operations to a Virtual Topic model. A network of brokers can network the Queues instead of having the durable topics subscriptions repaeted on each Broker.
This commit is contained in:
parent
fff3c83971
commit
413e4840d6
|
@ -70,9 +70,12 @@ public class MQTTInactivityMonitor extends TransportFilter {
|
||||||
int currentCounter = next.getReceiveCounter();
|
int currentCounter = next.getReceiveCounter();
|
||||||
int previousCounter = lastReceiveCounter.getAndSet(currentCounter);
|
int previousCounter = lastReceiveCounter.getAndSet(currentCounter);
|
||||||
|
|
||||||
// for the PINGREQ/RESP frames, the currentCounter will be different from previousCounter, and that
|
// for the PINGREQ/RESP frames, the currentCounter will be different
|
||||||
// should be sufficient to indicate the connection is still alive. If there were random data, or something
|
// from previousCounter, and that
|
||||||
// outside the scope of the spec, the wire format unrmarshalling would fail, so we don't need to handle
|
// should be sufficient to indicate the connection is still alive.
|
||||||
|
// If there were random data, or something
|
||||||
|
// outside the scope of the spec, the wire format unrmarshalling
|
||||||
|
// would fail, so we don't need to handle
|
||||||
// PINGREQ/RESP explicitly here
|
// PINGREQ/RESP explicitly here
|
||||||
if (inReceive.get() || currentCounter != previousCounter) {
|
if (inReceive.get() || currentCounter != previousCounter) {
|
||||||
if (LOG.isTraceEnabled()) {
|
if (LOG.isTraceEnabled()) {
|
||||||
|
@ -82,24 +85,21 @@ public class MQTTInactivityMonitor extends TransportFilter {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( (now-lastReceiveTime) >= readKeepAliveTime+readGraceTime && monitorStarted.get() && !ASYNC_TASKS.isTerminating()) {
|
if ((now - lastReceiveTime) >= readKeepAliveTime + readGraceTime && monitorStarted.get() && !ASYNC_TASKS.isTerminating()) {
|
||||||
if (LOG.isDebugEnabled()) {
|
if (LOG.isDebugEnabled()) {
|
||||||
LOG.debug("No message received since last read check for " + MQTTInactivityMonitor.this.toString() + "! Throwing InactivityIOException.");
|
LOG.debug("No message received since last read check for " + MQTTInactivityMonitor.this.toString() + "! Throwing InactivityIOException.");
|
||||||
}
|
}
|
||||||
ASYNC_TASKS.execute(new Runnable() {
|
ASYNC_TASKS.execute(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
onException(new InactivityIOException("Channel was inactive for too (>" + (readKeepAliveTime+readGraceTime) + ") long: " + next.getRemoteAddress()));
|
onException(new InactivityIOException("Channel was inactive for too (>" + (readKeepAliveTime + readGraceTime) + ") long: "
|
||||||
|
+ next.getRemoteAddress()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private boolean allowReadCheck(long elapsed) {
|
|
||||||
return elapsed > (readGraceTime * 9 / 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MQTTInactivityMonitor(Transport next, WireFormat wireFormat) {
|
public MQTTInactivityMonitor(Transport next, WireFormat wireFormat) {
|
||||||
super(next);
|
super(next);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ import java.util.Map;
|
||||||
|
|
||||||
import javax.net.ServerSocketFactory;
|
import javax.net.ServerSocketFactory;
|
||||||
import javax.net.SocketFactory;
|
import javax.net.SocketFactory;
|
||||||
import org.apache.activemq.broker.BrokerContext;
|
|
||||||
import org.apache.activemq.broker.BrokerService;
|
import org.apache.activemq.broker.BrokerService;
|
||||||
import org.apache.activemq.broker.BrokerServiceAware;
|
import org.apache.activemq.broker.BrokerServiceAware;
|
||||||
import org.apache.activemq.transport.MutexTransport;
|
import org.apache.activemq.transport.MutexTransport;
|
||||||
|
@ -44,12 +44,15 @@ public class MQTTNIOTransportFactory extends NIOTransportFactory implements Brok
|
||||||
|
|
||||||
private BrokerService brokerService = null;
|
private BrokerService brokerService = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
protected String getDefaultWireFormatType() {
|
protected String getDefaultWireFormatType() {
|
||||||
return "mqtt";
|
return "mqtt";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected TcpTransportServer createTcpTransportServer(URI location, ServerSocketFactory serverSocketFactory) throws IOException, URISyntaxException {
|
protected TcpTransportServer createTcpTransportServer(URI location, ServerSocketFactory serverSocketFactory) throws IOException, URISyntaxException {
|
||||||
TcpTransportServer result = new TcpTransportServer(this, location, serverSocketFactory) {
|
TcpTransportServer result = new TcpTransportServer(this, location, serverSocketFactory) {
|
||||||
|
@Override
|
||||||
protected Transport createTransport(Socket socket, WireFormat format) throws IOException {
|
protected Transport createTransport(Socket socket, WireFormat format) throws IOException {
|
||||||
return new MQTTNIOTransport(format, socket);
|
return new MQTTNIOTransport(format, socket);
|
||||||
}
|
}
|
||||||
|
@ -58,6 +61,7 @@ public class MQTTNIOTransportFactory extends NIOTransportFactory implements Brok
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected TcpTransport createTcpTransport(WireFormat wf, SocketFactory socketFactory, URI location, URI localLocation) throws UnknownHostException, IOException {
|
protected TcpTransport createTcpTransport(WireFormat wf, SocketFactory socketFactory, URI location, URI localLocation) throws UnknownHostException, IOException {
|
||||||
return new MQTTNIOTransport(wf, socketFactory, location, localLocation);
|
return new MQTTNIOTransport(wf, socketFactory, location, localLocation);
|
||||||
}
|
}
|
||||||
|
@ -75,6 +79,7 @@ public class MQTTNIOTransportFactory extends NIOTransportFactory implements Brok
|
||||||
return transport;
|
return transport;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
public Transport compositeConfigure(Transport transport, WireFormat format, Map options) {
|
public Transport compositeConfigure(Transport transport, WireFormat format, Map options) {
|
||||||
transport = new MQTTTransportFilter(transport, format, brokerService);
|
transport = new MQTTTransportFilter(transport, format, brokerService);
|
||||||
|
@ -82,16 +87,17 @@ public class MQTTNIOTransportFactory extends NIOTransportFactory implements Brok
|
||||||
return super.compositeConfigure(transport, format, options);
|
return super.compositeConfigure(transport, format, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void setBrokerService(BrokerService brokerService) {
|
public void setBrokerService(BrokerService brokerService) {
|
||||||
this.brokerService = brokerService;
|
this.brokerService = brokerService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected Transport createInactivityMonitor(Transport transport, WireFormat format) {
|
protected Transport createInactivityMonitor(Transport transport, WireFormat format) {
|
||||||
MQTTInactivityMonitor monitor = new MQTTInactivityMonitor(transport, format);
|
MQTTInactivityMonitor monitor = new MQTTInactivityMonitor(transport, format);
|
||||||
MQTTTransportFilter filter = transport.narrow(MQTTTransportFilter.class);
|
MQTTTransportFilter filter = transport.narrow(MQTTTransportFilter.class);
|
||||||
filter.setInactivityMonitor(monitor);
|
filter.setInactivityMonitor(monitor);
|
||||||
return monitor;
|
return monitor;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,11 +17,7 @@
|
||||||
package org.apache.activemq.transport.mqtt;
|
package org.apache.activemq.transport.mqtt;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.zip.DataFormatException;
|
import java.util.zip.DataFormatException;
|
||||||
|
@ -34,19 +30,13 @@ import javax.jms.Message;
|
||||||
import javax.security.auth.login.CredentialException;
|
import javax.security.auth.login.CredentialException;
|
||||||
|
|
||||||
import org.apache.activemq.broker.BrokerService;
|
import org.apache.activemq.broker.BrokerService;
|
||||||
import org.apache.activemq.broker.ConnectionContext;
|
import org.apache.activemq.broker.BrokerServiceAware;
|
||||||
import org.apache.activemq.broker.region.PrefetchSubscription;
|
|
||||||
import org.apache.activemq.broker.region.RegionBroker;
|
|
||||||
import org.apache.activemq.broker.region.Subscription;
|
|
||||||
import org.apache.activemq.broker.region.TopicRegion;
|
|
||||||
import org.apache.activemq.broker.region.policy.RetainedMessageSubscriptionRecoveryPolicy;
|
import org.apache.activemq.broker.region.policy.RetainedMessageSubscriptionRecoveryPolicy;
|
||||||
import org.apache.activemq.broker.region.virtual.VirtualTopicInterceptor;
|
|
||||||
import org.apache.activemq.command.ActiveMQBytesMessage;
|
import org.apache.activemq.command.ActiveMQBytesMessage;
|
||||||
import org.apache.activemq.command.ActiveMQDestination;
|
import org.apache.activemq.command.ActiveMQDestination;
|
||||||
import org.apache.activemq.command.ActiveMQMapMessage;
|
import org.apache.activemq.command.ActiveMQMapMessage;
|
||||||
import org.apache.activemq.command.ActiveMQMessage;
|
import org.apache.activemq.command.ActiveMQMessage;
|
||||||
import org.apache.activemq.command.ActiveMQTextMessage;
|
import org.apache.activemq.command.ActiveMQTextMessage;
|
||||||
import org.apache.activemq.command.ActiveMQTopic;
|
|
||||||
import org.apache.activemq.command.Command;
|
import org.apache.activemq.command.Command;
|
||||||
import org.apache.activemq.command.ConnectionError;
|
import org.apache.activemq.command.ConnectionError;
|
||||||
import org.apache.activemq.command.ConnectionId;
|
import org.apache.activemq.command.ConnectionId;
|
||||||
|
@ -60,17 +50,17 @@ import org.apache.activemq.command.MessageId;
|
||||||
import org.apache.activemq.command.ProducerId;
|
import org.apache.activemq.command.ProducerId;
|
||||||
import org.apache.activemq.command.ProducerInfo;
|
import org.apache.activemq.command.ProducerInfo;
|
||||||
import org.apache.activemq.command.RemoveInfo;
|
import org.apache.activemq.command.RemoveInfo;
|
||||||
import org.apache.activemq.command.RemoveSubscriptionInfo;
|
|
||||||
import org.apache.activemq.command.Response;
|
import org.apache.activemq.command.Response;
|
||||||
import org.apache.activemq.command.SessionId;
|
import org.apache.activemq.command.SessionId;
|
||||||
import org.apache.activemq.command.SessionInfo;
|
import org.apache.activemq.command.SessionInfo;
|
||||||
import org.apache.activemq.command.ShutdownInfo;
|
import org.apache.activemq.command.ShutdownInfo;
|
||||||
import org.apache.activemq.command.SubscriptionInfo;
|
import org.apache.activemq.transport.mqtt.strategy.MQTTSubscriptionStrategy;
|
||||||
import org.apache.activemq.store.PersistenceAdapterSupport;
|
|
||||||
import org.apache.activemq.util.ByteArrayOutputStream;
|
import org.apache.activemq.util.ByteArrayOutputStream;
|
||||||
import org.apache.activemq.util.ByteSequence;
|
import org.apache.activemq.util.ByteSequence;
|
||||||
|
import org.apache.activemq.util.FactoryFinder;
|
||||||
import org.apache.activemq.util.IOExceptionSupport;
|
import org.apache.activemq.util.IOExceptionSupport;
|
||||||
import org.apache.activemq.util.IdGenerator;
|
import org.apache.activemq.util.IdGenerator;
|
||||||
|
import org.apache.activemq.util.JMSExceptionSupport;
|
||||||
import org.apache.activemq.util.LRUCache;
|
import org.apache.activemq.util.LRUCache;
|
||||||
import org.apache.activemq.util.LongSequenceGenerator;
|
import org.apache.activemq.util.LongSequenceGenerator;
|
||||||
import org.fusesource.hawtbuf.Buffer;
|
import org.fusesource.hawtbuf.Buffer;
|
||||||
|
@ -114,9 +104,8 @@ public class MQTTProtocolConverter {
|
||||||
private final ConcurrentHashMap<Integer, ResponseHandler> resposeHandlers = new ConcurrentHashMap<Integer, ResponseHandler>();
|
private final ConcurrentHashMap<Integer, ResponseHandler> resposeHandlers = new ConcurrentHashMap<Integer, ResponseHandler>();
|
||||||
private final ConcurrentHashMap<ConsumerId, MQTTSubscription> subscriptionsByConsumerId = new ConcurrentHashMap<ConsumerId, MQTTSubscription>();
|
private final ConcurrentHashMap<ConsumerId, MQTTSubscription> subscriptionsByConsumerId = new ConcurrentHashMap<ConsumerId, MQTTSubscription>();
|
||||||
private final ConcurrentHashMap<String, MQTTSubscription> mqttSubscriptionByTopic = new ConcurrentHashMap<String, MQTTSubscription>();
|
private final ConcurrentHashMap<String, MQTTSubscription> mqttSubscriptionByTopic = new ConcurrentHashMap<String, MQTTSubscription>();
|
||||||
private final Map<String, ActiveMQTopic> activeMQTopicMap = new LRUCache<String, ActiveMQTopic>(DEFAULT_CACHE_SIZE);
|
private final Map<String, ActiveMQDestination> activeMQDestinationMap = new LRUCache<String, ActiveMQDestination>(DEFAULT_CACHE_SIZE);
|
||||||
private final Map<Destination, String> mqttTopicMap = new LRUCache<Destination, String>(DEFAULT_CACHE_SIZE);
|
private final Map<Destination, String> mqttTopicMap = new LRUCache<Destination, String>(DEFAULT_CACHE_SIZE);
|
||||||
private final Set<String> restoredSubs = Collections.synchronizedSet(new HashSet<String>());
|
|
||||||
|
|
||||||
private final Map<Short, MessageAck> consumerAcks = new LRUCache<Short, MessageAck>(DEFAULT_CACHE_SIZE);
|
private final Map<Short, MessageAck> consumerAcks = new LRUCache<Short, MessageAck>(DEFAULT_CACHE_SIZE);
|
||||||
private final Map<Short, PUBREC> publisherRecs = new LRUCache<Short, PUBREC>(DEFAULT_CACHE_SIZE);
|
private final Map<Short, PUBREC> publisherRecs = new LRUCache<Short, PUBREC>(DEFAULT_CACHE_SIZE);
|
||||||
|
@ -136,6 +125,15 @@ public class MQTTProtocolConverter {
|
||||||
private final MQTTPacketIdGenerator packetIdGenerator;
|
private final MQTTPacketIdGenerator packetIdGenerator;
|
||||||
private boolean publishDollarTopics;
|
private boolean publishDollarTopics;
|
||||||
|
|
||||||
|
private final FactoryFinder STRATAGY_FINDER = new FactoryFinder("META-INF/services/org/apache/activemq/transport/strategies/");
|
||||||
|
/*
|
||||||
|
* Subscription strategy configuration element.
|
||||||
|
* > mqtt-default-subscriptions
|
||||||
|
* > mqtt-virtual-topic-subscriptions
|
||||||
|
*/
|
||||||
|
private String subscriptionStrategyName = "mqtt-default-subscriptions";
|
||||||
|
private MQTTSubscriptionStrategy subsciptionStrategy;
|
||||||
|
|
||||||
public MQTTProtocolConverter(MQTTTransport mqttTransport, BrokerService brokerService) {
|
public MQTTProtocolConverter(MQTTTransport mqttTransport, BrokerService brokerService) {
|
||||||
this.mqttTransport = mqttTransport;
|
this.mqttTransport = mqttTransport;
|
||||||
this.brokerService = brokerService;
|
this.brokerService = brokerService;
|
||||||
|
@ -149,22 +147,26 @@ public class MQTTProtocolConverter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendToActiveMQ(Command command, ResponseHandler handler) {
|
public void sendToActiveMQ(Command command, ResponseHandler handler) {
|
||||||
|
|
||||||
// Lets intercept message send requests..
|
// Lets intercept message send requests..
|
||||||
if (command instanceof ActiveMQMessage) {
|
if (command instanceof ActiveMQMessage) {
|
||||||
ActiveMQMessage msg = (ActiveMQMessage) command;
|
ActiveMQMessage msg = (ActiveMQMessage) command;
|
||||||
if (!getPublishDollarTopics() && msg.getDestination().getPhysicalName().startsWith("$")) {
|
try {
|
||||||
// We don't allow users to send to $ prefixed topics to avoid failing MQTT 3.1.1
|
if (!getPublishDollarTopics() && getSubscriptionStrategy().isControlTopic(msg.getDestination())) {
|
||||||
// specification requirements
|
// We don't allow users to send to $ prefixed topics to avoid failing MQTT 3.1.1
|
||||||
if (handler != null) {
|
// specification requirements for system assigned destinations.
|
||||||
try {
|
if (handler != null) {
|
||||||
handler.onResponse(this, new Response());
|
try {
|
||||||
} catch (IOException e) {
|
handler.onResponse(this, new Response());
|
||||||
e.printStackTrace();
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return;
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,54 +191,43 @@ public class MQTTProtocolConverter {
|
||||||
*/
|
*/
|
||||||
public void onMQTTCommand(MQTTFrame frame) throws IOException, JMSException {
|
public void onMQTTCommand(MQTTFrame frame) throws IOException, JMSException {
|
||||||
switch (frame.messageType()) {
|
switch (frame.messageType()) {
|
||||||
case PINGREQ.TYPE: {
|
case PINGREQ.TYPE:
|
||||||
LOG.debug("Received a ping from client: " + getClientId());
|
LOG.debug("Received a ping from client: " + getClientId());
|
||||||
sendToMQTT(PING_RESP_FRAME);
|
sendToMQTT(PING_RESP_FRAME);
|
||||||
LOG.debug("Sent Ping Response to " + getClientId());
|
LOG.debug("Sent Ping Response to " + getClientId());
|
||||||
break;
|
break;
|
||||||
}
|
case CONNECT.TYPE:
|
||||||
case CONNECT.TYPE: {
|
|
||||||
CONNECT connect = new CONNECT().decode(frame);
|
CONNECT connect = new CONNECT().decode(frame);
|
||||||
onMQTTConnect(connect);
|
onMQTTConnect(connect);
|
||||||
LOG.debug("MQTT Client {} connected. (version: {})", getClientId(), connect.version());
|
LOG.debug("MQTT Client {} connected. (version: {})", getClientId(), connect.version());
|
||||||
break;
|
break;
|
||||||
}
|
case DISCONNECT.TYPE:
|
||||||
case DISCONNECT.TYPE: {
|
|
||||||
LOG.debug("MQTT Client {} disconnecting", getClientId());
|
LOG.debug("MQTT Client {} disconnecting", getClientId());
|
||||||
onMQTTDisconnect();
|
onMQTTDisconnect();
|
||||||
break;
|
break;
|
||||||
}
|
case SUBSCRIBE.TYPE:
|
||||||
case SUBSCRIBE.TYPE: {
|
|
||||||
onSubscribe(new SUBSCRIBE().decode(frame));
|
onSubscribe(new SUBSCRIBE().decode(frame));
|
||||||
break;
|
break;
|
||||||
}
|
case UNSUBSCRIBE.TYPE:
|
||||||
case UNSUBSCRIBE.TYPE: {
|
|
||||||
onUnSubscribe(new UNSUBSCRIBE().decode(frame));
|
onUnSubscribe(new UNSUBSCRIBE().decode(frame));
|
||||||
break;
|
break;
|
||||||
}
|
case PUBLISH.TYPE:
|
||||||
case PUBLISH.TYPE: {
|
|
||||||
onMQTTPublish(new PUBLISH().decode(frame));
|
onMQTTPublish(new PUBLISH().decode(frame));
|
||||||
break;
|
break;
|
||||||
}
|
case PUBACK.TYPE:
|
||||||
case PUBACK.TYPE: {
|
|
||||||
onMQTTPubAck(new PUBACK().decode(frame));
|
onMQTTPubAck(new PUBACK().decode(frame));
|
||||||
break;
|
break;
|
||||||
}
|
case PUBREC.TYPE:
|
||||||
case PUBREC.TYPE: {
|
|
||||||
onMQTTPubRec(new PUBREC().decode(frame));
|
onMQTTPubRec(new PUBREC().decode(frame));
|
||||||
break;
|
break;
|
||||||
}
|
case PUBREL.TYPE:
|
||||||
case PUBREL.TYPE: {
|
|
||||||
onMQTTPubRel(new PUBREL().decode(frame));
|
onMQTTPubRel(new PUBREL().decode(frame));
|
||||||
break;
|
break;
|
||||||
}
|
case PUBCOMP.TYPE:
|
||||||
case PUBCOMP.TYPE: {
|
|
||||||
onMQTTPubComp(new PUBCOMP().decode(frame));
|
onMQTTPubComp(new PUBCOMP().decode(frame));
|
||||||
break;
|
break;
|
||||||
}
|
default:
|
||||||
default: {
|
|
||||||
handleException(new MQTTProtocolException("Unknown MQTTFrame type: " + frame.messageType(), true), frame);
|
handleException(new MQTTProtocolException("Unknown MQTTFrame type: " + frame.messageType(), true), frame);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,54 +323,19 @@ public class MQTTProtocolConverter {
|
||||||
connected.set(true);
|
connected.set(true);
|
||||||
getMQTTTransport().sendToMQTT(ack.encode());
|
getMQTTTransport().sendToMQTT(ack.encode());
|
||||||
|
|
||||||
List<SubscriptionInfo> subs = PersistenceAdapterSupport.listSubscriptions(brokerService.getPersistenceAdapter(), connectionInfo.getClientId());
|
|
||||||
if (connect.cleanSession()) {
|
if (connect.cleanSession()) {
|
||||||
packetIdGenerator.stopClientSession(getClientId());
|
packetIdGenerator.stopClientSession(getClientId());
|
||||||
deleteDurableSubs(subs);
|
|
||||||
} else {
|
} else {
|
||||||
packetIdGenerator.startClientSession(getClientId());
|
packetIdGenerator.startClientSession(getClientId());
|
||||||
restoreDurableSubs(subs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSubscriptionStrategy().onConnect(connect);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteDurableSubs(List<SubscriptionInfo> subs) {
|
|
||||||
try {
|
|
||||||
for (SubscriptionInfo sub : subs) {
|
|
||||||
RemoveSubscriptionInfo rsi = new RemoveSubscriptionInfo();
|
|
||||||
rsi.setConnectionId(connectionId);
|
|
||||||
rsi.setSubscriptionName(sub.getSubcriptionName());
|
|
||||||
rsi.setClientId(sub.getClientId());
|
|
||||||
sendToActiveMQ(rsi, new ResponseHandler() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(MQTTProtocolConverter converter, Response response) throws IOException {
|
|
||||||
// ignore failures..
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (Throwable e) {
|
|
||||||
LOG.warn("Could not delete the MQTT durable subs.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void restoreDurableSubs(List<SubscriptionInfo> subs) {
|
|
||||||
try {
|
|
||||||
for (SubscriptionInfo sub : subs) {
|
|
||||||
String name = sub.getSubcriptionName();
|
|
||||||
String[] split = name.split(":", 2);
|
|
||||||
QoS qoS = QoS.valueOf(split[0]);
|
|
||||||
onSubscribe(new Topic(split[1], qoS));
|
|
||||||
// mark this durable subscription as restored by Broker
|
|
||||||
restoredSubs.add(split[1]);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.warn("Could not restore the MQTT durable subs.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onMQTTDisconnect() throws MQTTProtocolException {
|
void onMQTTDisconnect() throws MQTTProtocolException {
|
||||||
if (connected.get()) {
|
if (connected.get()) {
|
||||||
connected.set(false);
|
connected.set(false);
|
||||||
|
@ -408,42 +364,41 @@ public class MQTTProtocolConverter {
|
||||||
} else {
|
} else {
|
||||||
LOG.warn("No topics defined for Subscription " + command);
|
LOG.warn("No topics defined for Subscription " + command);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
byte onSubscribe(final Topic topic) throws MQTTProtocolException {
|
public byte onSubscribe(final Topic topic) throws MQTTProtocolException {
|
||||||
|
|
||||||
final String topicName = topic.name().toString();
|
final String destinationName = topic.name().toString();
|
||||||
final QoS topicQoS = topic.qos();
|
final QoS requestedQoS = topic.qos();
|
||||||
|
|
||||||
if (mqttSubscriptionByTopic.containsKey(topicName)) {
|
if (mqttSubscriptionByTopic.containsKey(destinationName)) {
|
||||||
final MQTTSubscription mqttSubscription = mqttSubscriptionByTopic.get(topicName);
|
final MQTTSubscription mqttSubscription = mqttSubscriptionByTopic.get(destinationName);
|
||||||
if (topicQoS != mqttSubscription.qos()) {
|
if (requestedQoS != mqttSubscription.getQoS()) {
|
||||||
// remove old subscription as the QoS has changed
|
// remove old subscription as the QoS has changed
|
||||||
onUnSubscribe(topicName);
|
onUnSubscribe(destinationName);
|
||||||
} else {
|
} else {
|
||||||
// duplicate SUBSCRIBE packet, find all matching topics and re-send retained messages
|
try {
|
||||||
resendRetainedMessages(mqttSubscription);
|
getSubscriptionStrategy().onReSubscribe(mqttSubscription);
|
||||||
return (byte) topicQoS.ordinal();
|
} catch (IOException e) {
|
||||||
|
throw new MQTTProtocolException("Failed to find subscription strategy", true, e);
|
||||||
|
}
|
||||||
|
return (byte) requestedQoS.ordinal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ActiveMQDestination destination = new ActiveMQTopic(MQTTProtocolSupport.convertMQTTToActiveMQ(topicName));
|
try {
|
||||||
|
return getSubscriptionStrategy().onSubscribe(destinationName, requestedQoS);
|
||||||
ConsumerId id = new ConsumerId(sessionId, consumerIdGenerator.getNextSequenceId());
|
} catch (IOException e) {
|
||||||
ConsumerInfo consumerInfo = new ConsumerInfo(id);
|
throw new MQTTProtocolException("Failed while intercepting subscribe", true, e);
|
||||||
consumerInfo.setDestination(destination);
|
|
||||||
consumerInfo.setPrefetchSize(getActiveMQSubscriptionPrefetch());
|
|
||||||
consumerInfo.setRetroactive(true);
|
|
||||||
consumerInfo.setDispatchAsync(true);
|
|
||||||
// create durable subscriptions only when cleansession is false
|
|
||||||
if (!connect.cleanSession() && connect.clientId() != null && topicQoS.ordinal() >= QoS.AT_LEAST_ONCE.ordinal()) {
|
|
||||||
consumerInfo.setSubscriptionName(topicQoS + ":" + topicName);
|
|
||||||
}
|
}
|
||||||
MQTTSubscription mqttSubscription = new MQTTSubscription(this, topicName, topicQoS, consumerInfo);
|
}
|
||||||
|
|
||||||
|
public byte doSubscribe(ConsumerInfo consumerInfo, final String topicName, final QoS qoS) throws MQTTProtocolException {
|
||||||
|
|
||||||
|
MQTTSubscription mqttSubscription = new MQTTSubscription(this, topicName, qoS, consumerInfo);
|
||||||
|
|
||||||
// optimistic add to local maps first to be able to handle commands in onActiveMQCommand
|
// optimistic add to local maps first to be able to handle commands in onActiveMQCommand
|
||||||
subscriptionsByConsumerId.put(id, mqttSubscription);
|
subscriptionsByConsumerId.put(consumerInfo.getConsumerId(), mqttSubscription);
|
||||||
mqttSubscriptionByTopic.put(topicName, mqttSubscription);
|
mqttSubscriptionByTopic.put(topicName, mqttSubscription);
|
||||||
|
|
||||||
final byte[] qos = {-1};
|
final byte[] qos = {-1};
|
||||||
|
@ -453,79 +408,24 @@ public class MQTTProtocolConverter {
|
||||||
// validate subscription request
|
// validate subscription request
|
||||||
if (response.isException()) {
|
if (response.isException()) {
|
||||||
final Throwable throwable = ((ExceptionResponse) response).getException();
|
final Throwable throwable = ((ExceptionResponse) response).getException();
|
||||||
LOG.warn("Error subscribing to " + topicName, throwable);
|
LOG.warn("Error subscribing to {}", topicName, throwable);
|
||||||
qos[0] = SUBSCRIBE_ERROR;
|
qos[0] = SUBSCRIBE_ERROR;
|
||||||
} else {
|
} else {
|
||||||
qos[0] = (byte) topicQoS.ordinal();
|
qos[0] = (byte) qoS.ordinal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (qos[0] == SUBSCRIBE_ERROR) {
|
if (qos[0] == SUBSCRIBE_ERROR) {
|
||||||
// remove from local maps if subscribe failed
|
// remove from local maps if subscribe failed
|
||||||
subscriptionsByConsumerId.remove(id);
|
subscriptionsByConsumerId.remove(consumerInfo.getConsumerId());
|
||||||
mqttSubscriptionByTopic.remove(topicName);
|
mqttSubscriptionByTopic.remove(topicName);
|
||||||
}
|
}
|
||||||
|
|
||||||
return qos[0];
|
return qos[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resendRetainedMessages(MQTTSubscription mqttSubscription) throws MQTTProtocolException {
|
public void onUnSubscribe(UNSUBSCRIBE command) throws MQTTProtocolException {
|
||||||
|
|
||||||
ActiveMQDestination destination = mqttSubscription.getDestination();
|
|
||||||
|
|
||||||
// check whether the Topic has been recovered in restoreDurableSubs
|
|
||||||
// mark subscription available for recovery for duplicate subscription
|
|
||||||
if (restoredSubs.remove(destination.getPhysicalName())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String topicName = mqttSubscription.getTopicName();
|
|
||||||
// get TopicRegion
|
|
||||||
RegionBroker regionBroker;
|
|
||||||
try {
|
|
||||||
regionBroker = (RegionBroker) brokerService.getBroker().getAdaptor(RegionBroker.class);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new MQTTProtocolException("Error subscribing to " + topicName + ": " + e.getMessage(), false, e);
|
|
||||||
}
|
|
||||||
final TopicRegion topicRegion = (TopicRegion) regionBroker.getTopicRegion();
|
|
||||||
|
|
||||||
final ConsumerInfo consumerInfo = mqttSubscription.getConsumerInfo();
|
|
||||||
final ConsumerId consumerId = consumerInfo.getConsumerId();
|
|
||||||
|
|
||||||
// use actual client id used to create connection to lookup connection context
|
|
||||||
final String connectionInfoClientId = connectionInfo.getClientId();
|
|
||||||
final ConnectionContext connectionContext = regionBroker.getConnectionContext(connectionInfoClientId);
|
|
||||||
|
|
||||||
// get all matching Topics
|
|
||||||
final Set<org.apache.activemq.broker.region.Destination> matchingDestinations = topicRegion.getDestinations(destination);
|
|
||||||
for (org.apache.activemq.broker.region.Destination dest : matchingDestinations) {
|
|
||||||
|
|
||||||
// recover retroactive messages for matching subscription
|
|
||||||
for (Subscription subscription : dest.getConsumers()) {
|
|
||||||
if (subscription.getConsumerInfo().getConsumerId().equals(consumerId)) {
|
|
||||||
try {
|
|
||||||
if (dest instanceof org.apache.activemq.broker.region.Topic) {
|
|
||||||
((org.apache.activemq.broker.region.Topic)dest).recoverRetroactiveMessages(connectionContext, subscription);
|
|
||||||
} else if (dest instanceof VirtualTopicInterceptor) {
|
|
||||||
((VirtualTopicInterceptor)dest).getTopic().recoverRetroactiveMessages(connectionContext, subscription);
|
|
||||||
}
|
|
||||||
if (subscription instanceof PrefetchSubscription) {
|
|
||||||
// request dispatch for prefetch subs
|
|
||||||
PrefetchSubscription prefetchSubscription = (PrefetchSubscription) subscription;
|
|
||||||
prefetchSubscription.dispatchPending();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new MQTTProtocolException("Error recovering retained messages for " +
|
|
||||||
dest.getName() + ": " + e.getMessage(), false, e);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onUnSubscribe(UNSUBSCRIBE command) throws MQTTProtocolException {
|
|
||||||
checkConnected();
|
checkConnected();
|
||||||
UTF8Buffer[] topics = command.topics();
|
UTF8Buffer[] topics = command.topics();
|
||||||
if (topics != null) {
|
if (topics != null) {
|
||||||
|
@ -538,33 +438,38 @@ public class MQTTProtocolConverter {
|
||||||
sendToMQTT(ack.encode());
|
sendToMQTT(ack.encode());
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUnSubscribe(String topicName) {
|
public void onUnSubscribe(String topicName) {
|
||||||
MQTTSubscription subs = mqttSubscriptionByTopic.remove(topicName);
|
MQTTSubscription subscription = mqttSubscriptionByTopic.remove(topicName);
|
||||||
if (subs != null) {
|
if (subscription != null) {
|
||||||
ConsumerInfo info = subs.getConsumerInfo();
|
doUnSubscribe(subscription);
|
||||||
if (info != null) {
|
|
||||||
subscriptionsByConsumerId.remove(info.getConsumerId());
|
|
||||||
}
|
|
||||||
RemoveInfo removeInfo = null;
|
|
||||||
if (info != null) {
|
|
||||||
removeInfo = info.createRemoveCommand();
|
|
||||||
}
|
|
||||||
sendToActiveMQ(removeInfo, null);
|
|
||||||
|
|
||||||
// check if the durable sub also needs to be removed
|
// check if the broker side of the subscription needs to be removed
|
||||||
if (subs.getConsumerInfo().getSubscriptionName() != null) {
|
try {
|
||||||
// also remove it from restored durable subscriptions set
|
getSubscriptionStrategy().onUnSubscribe(subscription);
|
||||||
restoredSubs.remove(MQTTProtocolSupport.convertMQTTToActiveMQ(topicName));
|
} catch (IOException e) {
|
||||||
|
// Ignore
|
||||||
RemoveSubscriptionInfo rsi = new RemoveSubscriptionInfo();
|
|
||||||
rsi.setConnectionId(connectionId);
|
|
||||||
rsi.setSubscriptionName(subs.getConsumerInfo().getSubscriptionName());
|
|
||||||
rsi.setClientId(connectionInfo.getClientId());
|
|
||||||
sendToActiveMQ(rsi, null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void doUnSubscribe(MQTTSubscription subscription) {
|
||||||
|
mqttSubscriptionByTopic.remove(subscription.getTopicName());
|
||||||
|
ConsumerInfo info = subscription.getConsumerInfo();
|
||||||
|
if (info != null) {
|
||||||
|
subscriptionsByConsumerId.remove(info.getConsumerId());
|
||||||
|
}
|
||||||
|
RemoveInfo removeInfo = null;
|
||||||
|
if (info != null) {
|
||||||
|
removeInfo = info.createRemoveCommand();
|
||||||
|
}
|
||||||
|
sendToActiveMQ(removeInfo, new ResponseHandler() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(MQTTProtocolConverter converter, Response response) throws IOException {
|
||||||
|
// ignore failures..
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatch an ActiveMQ command
|
* Dispatch an ActiveMQ command
|
||||||
*/
|
*/
|
||||||
|
@ -610,7 +515,7 @@ public class MQTTProtocolConverter {
|
||||||
} else if (command.isBrokerInfo()) {
|
} else if (command.isBrokerInfo()) {
|
||||||
//ignore
|
//ignore
|
||||||
} else {
|
} else {
|
||||||
LOG.debug("Do not know how to process ActiveMQ Command " + command);
|
LOG.debug("Do not know how to process ActiveMQ Command {}", command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -647,7 +552,7 @@ public class MQTTProtocolConverter {
|
||||||
ack = publisherRecs.remove(command.messageId());
|
ack = publisherRecs.remove(command.messageId());
|
||||||
}
|
}
|
||||||
if (ack == null) {
|
if (ack == null) {
|
||||||
LOG.warn("Unknown PUBREL: " + command.messageId() + " received");
|
LOG.warn("Unknown PUBREL: {} received", command.messageId());
|
||||||
}
|
}
|
||||||
PUBCOMP pubcomp = new PUBCOMP();
|
PUBCOMP pubcomp = new PUBCOMP();
|
||||||
pubcomp.messageId(command.messageId());
|
pubcomp.messageId(command.messageId());
|
||||||
|
@ -680,16 +585,23 @@ public class MQTTProtocolConverter {
|
||||||
msg.setBooleanProperty(RetainedMessageSubscriptionRecoveryPolicy.RETAIN_PROPERTY, true);
|
msg.setBooleanProperty(RetainedMessageSubscriptionRecoveryPolicy.RETAIN_PROPERTY, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
ActiveMQTopic topic;
|
ActiveMQDestination destination;
|
||||||
synchronized (activeMQTopicMap) {
|
synchronized (activeMQDestinationMap) {
|
||||||
topic = activeMQTopicMap.get(command.topicName());
|
destination = activeMQDestinationMap.get(command.topicName());
|
||||||
if (topic == null) {
|
if (destination == null) {
|
||||||
String topicName = MQTTProtocolSupport.convertMQTTToActiveMQ(command.topicName().toString());
|
String topicName = MQTTProtocolSupport.convertMQTTToActiveMQ(command.topicName().toString());
|
||||||
topic = new ActiveMQTopic(topicName);
|
|
||||||
activeMQTopicMap.put(command.topicName().toString(), topic);
|
try {
|
||||||
|
destination = getSubscriptionStrategy().onSend(topicName);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw JMSExceptionSupport.create(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
activeMQDestinationMap.put(command.topicName().toString(), destination);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
msg.setJMSDestination(topic);
|
|
||||||
|
msg.setJMSDestination(destination);
|
||||||
msg.writeBytes(command.payload().data, command.payload().offset, command.payload().length);
|
msg.writeBytes(command.payload().data, command.payload().offset, command.payload().length);
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
@ -714,7 +626,8 @@ public class MQTTProtocolConverter {
|
||||||
synchronized (mqttTopicMap) {
|
synchronized (mqttTopicMap) {
|
||||||
topicName = mqttTopicMap.get(message.getJMSDestination());
|
topicName = mqttTopicMap.get(message.getJMSDestination());
|
||||||
if (topicName == null) {
|
if (topicName == null) {
|
||||||
topicName = MQTTProtocolSupport.convertActiveMQToMQTT(message.getDestination().getPhysicalName());
|
String amqTopicName = getSubscriptionStrategy().onSend(message.getDestination());
|
||||||
|
topicName = MQTTProtocolSupport.convertActiveMQToMQTT(amqTopicName);
|
||||||
mqttTopicMap.put(message.getJMSDestination(), topicName);
|
mqttTopicMap.put(message.getJMSDestination(), topicName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -803,9 +716,7 @@ public class MQTTProtocolConverter {
|
||||||
|
|
||||||
long keepAliveMS = keepAliveSeconds * 1000;
|
long keepAliveMS = keepAliveSeconds * 1000;
|
||||||
|
|
||||||
if (LOG.isDebugEnabled()) {
|
LOG.debug("MQTT Client {} requests heart beat of {} ms", getClientId(), keepAliveMS);
|
||||||
LOG.debug("MQTT Client " + getClientId() + " requests heart beat of " + keepAliveMS + " ms");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// if we have a default keep-alive value, and the client is trying to turn off keep-alive,
|
// if we have a default keep-alive value, and the client is trying to turn off keep-alive,
|
||||||
|
@ -822,12 +733,8 @@ public class MQTTProtocolConverter {
|
||||||
monitor.setReadGraceTime(readGracePeriod);
|
monitor.setReadGraceTime(readGracePeriod);
|
||||||
monitor.startMonitorThread();
|
monitor.startMonitorThread();
|
||||||
|
|
||||||
if (LOG.isDebugEnabled()) {
|
LOG.debug("MQTT Client {} established heart beat of {} ms ({} ms + {} ms grace period)",
|
||||||
LOG.debug("MQTT Client " + getClientId() +
|
new Object[] { getClientId(), keepAliveMS, keepAliveMS, readGracePeriod });
|
||||||
" established heart beat of " + keepAliveMS +
|
|
||||||
" ms (" + keepAliveMS + "ms + " + readGracePeriod +
|
|
||||||
"ms grace period)");
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LOG.warn("Failed to start MQTT InactivityMonitor ", ex);
|
LOG.warn("Failed to start MQTT InactivityMonitor ", ex);
|
||||||
}
|
}
|
||||||
|
@ -835,9 +742,7 @@ public class MQTTProtocolConverter {
|
||||||
|
|
||||||
void handleException(Throwable exception, MQTTFrame command) {
|
void handleException(Throwable exception, MQTTFrame command) {
|
||||||
LOG.warn("Exception occurred processing: \n" + command + ": " + exception.toString());
|
LOG.warn("Exception occurred processing: \n" + command + ": " + exception.toString());
|
||||||
if (LOG.isDebugEnabled()) {
|
LOG.debug("Exception detail", exception);
|
||||||
LOG.debug("Exception detail", exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connected.get() && connectionInfo != null) {
|
if (connected.get() && connectionInfo != null) {
|
||||||
connected.set(false);
|
connected.set(false);
|
||||||
|
@ -861,7 +766,6 @@ public class MQTTProtocolConverter {
|
||||||
}
|
}
|
||||||
|
|
||||||
ResponseHandler createResponseHandler(final PUBLISH command) {
|
ResponseHandler createResponseHandler(final PUBLISH command) {
|
||||||
|
|
||||||
if (command != null) {
|
if (command != null) {
|
||||||
switch (command.qos()) {
|
switch (command.qos()) {
|
||||||
case AT_LEAST_ONCE:
|
case AT_LEAST_ONCE:
|
||||||
|
@ -944,6 +848,22 @@ public class MQTTProtocolConverter {
|
||||||
return connectionId;
|
return connectionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ConsumerId getNextConsumerId() {
|
||||||
|
return new ConsumerId(sessionId, consumerIdGenerator.getNextSequenceId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCleanSession() {
|
||||||
|
return this.connect.cleanSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSubscriptionStrategyName() {
|
||||||
|
return subscriptionStrategyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubscriptionStrategyName(String name) {
|
||||||
|
this.subscriptionStrategyName = name;
|
||||||
|
}
|
||||||
|
|
||||||
public String getClientId() {
|
public String getClientId() {
|
||||||
if (clientId == null) {
|
if (clientId == null) {
|
||||||
if (connect != null && connect.clientId() != null) {
|
if (connect != null && connect.clientId() != null) {
|
||||||
|
@ -954,4 +874,33 @@ public class MQTTProtocolConverter {
|
||||||
}
|
}
|
||||||
return clientId;
|
return clientId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected MQTTSubscriptionStrategy getSubscriptionStrategy() throws IOException {
|
||||||
|
if (subsciptionStrategy == null) {
|
||||||
|
synchronized (STRATAGY_FINDER) {
|
||||||
|
if (subsciptionStrategy != null) {
|
||||||
|
return subsciptionStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
MQTTSubscriptionStrategy strategy = null;
|
||||||
|
if (subscriptionStrategyName != null && !subscriptionStrategyName.isEmpty()) {
|
||||||
|
try {
|
||||||
|
strategy = (MQTTSubscriptionStrategy) STRATAGY_FINDER.newInstance(subscriptionStrategyName);
|
||||||
|
LOG.debug("MQTT Using subscription strategy: {}", subscriptionStrategyName);
|
||||||
|
if (strategy instanceof BrokerServiceAware) {
|
||||||
|
((BrokerServiceAware)strategy).setBrokerService(brokerService);
|
||||||
|
}
|
||||||
|
strategy.initialize(this);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw IOExceptionSupport.create(e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IOException("Invalid subscription strategy name given: " + subscriptionStrategyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.subsciptionStrategy = strategy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return subsciptionStrategy;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,7 +132,12 @@ public class MQTTSubscription {
|
||||||
/**
|
/**
|
||||||
* @return the assigned QoS value for this subscription.
|
* @return the assigned QoS value for this subscription.
|
||||||
*/
|
*/
|
||||||
public QoS qos() {
|
public QoS getQoS() {
|
||||||
return qos;
|
return qos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "MQTT Sub: topic[" + topicName + "] -> [" + consumerInfo.getDestination() + "]";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,13 +17,13 @@
|
||||||
package org.apache.activemq.transport.mqtt;
|
package org.apache.activemq.transport.mqtt;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.Socket;
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.activemq.broker.BrokerContext;
|
import javax.net.ServerSocketFactory;
|
||||||
|
|
||||||
import org.apache.activemq.broker.BrokerService;
|
import org.apache.activemq.broker.BrokerService;
|
||||||
import org.apache.activemq.broker.BrokerServiceAware;
|
import org.apache.activemq.broker.BrokerServiceAware;
|
||||||
import org.apache.activemq.transport.MutexTransport;
|
import org.apache.activemq.transport.MutexTransport;
|
||||||
|
@ -33,8 +33,6 @@ import org.apache.activemq.transport.tcp.TcpTransportServer;
|
||||||
import org.apache.activemq.util.IntrospectionSupport;
|
import org.apache.activemq.util.IntrospectionSupport;
|
||||||
import org.apache.activemq.wireformat.WireFormat;
|
import org.apache.activemq.wireformat.WireFormat;
|
||||||
|
|
||||||
import javax.net.ServerSocketFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A <a href="http://mqtt.org/">MQTT</a> transport factory
|
* A <a href="http://mqtt.org/">MQTT</a> transport factory
|
||||||
*/
|
*/
|
||||||
|
@ -42,16 +40,19 @@ public class MQTTTransportFactory extends TcpTransportFactory implements BrokerS
|
||||||
|
|
||||||
private BrokerService brokerService = null;
|
private BrokerService brokerService = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
protected String getDefaultWireFormatType() {
|
protected String getDefaultWireFormatType() {
|
||||||
return "mqtt";
|
return "mqtt";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected TcpTransportServer createTcpTransportServer(URI location, ServerSocketFactory serverSocketFactory) throws IOException, URISyntaxException {
|
protected TcpTransportServer createTcpTransportServer(URI location, ServerSocketFactory serverSocketFactory) throws IOException, URISyntaxException {
|
||||||
TcpTransportServer result = new TcpTransportServer(this, location, serverSocketFactory);
|
TcpTransportServer result = new TcpTransportServer(this, location, serverSocketFactory);
|
||||||
result.setAllowLinkStealing(true);
|
result.setAllowLinkStealing(true);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
public Transport compositeConfigure(Transport transport, WireFormat format, Map options) {
|
public Transport compositeConfigure(Transport transport, WireFormat format, Map options) {
|
||||||
transport = new MQTTTransportFilter(transport, format, brokerService);
|
transport = new MQTTTransportFilter(transport, format, brokerService);
|
||||||
|
@ -59,6 +60,7 @@ public class MQTTTransportFactory extends TcpTransportFactory implements BrokerS
|
||||||
return super.compositeConfigure(transport, format, options);
|
return super.compositeConfigure(transport, format, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void setBrokerService(BrokerService brokerService) {
|
public void setBrokerService(BrokerService brokerService) {
|
||||||
this.brokerService = brokerService;
|
this.brokerService = brokerService;
|
||||||
}
|
}
|
||||||
|
@ -79,10 +81,8 @@ public class MQTTTransportFactory extends TcpTransportFactory implements BrokerS
|
||||||
@Override
|
@Override
|
||||||
protected Transport createInactivityMonitor(Transport transport, WireFormat format) {
|
protected Transport createInactivityMonitor(Transport transport, WireFormat format) {
|
||||||
MQTTInactivityMonitor monitor = new MQTTInactivityMonitor(transport, format);
|
MQTTInactivityMonitor monitor = new MQTTInactivityMonitor(transport, format);
|
||||||
|
|
||||||
MQTTTransportFilter filter = transport.narrow(MQTTTransportFilter.class);
|
MQTTTransportFilter filter = transport.narrow(MQTTTransportFilter.class);
|
||||||
filter.setInactivityMonitor(monitor);
|
filter.setInactivityMonitor(monitor);
|
||||||
|
|
||||||
return monitor;
|
return monitor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.apache.activemq.transport.mqtt;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import javax.jms.JMSException;
|
import javax.jms.JMSException;
|
||||||
|
|
||||||
import org.apache.activemq.broker.BrokerService;
|
import org.apache.activemq.broker.BrokerService;
|
||||||
|
@ -29,7 +30,20 @@ import org.apache.activemq.transport.TransportListener;
|
||||||
import org.apache.activemq.transport.tcp.SslTransport;
|
import org.apache.activemq.transport.tcp.SslTransport;
|
||||||
import org.apache.activemq.util.IOExceptionSupport;
|
import org.apache.activemq.util.IOExceptionSupport;
|
||||||
import org.apache.activemq.wireformat.WireFormat;
|
import org.apache.activemq.wireformat.WireFormat;
|
||||||
import org.fusesource.mqtt.codec.*;
|
import org.fusesource.mqtt.codec.CONNACK;
|
||||||
|
import org.fusesource.mqtt.codec.CONNECT;
|
||||||
|
import org.fusesource.mqtt.codec.DISCONNECT;
|
||||||
|
import org.fusesource.mqtt.codec.MQTTFrame;
|
||||||
|
import org.fusesource.mqtt.codec.PINGREQ;
|
||||||
|
import org.fusesource.mqtt.codec.PINGRESP;
|
||||||
|
import org.fusesource.mqtt.codec.PUBACK;
|
||||||
|
import org.fusesource.mqtt.codec.PUBCOMP;
|
||||||
|
import org.fusesource.mqtt.codec.PUBLISH;
|
||||||
|
import org.fusesource.mqtt.codec.PUBREC;
|
||||||
|
import org.fusesource.mqtt.codec.PUBREL;
|
||||||
|
import org.fusesource.mqtt.codec.SUBACK;
|
||||||
|
import org.fusesource.mqtt.codec.SUBSCRIBE;
|
||||||
|
import org.fusesource.mqtt.codec.UNSUBSCRIBE;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -197,6 +211,14 @@ public class MQTTTransportFilter extends TransportFilter implements MQTTTranspor
|
||||||
protocolConverter.setPublishDollarTopics(publishDollarTopics);
|
protocolConverter.setPublishDollarTopics(publishDollarTopics);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSubscriptionStrategyName() {
|
||||||
|
return protocolConverter != null ? protocolConverter.getSubscriptionStrategyName() : "default";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubscriptionStrategyName(String name) {
|
||||||
|
protocolConverter.setSubscriptionStrategyName(name);
|
||||||
|
}
|
||||||
|
|
||||||
public int getActiveMQSubscriptionPrefetch() {
|
public int getActiveMQSubscriptionPrefetch() {
|
||||||
return protocolConverter.getActiveMQSubscriptionPrefetch();
|
return protocolConverter.getActiveMQSubscriptionPrefetch();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,129 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.activemq.transport.mqtt.strategy;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.activemq.broker.BrokerService;
|
||||||
|
import org.apache.activemq.broker.BrokerServiceAware;
|
||||||
|
import org.apache.activemq.broker.ConnectionContext;
|
||||||
|
import org.apache.activemq.broker.region.PrefetchSubscription;
|
||||||
|
import org.apache.activemq.broker.region.RegionBroker;
|
||||||
|
import org.apache.activemq.broker.region.Subscription;
|
||||||
|
import org.apache.activemq.broker.region.TopicRegion;
|
||||||
|
import org.apache.activemq.broker.region.virtual.VirtualTopicInterceptor;
|
||||||
|
import org.apache.activemq.command.ActiveMQDestination;
|
||||||
|
import org.apache.activemq.command.ActiveMQTopic;
|
||||||
|
import org.apache.activemq.command.ConsumerId;
|
||||||
|
import org.apache.activemq.command.ConsumerInfo;
|
||||||
|
import org.apache.activemq.transport.mqtt.MQTTProtocolConverter;
|
||||||
|
import org.apache.activemq.transport.mqtt.MQTTProtocolException;
|
||||||
|
import org.apache.activemq.transport.mqtt.MQTTSubscription;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract implementation of the {@link MQTTSubscriptionStrategy} interface providing
|
||||||
|
* the base functionality that is common to most implementations.
|
||||||
|
*/
|
||||||
|
public abstract class AbstractMQTTSubscriptionStrategy implements MQTTSubscriptionStrategy, BrokerServiceAware {
|
||||||
|
|
||||||
|
protected MQTTProtocolConverter protocol;
|
||||||
|
protected BrokerService brokerService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(MQTTProtocolConverter protocol) throws MQTTProtocolException {
|
||||||
|
setProtocolConverter(protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBrokerService(BrokerService brokerService) {
|
||||||
|
this.brokerService = brokerService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setProtocolConverter(MQTTProtocolConverter parent) {
|
||||||
|
this.protocol = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MQTTProtocolConverter getProtocolConverter() {
|
||||||
|
return protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReSubscribe(MQTTSubscription mqttSubscription) throws MQTTProtocolException {
|
||||||
|
String topicName = mqttSubscription.getTopicName();
|
||||||
|
|
||||||
|
// get TopicRegion
|
||||||
|
RegionBroker regionBroker;
|
||||||
|
try {
|
||||||
|
regionBroker = (RegionBroker) brokerService.getBroker().getAdaptor(RegionBroker.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new MQTTProtocolException("Error subscribing to " + topicName + ": " + e.getMessage(), false, e);
|
||||||
|
}
|
||||||
|
final TopicRegion topicRegion = (TopicRegion) regionBroker.getTopicRegion();
|
||||||
|
|
||||||
|
final ConsumerInfo consumerInfo = mqttSubscription.getConsumerInfo();
|
||||||
|
final ConsumerId consumerId = consumerInfo.getConsumerId();
|
||||||
|
|
||||||
|
// use actual client id used to create connection to lookup connection
|
||||||
|
// context
|
||||||
|
final String connectionInfoClientId = protocol.getClientId();
|
||||||
|
final ConnectionContext connectionContext = regionBroker.getConnectionContext(connectionInfoClientId);
|
||||||
|
|
||||||
|
// get all matching Topics
|
||||||
|
final Set<org.apache.activemq.broker.region.Destination> matchingDestinations =
|
||||||
|
topicRegion.getDestinations(mqttSubscription.getDestination());
|
||||||
|
for (org.apache.activemq.broker.region.Destination dest : matchingDestinations) {
|
||||||
|
|
||||||
|
// recover retroactive messages for matching subscription
|
||||||
|
for (Subscription subscription : dest.getConsumers()) {
|
||||||
|
if (subscription.getConsumerInfo().getConsumerId().equals(consumerId)) {
|
||||||
|
try {
|
||||||
|
if (dest instanceof org.apache.activemq.broker.region.Topic) {
|
||||||
|
((org.apache.activemq.broker.region.Topic) dest).recoverRetroactiveMessages(connectionContext, subscription);
|
||||||
|
} else if (dest instanceof VirtualTopicInterceptor) {
|
||||||
|
((VirtualTopicInterceptor) dest).getTopic().recoverRetroactiveMessages(connectionContext, subscription);
|
||||||
|
}
|
||||||
|
if (subscription instanceof PrefetchSubscription) {
|
||||||
|
// request dispatch for prefetch subs
|
||||||
|
PrefetchSubscription prefetchSubscription = (PrefetchSubscription) subscription;
|
||||||
|
prefetchSubscription.dispatchPending();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new MQTTProtocolException("Error recovering retained messages for " + dest.getName() + ": " + e.getMessage(), false, e);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ActiveMQDestination onSend(String topicName) {
|
||||||
|
return new ActiveMQTopic(topicName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String onSend(ActiveMQDestination destination) {
|
||||||
|
return destination.getPhysicalName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isControlTopic(ActiveMQDestination destination) {
|
||||||
|
return destination.getPhysicalName().startsWith("$");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,151 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.activemq.transport.mqtt.strategy;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.activemq.command.ActiveMQDestination;
|
||||||
|
import org.apache.activemq.command.ActiveMQTopic;
|
||||||
|
import org.apache.activemq.command.ConsumerInfo;
|
||||||
|
import org.apache.activemq.command.RemoveSubscriptionInfo;
|
||||||
|
import org.apache.activemq.command.Response;
|
||||||
|
import org.apache.activemq.command.SubscriptionInfo;
|
||||||
|
import org.apache.activemq.store.PersistenceAdapterSupport;
|
||||||
|
import org.apache.activemq.transport.mqtt.MQTTProtocolConverter;
|
||||||
|
import org.apache.activemq.transport.mqtt.MQTTProtocolException;
|
||||||
|
import org.apache.activemq.transport.mqtt.MQTTProtocolSupport;
|
||||||
|
import org.apache.activemq.transport.mqtt.MQTTSubscription;
|
||||||
|
import org.apache.activemq.transport.mqtt.ResponseHandler;
|
||||||
|
import org.fusesource.mqtt.client.QoS;
|
||||||
|
import org.fusesource.mqtt.client.Topic;
|
||||||
|
import org.fusesource.mqtt.codec.CONNECT;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default implementation that uses unmapped topic subscriptions.
|
||||||
|
*/
|
||||||
|
public class MQTTDefaultSubscriptionStrategy extends AbstractMQTTSubscriptionStrategy {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(MQTTDefaultSubscriptionStrategy.class);
|
||||||
|
|
||||||
|
private final Set<String> restoredSubs = Collections.synchronizedSet(new HashSet<String>());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnect(CONNECT connect) throws MQTTProtocolException {
|
||||||
|
List<SubscriptionInfo> subs;
|
||||||
|
try {
|
||||||
|
subs = PersistenceAdapterSupport.listSubscriptions(brokerService.getPersistenceAdapter(), protocol.getClientId());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new MQTTProtocolException("Error loading store subscriptions", true, e);
|
||||||
|
}
|
||||||
|
if (connect.cleanSession()) {
|
||||||
|
deleteDurableSubs(subs);
|
||||||
|
} else {
|
||||||
|
restoreDurableSubs(subs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte onSubscribe(String topicName, QoS requestedQoS) throws MQTTProtocolException {
|
||||||
|
ActiveMQDestination destination = new ActiveMQTopic(MQTTProtocolSupport.convertMQTTToActiveMQ(topicName));
|
||||||
|
|
||||||
|
ConsumerInfo consumerInfo = new ConsumerInfo(protocol.getNextConsumerId());
|
||||||
|
consumerInfo.setDestination(destination);
|
||||||
|
consumerInfo.setPrefetchSize(protocol.getActiveMQSubscriptionPrefetch());
|
||||||
|
consumerInfo.setRetroactive(true);
|
||||||
|
consumerInfo.setDispatchAsync(true);
|
||||||
|
// create durable subscriptions only when clean session is false
|
||||||
|
if (!protocol.isCleanSession() && protocol.getClientId() != null && requestedQoS.ordinal() >= QoS.AT_LEAST_ONCE.ordinal()) {
|
||||||
|
consumerInfo.setSubscriptionName(requestedQoS + ":" + topicName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return protocol.doSubscribe(consumerInfo, topicName, requestedQoS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReSubscribe(MQTTSubscription mqttSubscription) throws MQTTProtocolException {
|
||||||
|
|
||||||
|
ActiveMQDestination destination = mqttSubscription.getDestination();
|
||||||
|
|
||||||
|
// check whether the Topic has been recovered in restoreDurableSubs
|
||||||
|
// mark subscription available for recovery for duplicate subscription
|
||||||
|
if (restoredSubs.remove(destination.getPhysicalName())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onReSubscribe(mqttSubscription);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUnSubscribe(MQTTSubscription subscription) throws MQTTProtocolException {
|
||||||
|
// check if the durable sub also needs to be removed
|
||||||
|
if (subscription.getConsumerInfo().getSubscriptionName() != null) {
|
||||||
|
// also remove it from restored durable subscriptions set
|
||||||
|
restoredSubs.remove(MQTTProtocolSupport.convertMQTTToActiveMQ(subscription.getTopicName()));
|
||||||
|
|
||||||
|
RemoveSubscriptionInfo rsi = new RemoveSubscriptionInfo();
|
||||||
|
rsi.setConnectionId(protocol.getConnectionId());
|
||||||
|
rsi.setSubscriptionName(subscription.getConsumerInfo().getSubscriptionName());
|
||||||
|
rsi.setClientId(protocol.getClientId());
|
||||||
|
protocol.sendToActiveMQ(rsi, new ResponseHandler() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(MQTTProtocolConverter converter, Response response) throws IOException {
|
||||||
|
// ignore failures..
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteDurableSubs(List<SubscriptionInfo> subs) {
|
||||||
|
try {
|
||||||
|
for (SubscriptionInfo sub : subs) {
|
||||||
|
RemoveSubscriptionInfo rsi = new RemoveSubscriptionInfo();
|
||||||
|
rsi.setConnectionId(protocol.getConnectionId());
|
||||||
|
rsi.setSubscriptionName(sub.getSubcriptionName());
|
||||||
|
rsi.setClientId(sub.getClientId());
|
||||||
|
protocol.sendToActiveMQ(rsi, new ResponseHandler() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(MQTTProtocolConverter converter, Response response) throws IOException {
|
||||||
|
// ignore failures..
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
LOG.warn("Could not delete the MQTT durable subs.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restoreDurableSubs(List<SubscriptionInfo> subs) {
|
||||||
|
try {
|
||||||
|
for (SubscriptionInfo sub : subs) {
|
||||||
|
String name = sub.getSubcriptionName();
|
||||||
|
String[] split = name.split(":", 2);
|
||||||
|
QoS qoS = QoS.valueOf(split[0]);
|
||||||
|
protocol.onSubscribe(new Topic(split[1], qoS));
|
||||||
|
// mark this durable subscription as restored by Broker
|
||||||
|
restoredSubs.add(split[1]);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.warn("Could not restore the MQTT durable subs.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,139 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.activemq.transport.mqtt.strategy;
|
||||||
|
|
||||||
|
import org.apache.activemq.command.ActiveMQDestination;
|
||||||
|
import org.apache.activemq.transport.mqtt.MQTTProtocolConverter;
|
||||||
|
import org.apache.activemq.transport.mqtt.MQTTProtocolException;
|
||||||
|
import org.apache.activemq.transport.mqtt.MQTTSubscription;
|
||||||
|
import org.fusesource.mqtt.client.QoS;
|
||||||
|
import org.fusesource.mqtt.codec.CONNECT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription management strategy used to control how MQTT clients
|
||||||
|
* subscribe to destination and how messages are addressed in order to
|
||||||
|
* arrive on the appropriate destinations.
|
||||||
|
*/
|
||||||
|
public interface MQTTSubscriptionStrategy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the strategy before first use.
|
||||||
|
*
|
||||||
|
* @param protocol
|
||||||
|
* the MQTTProtocolConverter that is initializing the strategy
|
||||||
|
*
|
||||||
|
* @throws MQTTProtocolException if an error occurs during initialization.
|
||||||
|
*/
|
||||||
|
public void initialize(MQTTProtocolConverter protocol) throws MQTTProtocolException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the strategy to perform any needed actions on client connect
|
||||||
|
* prior to the CONNACK frame being sent back such as recovering old
|
||||||
|
* subscriptions and performing any clean session actions.
|
||||||
|
*
|
||||||
|
* @throws MQTTProtocolException if an error occurs while processing the connect actions.
|
||||||
|
*/
|
||||||
|
public void onConnect(CONNECT connect) throws MQTTProtocolException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a new Subscription is being requested. This method allows the
|
||||||
|
* strategy to create a specific type of subscription for the client such as
|
||||||
|
* mapping topic subscriptions to Queues etc.
|
||||||
|
*
|
||||||
|
* @param topicName
|
||||||
|
* the requested Topic name to subscribe to.
|
||||||
|
* @param requestedQoS
|
||||||
|
* the QoS level that the client has requested for this subscription.
|
||||||
|
*
|
||||||
|
* @return the assigned QoS value given to the new subscription
|
||||||
|
*
|
||||||
|
* @throws MQTTProtocolException if an error occurs while processing the subscribe actions.
|
||||||
|
*/
|
||||||
|
public byte onSubscribe(String topicName, QoS requestedQoS) throws MQTTProtocolException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a client sends a duplicate subscribe request which should
|
||||||
|
* force any retained messages on that topic to be replayed again as though
|
||||||
|
* the client had just subscribed for the first time. The method should
|
||||||
|
* not unsubscribe the client as it might miss messages sent while the
|
||||||
|
* subscription is being recreated.
|
||||||
|
*
|
||||||
|
* @param subscription
|
||||||
|
* the MQTTSubscription that contains the subscription state.
|
||||||
|
*/
|
||||||
|
public void onReSubscribe(MQTTSubscription subscription) throws MQTTProtocolException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a client requests an un-subscribe a previous subscription.
|
||||||
|
*
|
||||||
|
* @param subscription
|
||||||
|
* the {@link MQTTSubscription} that is being removed.
|
||||||
|
*
|
||||||
|
* @throws MQTTProtocolException if an error occurs during the un-subscribe processing.
|
||||||
|
*/
|
||||||
|
public void onUnSubscribe(MQTTSubscription subscription) throws MQTTProtocolException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intercepts PUBLISH operations from the client and allows the strategy to map the
|
||||||
|
* target destination so that the send operation will land in the destinations that
|
||||||
|
* this strategy has mapped the incoming subscribe requests to.
|
||||||
|
*
|
||||||
|
* @param topicName
|
||||||
|
* the targeted Topic that the client sent the message to.
|
||||||
|
*
|
||||||
|
* @return an ActiveMQ Topic instance that lands the send in the correct destinations.
|
||||||
|
*/
|
||||||
|
public ActiveMQDestination onSend(String topicName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intercepts send operations from the broker and allows the strategy to map the
|
||||||
|
* target topic name so that the client sees a valid Topic name.
|
||||||
|
*
|
||||||
|
* @param destination
|
||||||
|
* the destination that the message was dispatched from
|
||||||
|
*
|
||||||
|
* @return an Topic name that is valid for the receiving client.
|
||||||
|
*/
|
||||||
|
public String onSend(ActiveMQDestination destination);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the protocol handler to interrogate an destination name to determine if it
|
||||||
|
* is equivalent to the MQTT control topic (starts with $). Since the mapped destinations
|
||||||
|
* that the strategy might alter the naming scheme the strategy must provide a way to
|
||||||
|
* reverse map and determine if the destination was originally an MQTT control topic.
|
||||||
|
*
|
||||||
|
* @param destination
|
||||||
|
* the destination to query.
|
||||||
|
*
|
||||||
|
* @return true if the destination is an MQTT control topic.
|
||||||
|
*/
|
||||||
|
public boolean isControlTopic(ActiveMQDestination destination);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link MQTTProtocolConverter} that is the parent of this strategy object.
|
||||||
|
*
|
||||||
|
* @param parent
|
||||||
|
* the {@link MQTTProtocolConverter} that owns this strategy.
|
||||||
|
*/
|
||||||
|
public void setProtocolConverter(MQTTProtocolConverter parent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the {@link MQTTProtocolConverter} that owns this strategy.
|
||||||
|
*/
|
||||||
|
public MQTTProtocolConverter getProtocolConverter();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,221 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.activemq.transport.mqtt.strategy;
|
||||||
|
|
||||||
|
import static org.apache.activemq.transport.mqtt.MQTTProtocolSupport.convertActiveMQToMQTT;
|
||||||
|
import static org.apache.activemq.transport.mqtt.MQTTProtocolSupport.convertMQTTToActiveMQ;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
|
import org.apache.activemq.command.ActiveMQDestination;
|
||||||
|
import org.apache.activemq.command.ActiveMQQueue;
|
||||||
|
import org.apache.activemq.command.ActiveMQTopic;
|
||||||
|
import org.apache.activemq.command.ConsumerInfo;
|
||||||
|
import org.apache.activemq.command.DestinationInfo;
|
||||||
|
import org.apache.activemq.command.Response;
|
||||||
|
import org.apache.activemq.store.PersistenceAdapterSupport;
|
||||||
|
import org.apache.activemq.transport.mqtt.MQTTProtocolConverter;
|
||||||
|
import org.apache.activemq.transport.mqtt.MQTTProtocolException;
|
||||||
|
import org.apache.activemq.transport.mqtt.MQTTSubscription;
|
||||||
|
import org.apache.activemq.transport.mqtt.ResponseHandler;
|
||||||
|
import org.fusesource.mqtt.client.QoS;
|
||||||
|
import org.fusesource.mqtt.codec.CONNECT;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription strategy that converts all MQTT subscribes that would be durable to
|
||||||
|
* Virtual Topic Queue subscriptions. Also maps all publish requests to be prefixed
|
||||||
|
* with the VirtualTopic. prefix unless already present.
|
||||||
|
*/
|
||||||
|
public class MQTTVirtualTopicSubscriptionStrategy extends AbstractMQTTSubscriptionStrategy {
|
||||||
|
|
||||||
|
private static final String VIRTUALTOPIC_PREFIX = "VirtualTopic.";
|
||||||
|
private static final String VIRTUALTOPIC_CONSUMER_PREFIX = "Consumer.";
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(MQTTVirtualTopicSubscriptionStrategy.class);
|
||||||
|
|
||||||
|
private final Set<ActiveMQQueue> restoredQueues = Collections.synchronizedSet(new HashSet<ActiveMQQueue>());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnect(CONNECT connect) throws MQTTProtocolException {
|
||||||
|
List<ActiveMQQueue> queues;
|
||||||
|
try {
|
||||||
|
queues = PersistenceAdapterSupport.listQueues(brokerService.getPersistenceAdapter(), new PersistenceAdapterSupport.DestinationMatcher() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(ActiveMQDestination destination) {
|
||||||
|
if (destination.getPhysicalName().startsWith("Consumer." + protocol.getClientId())) {
|
||||||
|
LOG.debug("Recovered client sub: {}", destination.getPhysicalName());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new MQTTProtocolException("Error restoring durable subscriptions", true, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connect.cleanSession()) {
|
||||||
|
deleteDurableQueues(queues);
|
||||||
|
} else {
|
||||||
|
restoreDurableQueue(queues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte onSubscribe(String topicName, QoS requestedQoS) throws MQTTProtocolException {
|
||||||
|
ActiveMQDestination destination = null;
|
||||||
|
if (!protocol.isCleanSession() && protocol.getClientId() != null && requestedQoS.ordinal() >= QoS.AT_LEAST_ONCE.ordinal()) {
|
||||||
|
String converted = VIRTUALTOPIC_CONSUMER_PREFIX + protocol.getClientId() + ":" + requestedQoS + "." +
|
||||||
|
VIRTUALTOPIC_PREFIX + convertMQTTToActiveMQ(topicName);
|
||||||
|
destination = new ActiveMQQueue(converted);
|
||||||
|
} else {
|
||||||
|
String converted = convertMQTTToActiveMQ(topicName);
|
||||||
|
if (!converted.startsWith(VIRTUALTOPIC_PREFIX)) {
|
||||||
|
converted = VIRTUALTOPIC_PREFIX + convertMQTTToActiveMQ(topicName);
|
||||||
|
}
|
||||||
|
destination = new ActiveMQTopic(converted);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConsumerInfo consumerInfo = new ConsumerInfo(protocol.getNextConsumerId());
|
||||||
|
consumerInfo.setDestination(destination);
|
||||||
|
consumerInfo.setPrefetchSize(protocol.getActiveMQSubscriptionPrefetch());
|
||||||
|
consumerInfo.setRetroactive(true);
|
||||||
|
consumerInfo.setDispatchAsync(true);
|
||||||
|
|
||||||
|
return protocol.doSubscribe(consumerInfo, topicName, requestedQoS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReSubscribe(MQTTSubscription mqttSubscription) throws MQTTProtocolException {
|
||||||
|
|
||||||
|
ActiveMQDestination destination = mqttSubscription.getDestination();
|
||||||
|
|
||||||
|
// check whether the Topic has been recovered in restoreDurableSubs
|
||||||
|
// mark subscription available for recovery for duplicate subscription
|
||||||
|
if (restoredQueues.remove(destination)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mqttSubscription.getDestination().isTopic()) {
|
||||||
|
super.onReSubscribe(mqttSubscription);
|
||||||
|
} else {
|
||||||
|
protocol.doUnSubscribe(mqttSubscription);
|
||||||
|
ConsumerInfo consumerInfo = mqttSubscription.getConsumerInfo();
|
||||||
|
consumerInfo.setConsumerId(protocol.getNextConsumerId());
|
||||||
|
protocol.doSubscribe(consumerInfo, mqttSubscription.getTopicName(), mqttSubscription.getQoS());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUnSubscribe(MQTTSubscription subscription) throws MQTTProtocolException {
|
||||||
|
if (subscription.getDestination().isQueue()) {
|
||||||
|
DestinationInfo remove = new DestinationInfo();
|
||||||
|
remove.setConnectionId(protocol.getConnectionId());
|
||||||
|
remove.setDestination(subscription.getDestination());
|
||||||
|
remove.setOperationType(DestinationInfo.REMOVE_OPERATION_TYPE);
|
||||||
|
|
||||||
|
protocol.sendToActiveMQ(remove, new ResponseHandler() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(MQTTProtocolConverter converter, Response response) throws IOException {
|
||||||
|
// ignore failures..
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ActiveMQDestination onSend(String topicName) {
|
||||||
|
if (!topicName.startsWith(VIRTUALTOPIC_PREFIX)) {
|
||||||
|
return new ActiveMQTopic(VIRTUALTOPIC_PREFIX + topicName);
|
||||||
|
} else {
|
||||||
|
return new ActiveMQTopic(topicName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String onSend(ActiveMQDestination destination) {
|
||||||
|
String amqTopicName = destination.getPhysicalName();
|
||||||
|
if (amqTopicName.startsWith(VIRTUALTOPIC_PREFIX)) {
|
||||||
|
amqTopicName = amqTopicName.substring(VIRTUALTOPIC_PREFIX.length());
|
||||||
|
}
|
||||||
|
return amqTopicName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isControlTopic(ActiveMQDestination destination) {
|
||||||
|
String destinationName = destination.getPhysicalName();
|
||||||
|
if (destinationName.startsWith("$") || destinationName.startsWith(VIRTUALTOPIC_PREFIX + "$")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteDurableQueues(List<ActiveMQQueue> queues) {
|
||||||
|
try {
|
||||||
|
for (ActiveMQQueue queue : queues) {
|
||||||
|
DestinationInfo removeAction = new DestinationInfo();
|
||||||
|
removeAction.setConnectionId(protocol.getConnectionId());
|
||||||
|
removeAction.setDestination(queue);
|
||||||
|
removeAction.setOperationType(DestinationInfo.REMOVE_OPERATION_TYPE);
|
||||||
|
|
||||||
|
protocol.sendToActiveMQ(removeAction, new ResponseHandler() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(MQTTProtocolConverter converter, Response response) throws IOException {
|
||||||
|
// ignore failures..
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
LOG.warn("Could not delete the MQTT durable subs.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restoreDurableQueue(List<ActiveMQQueue> queues) {
|
||||||
|
try {
|
||||||
|
for (ActiveMQQueue queue : queues) {
|
||||||
|
String name = queue.getPhysicalName().substring(VIRTUALTOPIC_CONSUMER_PREFIX.length());
|
||||||
|
StringTokenizer tokenizer = new StringTokenizer(name);
|
||||||
|
tokenizer.nextToken(":.");
|
||||||
|
String qosString = tokenizer.nextToken();
|
||||||
|
tokenizer.nextToken();
|
||||||
|
String topicName = convertActiveMQToMQTT(tokenizer.nextToken("").substring(1));
|
||||||
|
QoS qoS = QoS.valueOf(qosString);
|
||||||
|
LOG.trace("Restoring subscription: {}:{}", topicName, qoS);
|
||||||
|
|
||||||
|
ConsumerInfo consumerInfo = new ConsumerInfo(protocol.getNextConsumerId());
|
||||||
|
consumerInfo.setDestination(queue);
|
||||||
|
consumerInfo.setPrefetchSize(protocol.getActiveMQSubscriptionPrefetch());
|
||||||
|
consumerInfo.setRetroactive(true);
|
||||||
|
consumerInfo.setDispatchAsync(true);
|
||||||
|
|
||||||
|
protocol.doSubscribe(consumerInfo, topicName, qoS);
|
||||||
|
|
||||||
|
// mark this durable subscription as restored by Broker
|
||||||
|
restoredQueues.add(queue);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.warn("Could not restore the MQTT durable subs.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
## ---------------------------------------------------------------------------
|
||||||
|
## Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
## contributor license agreements. See the NOTICE file distributed with
|
||||||
|
## this work for additional information regarding copyright ownership.
|
||||||
|
## The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
## (the "License"); you may not use this file except in compliance with
|
||||||
|
## the License. You may obtain a copy of the License at
|
||||||
|
##
|
||||||
|
## http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
##
|
||||||
|
## Unless required by applicable law or agreed to in writing, software
|
||||||
|
## distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
## See the License for the specific language governing permissions and
|
||||||
|
## limitations under the License.
|
||||||
|
## ---------------------------------------------------------------------------
|
||||||
|
class=org.apache.activemq.transport.mqtt.strategy.MQTTDefaultSubscriptionStrategy
|
|
@ -0,0 +1,17 @@
|
||||||
|
## ---------------------------------------------------------------------------
|
||||||
|
## Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
## contributor license agreements. See the NOTICE file distributed with
|
||||||
|
## this work for additional information regarding copyright ownership.
|
||||||
|
## The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
## (the "License"); you may not use this file except in compliance with
|
||||||
|
## the License. You may obtain a copy of the License at
|
||||||
|
##
|
||||||
|
## http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
##
|
||||||
|
## Unless required by applicable law or agreed to in writing, software
|
||||||
|
## distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
## See the License for the specific language governing permissions and
|
||||||
|
## limitations under the License.
|
||||||
|
## ---------------------------------------------------------------------------
|
||||||
|
class=org.apache.activemq.transport.mqtt.strategy.MQTTVirtualTopicSubscriptionStrategy
|
|
@ -62,6 +62,7 @@ import org.fusesource.mqtt.client.Topic;
|
||||||
import org.fusesource.mqtt.client.Tracer;
|
import org.fusesource.mqtt.client.Tracer;
|
||||||
import org.fusesource.mqtt.codec.MQTTFrame;
|
import org.fusesource.mqtt.codec.MQTTFrame;
|
||||||
import org.fusesource.mqtt.codec.PUBLISH;
|
import org.fusesource.mqtt.codec.PUBLISH;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -153,7 +154,7 @@ public class MQTTTest extends MQTTTestSupport {
|
||||||
publishProvider.publish(topic, payload.getBytes(), AT_LEAST_ONCE);
|
publishProvider.publish(topic, payload.getBytes(), AT_LEAST_ONCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
latch.await(10, TimeUnit.SECONDS);
|
latch.await(20, TimeUnit.SECONDS);
|
||||||
assertEquals(0, latch.getCount());
|
assertEquals(0, latch.getCount());
|
||||||
subscriptionProvider.disconnect();
|
subscriptionProvider.disconnect();
|
||||||
publishProvider.disconnect();
|
publishProvider.disconnect();
|
||||||
|
@ -488,13 +489,14 @@ public class MQTTTest extends MQTTTestSupport {
|
||||||
@Test(timeout = 120 * 1000)
|
@Test(timeout = 120 * 1000)
|
||||||
public void testRetainedMessage() throws Exception {
|
public void testRetainedMessage() throws Exception {
|
||||||
MQTT mqtt = createMQTTConnection();
|
MQTT mqtt = createMQTTConnection();
|
||||||
mqtt.setKeepAlive((short) 2);
|
mqtt.setKeepAlive((short) 60);
|
||||||
|
|
||||||
final String RETAIN = "RETAIN";
|
final String RETAIN = "RETAIN";
|
||||||
final String TOPICA = "TopicA";
|
final String TOPICA = "TopicA";
|
||||||
|
|
||||||
final String[] clientIds = { null, "foo", "durable" };
|
final String[] clientIds = { null, "foo", "durable" };
|
||||||
for (String clientId : clientIds) {
|
for (String clientId : clientIds) {
|
||||||
|
LOG.info("Testing now with Client ID: {}", clientId);
|
||||||
|
|
||||||
mqtt.setClientId(clientId);
|
mqtt.setClientId(clientId);
|
||||||
mqtt.setCleanSession(!"durable".equals(clientId));
|
mqtt.setCleanSession(!"durable".equals(clientId));
|
||||||
|
@ -552,6 +554,76 @@ public class MQTTTest extends MQTTTestSupport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
@Test(timeout = 120 * 1000)
|
||||||
|
public void testRetainedMessageOnVirtualTopics() throws Exception {
|
||||||
|
MQTT mqtt = createMQTTConnection();
|
||||||
|
mqtt.setKeepAlive((short) 60);
|
||||||
|
|
||||||
|
final String RETAIN = "RETAIN";
|
||||||
|
final String TOPICA = "VirtualTopic/TopicA";
|
||||||
|
|
||||||
|
final String[] clientIds = { null, "foo", "durable" };
|
||||||
|
for (String clientId : clientIds) {
|
||||||
|
LOG.info("Testing now with Client ID: {}", clientId);
|
||||||
|
|
||||||
|
mqtt.setClientId(clientId);
|
||||||
|
mqtt.setCleanSession(!"durable".equals(clientId));
|
||||||
|
|
||||||
|
BlockingConnection connection = mqtt.blockingConnection();
|
||||||
|
connection.connect();
|
||||||
|
|
||||||
|
// set retained message and check
|
||||||
|
connection.publish(TOPICA, RETAIN.getBytes(), QoS.EXACTLY_ONCE, true);
|
||||||
|
connection.subscribe(new Topic[]{new Topic(TOPICA, QoS.AT_LEAST_ONCE)});
|
||||||
|
Message msg = connection.receive(5000, TimeUnit.MILLISECONDS);
|
||||||
|
assertNotNull("No retained message for " + clientId, msg);
|
||||||
|
assertEquals(RETAIN, new String(msg.getPayload()));
|
||||||
|
msg.ack();
|
||||||
|
assertNull(connection.receive(500, TimeUnit.MILLISECONDS));
|
||||||
|
|
||||||
|
// test duplicate subscription
|
||||||
|
connection.subscribe(new Topic[]{new Topic(TOPICA, QoS.AT_LEAST_ONCE)});
|
||||||
|
msg = connection.receive(15000, TimeUnit.MILLISECONDS);
|
||||||
|
assertNotNull("No retained message on duplicate subscription for " + clientId, msg);
|
||||||
|
assertEquals(RETAIN, new String(msg.getPayload()));
|
||||||
|
msg.ack();
|
||||||
|
assertNull(connection.receive(500, TimeUnit.MILLISECONDS));
|
||||||
|
connection.unsubscribe(new String[]{TOPICA});
|
||||||
|
|
||||||
|
// clear retained message and check that we don't receive it
|
||||||
|
connection.publish(TOPICA, "".getBytes(), QoS.AT_MOST_ONCE, true);
|
||||||
|
connection.subscribe(new Topic[]{new Topic(TOPICA, QoS.AT_LEAST_ONCE)});
|
||||||
|
msg = connection.receive(500, TimeUnit.MILLISECONDS);
|
||||||
|
assertNull("Retained message not cleared for " + clientId, msg);
|
||||||
|
connection.unsubscribe(new String[]{TOPICA});
|
||||||
|
|
||||||
|
// set retained message again and check
|
||||||
|
connection.publish(TOPICA, RETAIN.getBytes(), QoS.EXACTLY_ONCE, true);
|
||||||
|
connection.subscribe(new Topic[]{new Topic(TOPICA, QoS.AT_LEAST_ONCE)});
|
||||||
|
msg = connection.receive(5000, TimeUnit.MILLISECONDS);
|
||||||
|
assertNotNull("No reset retained message for " + clientId, msg);
|
||||||
|
assertEquals(RETAIN, new String(msg.getPayload()));
|
||||||
|
msg.ack();
|
||||||
|
assertNull(connection.receive(500, TimeUnit.MILLISECONDS));
|
||||||
|
|
||||||
|
// re-connect and check
|
||||||
|
connection.disconnect();
|
||||||
|
connection = mqtt.blockingConnection();
|
||||||
|
connection.connect();
|
||||||
|
connection.subscribe(new Topic[]{new Topic(TOPICA, QoS.AT_LEAST_ONCE)});
|
||||||
|
msg = connection.receive(5000, TimeUnit.MILLISECONDS);
|
||||||
|
assertNotNull("No reset retained message for " + clientId, msg);
|
||||||
|
assertEquals(RETAIN, new String(msg.getPayload()));
|
||||||
|
msg.ack();
|
||||||
|
assertNull(connection.receive(500, TimeUnit.MILLISECONDS));
|
||||||
|
|
||||||
|
LOG.info("Test now unsubscribing from: {} for the last time", TOPICA);
|
||||||
|
connection.unsubscribe(new String[]{TOPICA});
|
||||||
|
connection.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test(timeout = 60 * 1000)
|
@Test(timeout = 60 * 1000)
|
||||||
public void testUniqueMessageIds() throws Exception {
|
public void testUniqueMessageIds() throws Exception {
|
||||||
MQTT mqtt = createMQTTConnection();
|
MQTT mqtt = createMQTTConnection();
|
||||||
|
@ -914,9 +986,12 @@ public class MQTTTest extends MQTTTestSupport {
|
||||||
|
|
||||||
@Test(timeout = 60 * 1000)
|
@Test(timeout = 60 * 1000)
|
||||||
public void testSendMQTTReceiveJMS() throws Exception {
|
public void testSendMQTTReceiveJMS() throws Exception {
|
||||||
|
doTestSendMQTTReceiveJMS("foo.*");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void doTestSendMQTTReceiveJMS(String destinationName) throws Exception {
|
||||||
final MQTTClientProvider provider = getMQTTClientProvider();
|
final MQTTClientProvider provider = getMQTTClientProvider();
|
||||||
initializeConnection(provider);
|
initializeConnection(provider);
|
||||||
final String DESTINATION_NAME = "foo.*";
|
|
||||||
|
|
||||||
// send retained message
|
// send retained message
|
||||||
final String RETAINED = "RETAINED";
|
final String RETAINED = "RETAINED";
|
||||||
|
@ -927,7 +1002,7 @@ public class MQTTTest extends MQTTTestSupport {
|
||||||
activeMQConnection.setUseRetroactiveConsumer(true);
|
activeMQConnection.setUseRetroactiveConsumer(true);
|
||||||
activeMQConnection.start();
|
activeMQConnection.start();
|
||||||
Session s = activeMQConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
|
Session s = activeMQConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
|
||||||
javax.jms.Topic jmsTopic = s.createTopic(DESTINATION_NAME);
|
javax.jms.Topic jmsTopic = s.createTopic(destinationName);
|
||||||
MessageConsumer consumer = s.createConsumer(jmsTopic);
|
MessageConsumer consumer = s.createConsumer(jmsTopic);
|
||||||
|
|
||||||
// check whether we received retained message on JMS subscribe
|
// check whether we received retained message on JMS subscribe
|
||||||
|
@ -952,6 +1027,10 @@ public class MQTTTest extends MQTTTestSupport {
|
||||||
|
|
||||||
@Test(timeout = 2 * 60 * 1000)
|
@Test(timeout = 2 * 60 * 1000)
|
||||||
public void testSendJMSReceiveMQTT() throws Exception {
|
public void testSendJMSReceiveMQTT() throws Exception {
|
||||||
|
doTestSendJMSReceiveMQTT("foo.far");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void doTestSendJMSReceiveMQTT(String destinationName) throws Exception {
|
||||||
final MQTTClientProvider provider = getMQTTClientProvider();
|
final MQTTClientProvider provider = getMQTTClientProvider();
|
||||||
initializeConnection(provider);
|
initializeConnection(provider);
|
||||||
|
|
||||||
|
@ -959,7 +1038,7 @@ public class MQTTTest extends MQTTTestSupport {
|
||||||
activeMQConnection.setUseRetroactiveConsumer(true);
|
activeMQConnection.setUseRetroactiveConsumer(true);
|
||||||
activeMQConnection.start();
|
activeMQConnection.start();
|
||||||
Session s = activeMQConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
|
Session s = activeMQConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
|
||||||
javax.jms.Topic jmsTopic = s.createTopic("foo.far");
|
javax.jms.Topic jmsTopic = s.createTopic(destinationName);
|
||||||
MessageProducer producer = s.createProducer(jmsTopic);
|
MessageProducer producer = s.createProducer(jmsTopic);
|
||||||
|
|
||||||
// send retained message from JMS
|
// send retained message from JMS
|
||||||
|
@ -1130,10 +1209,14 @@ public class MQTTTest extends MQTTTestSupport {
|
||||||
|
|
||||||
@Test(timeout = 30 * 10000)
|
@Test(timeout = 30 * 10000)
|
||||||
public void testJmsMapping() throws Exception {
|
public void testJmsMapping() throws Exception {
|
||||||
|
doTestJmsMapping("test.foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void doTestJmsMapping(String destinationName) throws Exception {
|
||||||
// start up jms consumer
|
// start up jms consumer
|
||||||
Connection jmsConn = cf.createConnection();
|
Connection jmsConn = cf.createConnection();
|
||||||
Session session = jmsConn.createSession(false, Session.AUTO_ACKNOWLEDGE);
|
Session session = jmsConn.createSession(false, Session.AUTO_ACKNOWLEDGE);
|
||||||
Destination dest = session.createTopic("test.foo");
|
Destination dest = session.createTopic(destinationName);
|
||||||
MessageConsumer consumer = session.createConsumer(dest);
|
MessageConsumer consumer = session.createConsumer(dest);
|
||||||
jmsConn.start();
|
jmsConn.start();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.activemq.transport.mqtt;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the basic tests with the NIO Transport.
|
||||||
|
*/
|
||||||
|
public class MQTTVirtualTopicSubscriptionsTest extends MQTTTest {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
protocolConfig = "transport.subscriptionStrategyName=mqtt-virtual-topic-subscriptions";
|
||||||
|
super.setUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO - This currently fails on the durable case because we have a hard time
|
||||||
|
// recovering the original Topic name when a client tries to subscribe
|
||||||
|
// durable to a VirtualTopic.* type topic.
|
||||||
|
@Override
|
||||||
|
@Ignore
|
||||||
|
public void testRetainedMessageOnVirtualTopics() throws Exception {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Test(timeout = 60 * 1000)
|
||||||
|
public void testSendMQTTReceiveJMS() throws Exception {
|
||||||
|
doTestSendMQTTReceiveJMS("VirtualTopic.foo.*");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Test(timeout = 2 * 60 * 1000)
|
||||||
|
public void testSendJMSReceiveMQTT() throws Exception {
|
||||||
|
doTestSendJMSReceiveMQTT("VirtualTopic.foo.far");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Test(timeout = 30 * 10000)
|
||||||
|
public void testJmsMapping() throws Exception {
|
||||||
|
doTestJmsMapping("VirtualTopic.test.foo");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue