Fixed checkStyle and ErrorProne warns
This commit is contained in:
@@ -24,6 +24,7 @@ import io.netty.buffer.ByteBuf;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import static io.moquette.spi.impl.Utils.readBytesAndRewind;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
public class HazelcastInterceptHandler extends AbstractInterceptHandler {
|
||||
|
||||
@@ -45,7 +46,8 @@ public class HazelcastInterceptHandler extends AbstractInterceptHandler {
|
||||
ByteBuf payload = msg.getPayload();
|
||||
byte[] payloadContent = readBytesAndRewind(payload);
|
||||
|
||||
LOG.info("{} publish on {} message: {}", msg.getClientID(), msg.getTopicName(), new String(payloadContent));
|
||||
LOG.info("{} publish on {} message: {}", msg.getClientID(), msg.getTopicName(),
|
||||
new String(payloadContent, UTF_8));
|
||||
ITopic<HazelcastMsg> topic = hz.getTopic("moquette");
|
||||
HazelcastMsg hazelcastMsg = new HazelcastMsg(msg);
|
||||
topic.publish(hazelcastMsg);
|
||||
|
||||
@@ -18,6 +18,8 @@ package io.moquette.interception.messages;
|
||||
|
||||
import io.netty.handler.codec.mqtt.MqttConnectMessage;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class InterceptConnectMessage extends InterceptAbstractMessage {
|
||||
|
||||
private final MqttConnectMessage msg;
|
||||
@@ -72,7 +74,7 @@ public class InterceptConnectMessage extends InterceptAbstractMessage {
|
||||
}
|
||||
|
||||
public byte[] getPassword() {
|
||||
return msg.payload().password().getBytes();
|
||||
return msg.payload().password().getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public String getWillTopic() {
|
||||
@@ -80,6 +82,6 @@ public class InterceptConnectMessage extends InterceptAbstractMessage {
|
||||
}
|
||||
|
||||
public byte[] getWillMessage() {
|
||||
return msg.payload().willMessage().getBytes();
|
||||
return msg.payload().willMessage().getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ public class MemoryMessagesStore implements IMessagesStore {
|
||||
public void storeRetained(Topic topic, StoredMessage storedMessage) {
|
||||
LOG.debug("Store retained message for topic={}, CId={}", topic, storedMessage.getClientID());
|
||||
if (storedMessage.getClientID() == null) {
|
||||
throw new IllegalArgumentException( "Message to be persisted must have a not null client ID");
|
||||
throw new IllegalArgumentException("Message to be persisted must have a not null client ID");
|
||||
}
|
||||
m_retainedStore.put(topic, storedMessage);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public class MemorySessionStore implements ISessionsStore, ISubscriptionsStore {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MemorySessionStore.class);
|
||||
|
||||
class Session {
|
||||
static class Session {
|
||||
final String clientID;
|
||||
final Map<Topic, Subscription> subscriptions = new ConcurrentHashMap<>();
|
||||
boolean cleanSession;
|
||||
@@ -170,7 +170,6 @@ public class MemorySessionStore implements ISessionsStore, ISubscriptionsStore {
|
||||
return subscriptions.get(subcription.getTopicFilter());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public StoredMessage inFlightAck(String clientID, int messageID) {
|
||||
return getSession(clientID).outboundFlightMessages.remove(messageID);
|
||||
|
||||
@@ -29,6 +29,10 @@ import io.moquette.server.netty.metrics.MessageMetrics;
|
||||
import io.moquette.server.netty.metrics.MessageMetricsHandler;
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
import static io.netty.channel.ChannelFutureListener.CLOSE;
|
||||
import static io.netty.channel.ChannelFutureListener.CLOSE_ON_FAILURE;
|
||||
import static io.netty.channel.ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE;
|
||||
|
||||
/**
|
||||
* Value object to maintain the information of single connection, like ClientID, Channel, and clean
|
||||
* session flag.
|
||||
@@ -62,7 +66,7 @@ public class ConnectionDescriptor {
|
||||
}
|
||||
|
||||
public void writeAndFlush(Object payload) {
|
||||
this.channel.writeAndFlush(payload);
|
||||
this.channel.writeAndFlush(payload).addListener(FIRE_EXCEPTION_ON_FAILURE);
|
||||
}
|
||||
|
||||
public void setupAutoFlusher(int flushIntervalMs) {
|
||||
@@ -88,7 +92,7 @@ public class ConnectionDescriptor {
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
this.channel.close();
|
||||
this.channel.close().addListener(CLOSE_ON_FAILURE);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -100,7 +104,7 @@ public class ConnectionDescriptor {
|
||||
LOG.info("Closing connection descriptor. MqttClientId = {}.", clientID);
|
||||
// try {
|
||||
// this.channel.disconnect().sync();
|
||||
this.channel.close(); // .sync();
|
||||
this.channel.close().addListener(CLOSE_ON_FAILURE); // .sync();
|
||||
// } catch (InterruptedException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
|
||||
@@ -19,6 +19,8 @@ package io.moquette.server.config;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -51,7 +53,7 @@ public class ClasspathResourceLoader implements IResourceLoader {
|
||||
public Reader loadResource(String relativePath) {
|
||||
LOG.info("Loading resource. RelativePath = {}.", relativePath);
|
||||
InputStream is = this.classLoader.getResourceAsStream(relativePath);
|
||||
return is != null ? new InputStreamReader(is) : null;
|
||||
return is != null ? new InputStreamReader(is, StandardCharsets.UTF_8) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -24,6 +24,8 @@ import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.text.ParseException;
|
||||
import java.util.Properties;
|
||||
|
||||
@@ -55,15 +57,10 @@ class ConfigurationParser {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
FileReader reader = new FileReader(file);
|
||||
Reader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8);
|
||||
parse(reader);
|
||||
} catch (FileNotFoundException fex) {
|
||||
LOG.warn(
|
||||
String.format(
|
||||
"parsing not existing file %s, so fallback on default configuration!",
|
||||
file.getAbsolutePath()),
|
||||
fex);
|
||||
return;
|
||||
} catch (IOException fex) {
|
||||
LOG.warn("parsing not existing file {}, fallback on default configuration!", file.getAbsolutePath(), fex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,15 @@ package io.moquette.server.config;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
public class FileResourceLoader implements IResourceLoader {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FileResourceLoader.class);
|
||||
@@ -68,8 +73,8 @@ public class FileResourceLoader implements IResourceLoader {
|
||||
throw new ResourceIsDirectoryException("File \"" + f + "\" is a directory!");
|
||||
}
|
||||
try {
|
||||
return new FileReader(f);
|
||||
} catch (FileNotFoundException e) {
|
||||
return Files.newBufferedReader(f.toPath(), UTF_8);
|
||||
} catch (IOException e) {
|
||||
LOG.error("The file does not exist. Path = {}.", f.getAbsolutePath());
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.timeout.IdleState;
|
||||
import io.netty.handler.timeout.IdleStateEvent;
|
||||
|
||||
import static io.netty.channel.ChannelFutureListener.CLOSE_ON_FAILURE;
|
||||
|
||||
@Sharable
|
||||
class MoquetteIdleTimeoutHandler extends ChannelDuplexHandler {
|
||||
|
||||
@@ -37,7 +39,7 @@ class MoquetteIdleTimeoutHandler extends ChannelDuplexHandler {
|
||||
LOG.info("Firing channel inactive event. MqttClientId = {}.", NettyUtils.clientID(ctx.channel()));
|
||||
// fire a channelInactive to trigger publish of Will
|
||||
ctx.fireChannelInactive();
|
||||
ctx.close();
|
||||
ctx.close().addListener(CLOSE_ON_FAILURE);
|
||||
}
|
||||
} else {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
|
||||
@@ -44,6 +44,7 @@ import io.netty.handler.codec.mqtt.MqttEncoder;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
import io.netty.handler.timeout.IdleStateHandler;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.GenericFutureListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
@@ -53,6 +54,7 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import static io.moquette.BrokerConstants.*;
|
||||
import static io.netty.channel.ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE;
|
||||
|
||||
public class NettyAcceptor implements ServerAcceptor {
|
||||
|
||||
@@ -85,7 +87,7 @@ public class NettyAcceptor implements ServerAcceptor {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class PipelineInitializer {
|
||||
abstract static class PipelineInitializer {
|
||||
|
||||
abstract void init(ChannelPipeline pipeline) throws Exception;
|
||||
}
|
||||
@@ -197,7 +199,7 @@ public class NettyAcceptor implements ServerAcceptor {
|
||||
// Bind and start to accept incoming connections.
|
||||
ChannelFuture f = b.bind(host, port);
|
||||
LOG.info("Server has been bound. host={}, port={}", host, port);
|
||||
f.sync();
|
||||
f.sync().addListener(FIRE_EXCEPTION_ON_FAILURE);
|
||||
} catch (InterruptedException ex) {
|
||||
LOG.error("An interruptedException was caught while initializing server. Protocol={}", protocol, ex);
|
||||
}
|
||||
@@ -349,6 +351,8 @@ public class NettyAcceptor implements ServerAcceptor {
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("FutureReturnValueIgnored")
|
||||
@Override
|
||||
public void close() {
|
||||
LOG.info("Closing Netty acceptor...");
|
||||
if (m_workerGroup == null || m_bossGroup == null) {
|
||||
|
||||
@@ -24,6 +24,8 @@ import io.netty.handler.codec.mqtt.*;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static io.netty.channel.ChannelFutureListener.CLOSE_ON_FAILURE;
|
||||
import static io.netty.handler.codec.mqtt.MqttQoS.AT_MOST_ONCE;
|
||||
|
||||
@Sharable
|
||||
@@ -78,16 +80,16 @@ public class NettyMQTTHandler extends ChannelInboundHandlerAdapter {
|
||||
false,
|
||||
0);
|
||||
MqttMessage pingResp = new MqttMessage(pingHeader);
|
||||
ctx.writeAndFlush(pingResp);
|
||||
ctx.writeAndFlush(pingResp).addListener(CLOSE_ON_FAILURE);
|
||||
break;
|
||||
default:
|
||||
LOG.error("Unkonwn MessageType:{}", messageType);
|
||||
break;
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
LOG.error("Exception was caught while processing MQTT message, " + ex.getCause(), ex);
|
||||
LOG.error("Exception was caught while processing MQTT message, {}", ex.getCause(), ex);
|
||||
ctx.fireExceptionCaught(ex);
|
||||
ctx.close();
|
||||
ctx.close().addListener(CLOSE_ON_FAILURE);
|
||||
} finally {
|
||||
ReferenceCountUtil.release(msg);
|
||||
}
|
||||
@@ -97,17 +99,17 @@ public class NettyMQTTHandler extends ChannelInboundHandlerAdapter {
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
String clientID = NettyUtils.clientID(ctx.channel());
|
||||
if (clientID != null && !clientID.isEmpty()) {
|
||||
LOG.info("Notifying connection lost event. MqttClientId = {}.", clientID);
|
||||
LOG.info("Notifying connection lost event. MqttClientId = {}", clientID);
|
||||
m_processor.processConnectionLost(clientID, ctx.channel());
|
||||
}
|
||||
ctx.close();
|
||||
ctx.close().addListener(CLOSE_ON_FAILURE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
LOG.error("An unexpected exception was caught while processing MQTT message. Closing Netty channel. CId={}, " +
|
||||
"cause={}, errorMessage={}", NettyUtils.clientID(ctx.channel()), cause.getCause(), cause.getMessage());
|
||||
ctx.close();
|
||||
ctx.close().addListener(CLOSE_ON_FAILURE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -24,6 +24,8 @@ import io.netty.channel.ChannelPromise;
|
||||
import io.netty.util.Attribute;
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
import static io.netty.channel.ChannelFutureListener.CLOSE_ON_FAILURE;
|
||||
|
||||
public class BytesMetricsHandler extends ChannelDuplexHandler {
|
||||
|
||||
private static final AttributeKey<BytesMetrics> ATTR_KEY_METRICS = AttributeKey.valueOf("BytesMetrics");
|
||||
@@ -53,7 +55,7 @@ public class BytesMetricsHandler extends ChannelDuplexHandler {
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||
BytesMetrics metrics = ctx.channel().attr(ATTR_KEY_METRICS).get();
|
||||
metrics.incrementWrote(((ByteBuf) msg).writableBytes());
|
||||
ctx.write(msg, promise);
|
||||
ctx.write(msg, promise).addListener(CLOSE_ON_FAILURE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.slf4j.LoggerFactory;
|
||||
import java.util.List;
|
||||
|
||||
import static io.moquette.spi.impl.Utils.messageId;
|
||||
import static io.netty.channel.ChannelFutureListener.CLOSE_ON_FAILURE;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -100,6 +101,6 @@ public class MQTTMessageLogger extends ChannelDuplexHandler {
|
||||
@Override
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||
logMQTTMessage(ctx, msg, "C<-B");
|
||||
ctx.write(msg, promise);
|
||||
ctx.write(msg, promise).addListener(CLOSE_ON_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ import io.netty.channel.ChannelPromise;
|
||||
import io.netty.util.Attribute;
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
import static io.netty.channel.ChannelFutureListener.CLOSE_ON_FAILURE;
|
||||
|
||||
public class MessageMetricsHandler extends ChannelDuplexHandler {
|
||||
|
||||
private static final AttributeKey<MessageMetrics> ATTR_KEY_METRICS = AttributeKey.valueOf("MessageMetrics");
|
||||
@@ -52,7 +54,7 @@ public class MessageMetricsHandler extends ChannelDuplexHandler {
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||
MessageMetrics metrics = ctx.channel().attr(ATTR_KEY_METRICS).get();
|
||||
metrics.incrementWrote(1);
|
||||
ctx.write(msg, promise);
|
||||
ctx.write(msg, promise).addListener(CLOSE_ON_FAILURE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -22,9 +22,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class DurableSession extends ClientSession {
|
||||
|
||||
@@ -51,20 +49,6 @@ public class DurableSession extends ClientSession {
|
||||
}
|
||||
}
|
||||
|
||||
class InboundFlightZone {
|
||||
|
||||
final Map<Integer, IMessagesStore.StoredMessage> inboundFlightMessages = new ConcurrentHashMap<>();
|
||||
|
||||
IMessagesStore.StoredMessage lookup(int messageID) {
|
||||
return inboundFlightMessages.get(messageID);
|
||||
}
|
||||
|
||||
void waitingRel(int messageID, IMessagesStore.StoredMessage msg) {
|
||||
inboundFlightMessages.put(messageID, msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DurableSession.class);
|
||||
|
||||
private final ISessionsStore sessionsStore;
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2017 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
|
||||
package io.moquette.spi;
|
||||
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2017 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.spi;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
class InboundFlightZone {
|
||||
|
||||
private final Map<Integer, IMessagesStore.StoredMessage> inboundFlightMessages = new ConcurrentHashMap<>();
|
||||
|
||||
IMessagesStore.StoredMessage lookup(int messageID) {
|
||||
return inboundFlightMessages.get(messageID);
|
||||
}
|
||||
|
||||
void waitingRel(int messageID, IMessagesStore.StoredMessage msg) {
|
||||
inboundFlightMessages.put(messageID, msg);
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ public class TransientSession extends ClientSession {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TransientSession.class);
|
||||
|
||||
private Queue<StoredMessage> messagesQueue = new LinkedList<>();
|
||||
private Queue<StoredMessage> messagesQueue = new ArrayDeque<>();
|
||||
|
||||
private final AtomicInteger packetGenerator = new AtomicInteger(1);
|
||||
|
||||
@@ -43,7 +43,7 @@ public class TransientSession extends ClientSession {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {}
|
||||
public void disconnect() { }
|
||||
|
||||
@Override
|
||||
public boolean isCleanSession() {
|
||||
|
||||
@@ -18,10 +18,12 @@ package io.moquette.spi.impl;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
final class DebugUtils {
|
||||
|
||||
static String payload2Str(ByteBuf content) {
|
||||
return new String(content.copy().array());
|
||||
return new String(content.copy().array(), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private DebugUtils() {
|
||||
|
||||
@@ -62,11 +62,12 @@ class MessagesPublisher {
|
||||
void publish2Subscribers(IMessagesStore.StoredMessage pubMsg, Topic topic, int messageID) {
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("Sending publish message to subscribers. ClientId={}, topic={}, messageId={}, payload={}, " +
|
||||
"subscriptionTree={}", pubMsg.getClientID(), topic, messageID, DebugUtils.payload2Str(pubMsg.getPayload()),
|
||||
"subscriptionTree={}", pubMsg.getClientID(), topic, messageID,
|
||||
DebugUtils.payload2Str(pubMsg.getPayload()),
|
||||
subscriptions.dumpTree());
|
||||
} else {
|
||||
LOG.info("Sending publish message to subscribers. ClientId={}, topic={}, messageId={}", pubMsg.getClientID(), topic,
|
||||
messageID);
|
||||
LOG.info("Sending publish message to subscribers. ClientId={}, topic={}, messageId={}",
|
||||
pubMsg.getClientID(), topic, messageID);
|
||||
}
|
||||
publish2Subscribers(pubMsg, topic);
|
||||
}
|
||||
@@ -82,7 +83,8 @@ class MessagesPublisher {
|
||||
ClientSession targetSession = this.sessionsRepository.sessionForClient(sub.getClientId());
|
||||
|
||||
boolean targetIsActive = this.connectionDescriptors.isConnected(sub.getClientId());
|
||||
//TODO move all this logic into messageSender, which puts into the flightZone only the messages that pull out of the queue.
|
||||
// TODO move all this logic into messageSender, which puts into the flightZone only the messages
|
||||
// that pull out of the queue.
|
||||
if (targetIsActive) {
|
||||
LOG.debug("Sending PUBLISH message to active subscriber. CId={}, topicFilter={}, qos={}",
|
||||
sub.getClientId(), sub.getTopicFilter(), qos);
|
||||
|
||||
@@ -50,12 +50,12 @@ class PersistentQueueMessageSender {
|
||||
|
||||
if (!messageDelivered) {
|
||||
if (qos != AT_MOST_ONCE && !clientsession.isCleanSession()) {
|
||||
LOG.warn("PUBLISH message could not be delivered. It will be stored. MessageId={}, CId={}, topic={}, "
|
||||
+ "qos={}, removeTemporaryQoS2={}", messageId, clientId, topicName, qos, false);
|
||||
LOG.warn("PUBLISH message could not be delivered. It will be stored. MessageId={}, CId={}, topic={}, " +
|
||||
"qos={}, removeTemporaryQoS2={}", messageId, clientId, topicName, qos, false);
|
||||
clientsession.enqueue(asStoredMessage(pubMessage));
|
||||
} else {
|
||||
LOG.warn("PUBLISH message could not be delivered. It will be discarded. MessageId={}, CId={}, topic={}, " +
|
||||
"qos={}, removeTemporaryQoS2={}", messageId, clientId, topicName, qos, true);
|
||||
LOG.warn("PUBLISH message could not be delivered. It will be discarded. MessageId={}, CId={}, " +
|
||||
"topic={}, qos={}, removeTemporaryQoS2={}", messageId, clientId, topicName, qos, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
@@ -45,6 +46,8 @@ import static io.moquette.server.ConnectionDescriptor.ConnectionState.*;
|
||||
import static io.moquette.spi.impl.InternalRepublisher.createPublishForQos;
|
||||
import static io.moquette.spi.impl.Utils.messageId;
|
||||
import static io.moquette.spi.impl.Utils.readBytesAndRewind;
|
||||
import static io.netty.channel.ChannelFutureListener.CLOSE_ON_FAILURE;
|
||||
import static io.netty.channel.ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE;
|
||||
import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.*;
|
||||
import static io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader.from;
|
||||
import static io.netty.handler.codec.mqtt.MqttQoS.*;
|
||||
@@ -92,37 +95,6 @@ public class ProtocolProcessor {
|
||||
STORED, VERIFIED
|
||||
}
|
||||
|
||||
private class RunningSubscription {
|
||||
|
||||
final String clientID;
|
||||
final long packetId;
|
||||
|
||||
RunningSubscription(String clientID, long packetId) {
|
||||
this.clientID = clientID;
|
||||
this.packetId = packetId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
|
||||
RunningSubscription that = (RunningSubscription) o;
|
||||
|
||||
return packetId == that.packetId
|
||||
&& (clientID != null ? clientID.equals(that.clientID) : that.clientID == null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = clientID != null ? clientID.hashCode() : 0;
|
||||
result = 31 * result + (int) (packetId ^ (packetId >>> 32));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ProtocolProcessor.class);
|
||||
|
||||
private ConnectionDescriptorStore connectionDescriptors;
|
||||
@@ -157,15 +129,15 @@ public class ProtocolProcessor {
|
||||
public void init(ISubscriptionsDirectory subscriptions, IMessagesStore storageService, ISessionsStore sessionsStore,
|
||||
IAuthenticator authenticator, boolean allowAnonymous, IAuthorizator authorizator,
|
||||
BrokerInterceptor interceptor, SessionsRepository sessionsRepository) {
|
||||
init(subscriptions, storageService, sessionsStore, authenticator, allowAnonymous, false, authorizator,
|
||||
interceptor, sessionsRepository);
|
||||
init(subscriptions, storageService, sessionsStore, authenticator, allowAnonymous, false,
|
||||
authorizator, interceptor, sessionsRepository);
|
||||
}
|
||||
|
||||
public void init(ISubscriptionsDirectory subscriptions, IMessagesStore storageService, ISessionsStore sessionsStore,
|
||||
IAuthenticator authenticator, boolean allowAnonymous, boolean allowZeroByteClientId,
|
||||
IAuthorizator authorizator, BrokerInterceptor interceptor, SessionsRepository sessionsRepository) {
|
||||
init(new ConnectionDescriptorStore(sessionsRepository), subscriptions, storageService, sessionsStore, authenticator,
|
||||
allowAnonymous, allowZeroByteClientId, authorizator, interceptor, sessionsRepository);
|
||||
init(new ConnectionDescriptorStore(sessionsRepository), subscriptions, storageService, sessionsStore,
|
||||
authenticator, allowAnonymous, allowZeroByteClientId, authorizator, interceptor, sessionsRepository);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -235,8 +207,8 @@ public class ProtocolProcessor {
|
||||
MqttConnAckMessage badProto = connAck(CONNECTION_REFUSED_UNACCEPTABLE_PROTOCOL_VERSION);
|
||||
|
||||
LOG.error("MQTT protocol version is not valid. CId={}", clientId);
|
||||
channel.writeAndFlush(badProto);
|
||||
channel.close();
|
||||
channel.writeAndFlush(badProto).addListener(FIRE_EXCEPTION_ON_FAILURE);
|
||||
channel.close().addListener(CLOSE_ON_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -245,8 +217,8 @@ public class ProtocolProcessor {
|
||||
if (!cleanSession || !this.allowZeroByteClientId) {
|
||||
MqttConnAckMessage badId = connAck(CONNECTION_REFUSED_IDENTIFIER_REJECTED);
|
||||
|
||||
channel.writeAndFlush(badId);
|
||||
channel.close();
|
||||
channel.writeAndFlush(badId).addListener(FIRE_EXCEPTION_ON_FAILURE);
|
||||
channel.close().addListener(CLOSE_ON_FAILURE);
|
||||
LOG.error("The MQTT client ID cannot be empty. Username={}", payload.userName());
|
||||
return;
|
||||
}
|
||||
@@ -258,7 +230,7 @@ public class ProtocolProcessor {
|
||||
}
|
||||
|
||||
if (!login(channel, msg, clientId)) {
|
||||
channel.close();
|
||||
channel.close().addListener(CLOSE_ON_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -275,25 +247,25 @@ public class ProtocolProcessor {
|
||||
initializeKeepAliveTimeout(channel, msg, clientId);
|
||||
storeWillMessage(msg, clientId);
|
||||
if (!sendAck(descriptor, msg, clientId)) {
|
||||
channel.close();
|
||||
channel.close().addListener(CLOSE_ON_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
m_interceptor.notifyClientConnected(msg);
|
||||
|
||||
if (!descriptor.assignState(SENDACK, SESSION_CREATED)) {
|
||||
channel.close();
|
||||
channel.close().addListener(CLOSE_ON_FAILURE);
|
||||
return;
|
||||
}
|
||||
final ClientSession clientSession = this.sessionsRepository.createOrLoadClientSession(clientId, cleanSession);
|
||||
|
||||
if (!republish(descriptor, msg, clientSession)) {
|
||||
channel.close();
|
||||
channel.close().addListener(CLOSE_ON_FAILURE);
|
||||
return;
|
||||
}
|
||||
final boolean success = descriptor.assignState(MESSAGES_REPUBLISHED, ESTABLISHED);
|
||||
if (!success) {
|
||||
channel.close();
|
||||
channel.close().addListener(CLOSE_ON_FAILURE);
|
||||
}
|
||||
|
||||
LOG.info("CONNECT message processed CId={}, username={}", clientId, payload.userName());
|
||||
@@ -319,7 +291,7 @@ public class ProtocolProcessor {
|
||||
if (msg.variableHeader().hasUserName()) {
|
||||
byte[] pwd = null;
|
||||
if (msg.variableHeader().hasPassword()) {
|
||||
pwd = msg.payload().password().getBytes();
|
||||
pwd = msg.payload().password().getBytes(StandardCharsets.UTF_8);
|
||||
} else if (!this.allowAnonymous) {
|
||||
LOG.error("Client didn't supply any password and MQTT anonymous mode is disabled CId={}", clientId);
|
||||
failedCredentials(channel);
|
||||
@@ -386,12 +358,12 @@ public class ProtocolProcessor {
|
||||
if (msg.variableHeader().isWillFlag()) {
|
||||
MqttQoS willQos = MqttQoS.valueOf(msg.variableHeader().willQos());
|
||||
LOG.info("Configuring MQTT last will and testament CId={}, willQos={}, willTopic={}, willRetain={}",
|
||||
clientId, willQos, msg.payload().willTopic(), msg.variableHeader().isWillRetain());
|
||||
byte[] willPayload = msg.payload().willMessage().getBytes();
|
||||
clientId, willQos, msg.payload().willTopic(), msg.variableHeader().isWillRetain());
|
||||
byte[] willPayload = msg.payload().willMessage().getBytes(StandardCharsets.UTF_8);
|
||||
ByteBuffer bb = (ByteBuffer) ByteBuffer.allocate(willPayload.length).put(willPayload).flip();
|
||||
// save the will testament in the clientID store
|
||||
WillMessage will = new WillMessage(msg.payload().willTopic(), bb, msg.variableHeader().isWillRetain(),
|
||||
willQos);
|
||||
willQos);
|
||||
m_willStore.put(clientId, will);
|
||||
LOG.info("MQTT last will and testament has been configured. CId={}", clientId);
|
||||
}
|
||||
@@ -414,7 +386,8 @@ public class ProtocolProcessor {
|
||||
}
|
||||
|
||||
private void failedCredentials(Channel session) {
|
||||
session.writeAndFlush(connAck(CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD));
|
||||
session.writeAndFlush(connAck(CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD))
|
||||
.addListener(FIRE_EXCEPTION_ON_FAILURE);
|
||||
LOG.info("Client {} failed to connect with bad username or password.", session);
|
||||
}
|
||||
|
||||
@@ -435,7 +408,8 @@ public class ProtocolProcessor {
|
||||
StoredMessage inflightMsg = targetSession.inFlightAcknowledged(messageID);
|
||||
|
||||
String topic = inflightMsg.getTopic();
|
||||
InterceptAcknowledgedMessage wrapped = new InterceptAcknowledgedMessage(inflightMsg, topic, username, messageID);
|
||||
InterceptAcknowledgedMessage wrapped = new InterceptAcknowledgedMessage(inflightMsg, topic, username,
|
||||
messageID);
|
||||
m_interceptor.notifyMessageAcknowledged(wrapped);
|
||||
}
|
||||
|
||||
@@ -517,19 +491,19 @@ public class ProtocolProcessor {
|
||||
/**
|
||||
* Specialized version to publish will testament message.
|
||||
*/
|
||||
private void forwardPublishWill(WillMessage will, String clientID) {
|
||||
LOG.info("Publishing will message. CId={}, topic={}", clientID, will.getTopic());
|
||||
// it has just to publish the message downstream to the subscribers
|
||||
// NB it's a will publish, it needs a PacketIdentifier for this conn, default to 1
|
||||
IMessagesStore.StoredMessage tobeStored = asStoredMessage(will);
|
||||
tobeStored.setClientID(clientID);
|
||||
Topic topic = new Topic(tobeStored.getTopic());
|
||||
this.messagesPublisher.publish2Subscribers(tobeStored, topic);
|
||||
private void forwardPublishWill(WillMessage will, String clientID) {
|
||||
LOG.info("Publishing will message. CId={}, topic={}", clientID, will.getTopic());
|
||||
// it has just to publish the message downstream to the subscribers
|
||||
// NB it's a will publish, it needs a PacketIdentifier for this conn, default to 1
|
||||
IMessagesStore.StoredMessage tobeStored = asStoredMessage(will);
|
||||
tobeStored.setClientID(clientID);
|
||||
Topic topic = new Topic(tobeStored.getTopic());
|
||||
this.messagesPublisher.publish2Subscribers(tobeStored, topic);
|
||||
|
||||
//Stores retained message to the topic
|
||||
if(will.isRetained()) {
|
||||
m_messagesStore.storeRetained(topic, tobeStored);
|
||||
}
|
||||
//Stores retained message to the topic
|
||||
if (will.isRetained()) {
|
||||
m_messagesStore.storeRetained(topic, tobeStored);
|
||||
}
|
||||
}
|
||||
|
||||
static MqttQoS lowerQosToTheSubscriptionDesired(Subscription sub, MqttQoS qos) {
|
||||
@@ -565,7 +539,7 @@ public class ProtocolProcessor {
|
||||
|
||||
MqttFixedHeader pubRelHeader = new MqttFixedHeader(MqttMessageType.PUBREL, false, AT_LEAST_ONCE, false, 0);
|
||||
MqttMessage pubRelMessage = new MqttMessage(pubRelHeader, from(messageID));
|
||||
channel.writeAndFlush(pubRelMessage);
|
||||
channel.writeAndFlush(pubRelMessage).addListener(FIRE_EXCEPTION_ON_FAILURE);
|
||||
}
|
||||
|
||||
public void processPubComp(Channel channel, MqttMessage msg) {
|
||||
@@ -589,7 +563,7 @@ public class ProtocolProcessor {
|
||||
final ConnectionDescriptor existingDescriptor = this.connectionDescriptors.getConnection(clientID);
|
||||
if (existingDescriptor == null) {
|
||||
// another client with same ID removed the descriptor, we must exit
|
||||
channel.close();
|
||||
channel.close().addListener(CLOSE_ON_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -714,7 +688,7 @@ public class ProtocolProcessor {
|
||||
boolean validTopic = topic.isValid();
|
||||
if (!validTopic) {
|
||||
// close the connection, not valid topicFilter is a protocol violation
|
||||
channel.close();
|
||||
channel.close().addListener(CLOSE_ON_FAILURE);
|
||||
LOG.error("Topic filter is not valid. CId={}, topics={}, badTopicFilter={}", clientID, topics, topic);
|
||||
return;
|
||||
}
|
||||
@@ -732,7 +706,7 @@ public class ProtocolProcessor {
|
||||
MqttUnsubAckMessage ackMessage = new MqttUnsubAckMessage(fixedHeader, from(messageID));
|
||||
|
||||
LOG.info("Sending UNSUBACK message. CId={}, topics={}, messageId={}", clientID, topics, messageID);
|
||||
channel.writeAndFlush(ackMessage);
|
||||
channel.writeAndFlush(ackMessage).addListener(FIRE_EXCEPTION_ON_FAILURE);
|
||||
}
|
||||
|
||||
public void processSubscribe(Channel channel, MqttSubscribeMessage msg) {
|
||||
@@ -766,7 +740,7 @@ public class ProtocolProcessor {
|
||||
}
|
||||
|
||||
LOG.info("Sending SUBACK response CId={}, messageId={}", clientID, messageID);
|
||||
channel.writeAndFlush(ackMessage);
|
||||
channel.writeAndFlush(ackMessage).addListener(FIRE_EXCEPTION_ON_FAILURE);
|
||||
|
||||
// fire the persisted messages in session
|
||||
for (Subscription subscription : newSubscriptions) {
|
||||
@@ -879,9 +853,10 @@ public class ProtocolProcessor {
|
||||
emptyQueue = true;
|
||||
} else {
|
||||
// recreate a publish from stored publish in queue
|
||||
MqttPublishMessage pubMsg = createPublishForQos(msg.msg.getTopic(), msg.msg.getQos(), msg.msg.getPayload(),
|
||||
msg.msg.isRetained(), msg.messageId);
|
||||
channel.write(pubMsg);
|
||||
MqttPublishMessage pubMsg = createPublishForQos(msg.msg.getTopic(), msg.msg.getQos(),
|
||||
msg.msg.getPayload(), msg.msg.isRetained(),
|
||||
msg.messageId);
|
||||
channel.write(pubMsg).addListener(FIRE_EXCEPTION_ON_FAILURE);
|
||||
}
|
||||
}
|
||||
channel.flush();
|
||||
|
||||
@@ -222,8 +222,9 @@ public class ProtocolProcessorBootstrapper {
|
||||
.getConstructor(constructorArgClass)
|
||||
.newInstance(props);
|
||||
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException ex) {
|
||||
LOG.warn("Unable to invoke constructor with {} argument. ClassName={}, interfaceName={}, cause={}, errorMessage={}",
|
||||
constructorArgClass.getName(), className, intrface.getName(), ex.getCause(), ex.getMessage());
|
||||
LOG.warn("Unable to invoke constructor with {} argument. ClassName={}, interfaceName={}, cause={}, " +
|
||||
"errorMessage={}", constructorArgClass.getName(), className, intrface.getName(), ex.getCause(),
|
||||
ex.getMessage());
|
||||
return null;
|
||||
} catch (NoSuchMethodException | InvocationTargetException e) {
|
||||
try {
|
||||
@@ -232,10 +233,11 @@ public class ProtocolProcessorBootstrapper {
|
||||
instance = this.getClass().getClassLoader()
|
||||
.loadClass(className)
|
||||
.asSubclass(intrface)
|
||||
.newInstance();
|
||||
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException ex) {
|
||||
LOG.error("Unable to invoke default constructor. ClassName={}, interfaceName={}, cause={}, errorMessage={}",
|
||||
className, intrface.getName(), ex.getCause(), ex.getMessage());
|
||||
.getDeclaredConstructor().newInstance();
|
||||
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException |
|
||||
NoSuchMethodException | InvocationTargetException ex) {
|
||||
LOG.error("Unable to invoke default constructor. ClassName={}, interfaceName={}, cause={}, " +
|
||||
"errorMessage={}", className, intrface.getName(), ex.getCause(), ex.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2017 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
|
||||
package io.moquette.spi.impl;
|
||||
|
||||
class RunningSubscription {
|
||||
|
||||
final String clientID;
|
||||
final long packetId;
|
||||
|
||||
RunningSubscription(String clientID, long packetId) {
|
||||
this.clientID = clientID;
|
||||
this.packetId = packetId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
|
||||
RunningSubscription that = (RunningSubscription) o;
|
||||
|
||||
return packetId == that.packetId
|
||||
&& (clientID != null ? clientID.equals(that.clientID) : that.clientID == null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = clientID != null ? clientID.hashCode() : 0;
|
||||
result = 31 * result + (int) (packetId ^ (packetId >>> 32));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,8 @@ import java.util.concurrent.TimeUnit;
|
||||
public class SessionsRepository {
|
||||
|
||||
/**
|
||||
* Task to be scheduled to execute the cleaning of persisted sessions (clean flag=false) older than a defined period.
|
||||
* Task to be scheduled to execute the cleaning of persisted sessions (clean flag=false) older than a
|
||||
* defined period.
|
||||
*/
|
||||
private class SessionCleanerTask implements Runnable {
|
||||
|
||||
@@ -55,6 +56,7 @@ public class SessionsRepository {
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
@SuppressWarnings("FutureReturnValueIgnored")
|
||||
public void init() {
|
||||
SessionCleanerTask cleanerTask = new SessionCleanerTask();
|
||||
this.scheduler.schedule(cleanerTask, 1, TimeUnit.HOURS);
|
||||
|
||||
@@ -18,11 +18,18 @@ package io.moquette.spi.impl.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import java.io.*;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.nio.file.Files;
|
||||
import java.text.ParseException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
* Parses the acl configuration file. If a line starts with # it's comment. Blank lines are skipped.
|
||||
* The format is "topic [read|write|readwrite] {topic name}"
|
||||
@@ -53,9 +60,9 @@ public final class ACLFileParser {
|
||||
return AuthorizationsCollector.emptyImmutableCollector();
|
||||
}
|
||||
try {
|
||||
FileReader reader = new FileReader(file);
|
||||
Reader reader = Files.newBufferedReader(file.toPath(), UTF_8);
|
||||
return parse(reader);
|
||||
} catch (FileNotFoundException fex) {
|
||||
} catch (IOException fex) {
|
||||
LOG.warn(
|
||||
String.format(
|
||||
"parsing not existing file %s, so fallback on default configuration!",
|
||||
@@ -88,7 +95,8 @@ public final class ACLFileParser {
|
||||
Pattern emptyLine = Pattern.compile("^\\s*$");
|
||||
Pattern commentLine = Pattern.compile("^#.*"); // As spec, comment lines should start with '#'
|
||||
Pattern invalidCommentLine = Pattern.compile("^\\s*#.*");
|
||||
Pattern endLineComment = Pattern.compile("^([\\w\\s\\/\\+]+#?)(\\s*#.*)$"); // This pattern has a dependency on filtering `commentLine`.
|
||||
// This pattern has a dependency on filtering `commentLine`.
|
||||
Pattern endLineComment = Pattern.compile("^([\\w\\s\\/\\+]+#?)(\\s*#.*)$");
|
||||
Matcher endLineCommentMatcher;
|
||||
|
||||
try {
|
||||
|
||||
@@ -20,6 +20,7 @@ import io.moquette.spi.security.IAuthenticator;
|
||||
|
||||
public class AcceptAllAuthenticator implements IAuthenticator {
|
||||
|
||||
@Override
|
||||
public boolean checkValid(String clientId, String username, byte[] password) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -15,10 +15,7 @@
|
||||
*/
|
||||
package io.moquette.spi.impl.subscriptions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
class CNode {
|
||||
|
||||
@@ -41,7 +38,7 @@ class CNode {
|
||||
boolean anyChildrenMatch(Token token) {
|
||||
for (INode iNode : children) {
|
||||
final CNode child = iNode.mainNode();
|
||||
if (child.equals(token)) {
|
||||
if (child.equalsToken(token)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -55,15 +52,20 @@ class CNode {
|
||||
INode childOf(Token token) {
|
||||
for (INode iNode : children) {
|
||||
final CNode child = iNode.mainNode();
|
||||
if (child.equals(token)) {
|
||||
if (child.equalsToken(token)) {
|
||||
return iNode;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Asked for a token that doesn't exists in any child [" + token + "]");
|
||||
}
|
||||
|
||||
private boolean equals(Token token) {
|
||||
return this.token != null && this.token.equals(token);
|
||||
private boolean equalsToken(Token token) {
|
||||
return token != null && this.token != null && this.token.equals(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(token);
|
||||
}
|
||||
|
||||
CNode copy() {
|
||||
|
||||
+10
-75
@@ -17,12 +17,10 @@ package io.moquette.spi.impl.subscriptions;
|
||||
|
||||
import io.moquette.spi.ClientSession;
|
||||
import io.moquette.spi.impl.SessionsRepository;
|
||||
import io.netty.util.internal.StringUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
@@ -36,66 +34,18 @@ public class CTrieSubscriptionDirectory implements ISubscriptionsDirectory {
|
||||
INode root;
|
||||
private volatile SessionsRepository sessionsRepository;
|
||||
|
||||
private interface IVisitor<T> {
|
||||
interface IVisitor<T> {
|
||||
|
||||
void visit(CNode node, int deep);
|
||||
|
||||
T getResult();
|
||||
}
|
||||
|
||||
private class DumpTreeVisitor implements IVisitor<String> {
|
||||
|
||||
String s = "";
|
||||
|
||||
@Override
|
||||
public void visit(CNode node, int deep) {
|
||||
String indentTabs = indentTabs(deep);
|
||||
s += indentTabs + (node.token == null ? "''" : node.token.toString()) + prettySubscriptions(node) + "\n";
|
||||
}
|
||||
|
||||
private String prettySubscriptions(CNode node) {
|
||||
if (node instanceof TNode) {
|
||||
return "TNode";
|
||||
}
|
||||
if (node.subscriptions.isEmpty()) {
|
||||
return StringUtil.EMPTY_STRING;
|
||||
}
|
||||
StringBuilder subScriptionsStr = new StringBuilder(" ~~[");
|
||||
int counter = 0;
|
||||
for (Subscription couple : node.subscriptions) {
|
||||
subScriptionsStr
|
||||
.append("{filter=").append(couple.topicFilter).append(", ")
|
||||
.append("client='").append(couple.clientId).append("'}");
|
||||
counter++;
|
||||
if (counter < node.subscriptions.size()) {
|
||||
subScriptionsStr.append(";");
|
||||
}
|
||||
}
|
||||
return subScriptionsStr.append("]").toString();
|
||||
}
|
||||
|
||||
private String indentTabs(int deep) {
|
||||
StringBuilder s = new StringBuilder();
|
||||
if (deep > 0) {
|
||||
s.append(" ");
|
||||
for (int i = 0; i < deep - 1; i++) {
|
||||
s.append("| ");
|
||||
}
|
||||
s.append("|-");
|
||||
}
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResult() {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
private enum Action {
|
||||
OK, REPEAT
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(SessionsRepository sessionsRepository) {
|
||||
LOG.info("Initializing CTrie");
|
||||
final CNode mainNode = new CNode();
|
||||
@@ -134,13 +84,11 @@ public class CTrieSubscriptionDirectory implements ISubscriptionsDirectory {
|
||||
return Optional.of(inode.mainNode());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<Subscription> matches(Topic topic) {
|
||||
return new ArrayList<>(match(topic));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Given a topic string return the clients subscriptions that matches it. Topic string can't
|
||||
* contain character # and + because they are reserved to listeners subscriptions, and not topic
|
||||
@@ -177,7 +125,7 @@ public class CTrieSubscriptionDirectory implements ISubscriptionsDirectory {
|
||||
|
||||
Set<Subscription> recursiveMatch(Topic topic, INode inode) {
|
||||
CNode cnode = inode.mainNode();
|
||||
if (cnode.token == Token.MULTI) {
|
||||
if (Token.MULTI.equals(cnode.token)) {
|
||||
return cnode.subscriptions;
|
||||
}
|
||||
if (topic.isEmpty()) {
|
||||
@@ -187,10 +135,10 @@ public class CTrieSubscriptionDirectory implements ISubscriptionsDirectory {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
final Token token = topic.headToken();
|
||||
if (!(cnode.token == Token.SINGLE || cnode.token.equals(token) || cnode.token == ROOT)) {
|
||||
if (!(Token.SINGLE.equals(cnode.token) || cnode.token.equals(token) || ROOT.equals(cnode.token))) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
Topic remainingTopic = (cnode.token == ROOT) ? topic : topic.exceptHeadToken();
|
||||
Topic remainingTopic = (ROOT.equals(cnode.token)) ? topic : topic.exceptHeadToken();
|
||||
Set<Subscription> subscriptions = new HashSet<>();
|
||||
if (remainingTopic.isEmpty()) {
|
||||
subscriptions.addAll(cnode.subscriptions);
|
||||
@@ -219,6 +167,7 @@ public class CTrieSubscriptionDirectory implements ISubscriptionsDirectory {
|
||||
return iParent.compareAndSet(iParent.mainNode(), updatedCnode) ? Action.OK : Action.REPEAT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Subscription newSubscription) {
|
||||
Action res;
|
||||
do {
|
||||
@@ -282,14 +231,13 @@ public class CTrieSubscriptionDirectory implements ISubscriptionsDirectory {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Removes subscription from CTrie, adds TNode when the last client unsubscribes, then calls for cleanTomb in a
|
||||
* seperate atomic CAS operation.
|
||||
*
|
||||
* separate atomic CAS operation.
|
||||
*
|
||||
* @param topic
|
||||
* @param clientID
|
||||
*/
|
||||
@Override
|
||||
public void removeSubscription(Topic topic, String clientID) {
|
||||
Action res;
|
||||
do {
|
||||
@@ -329,27 +277,14 @@ public class CTrieSubscriptionDirectory implements ISubscriptionsDirectory {
|
||||
}
|
||||
}
|
||||
|
||||
private class SubscriptionCounterVisitor implements IVisitor<Integer> {
|
||||
|
||||
AtomicInteger accumulator = new AtomicInteger(0);
|
||||
|
||||
@Override
|
||||
public void visit(CNode node, int deep) {
|
||||
accumulator.addAndGet(node.subscriptions.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getResult() {
|
||||
return accumulator.get();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
SubscriptionCounterVisitor visitor = new SubscriptionCounterVisitor();
|
||||
dfsVisit(this.root, visitor, 0);
|
||||
return visitor.getResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String dumpTree() {
|
||||
DumpTreeVisitor visitor = new DumpTreeVisitor();
|
||||
dfsVisit(this.root, visitor, 0);
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2017 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.spi.impl.subscriptions;
|
||||
|
||||
import io.netty.util.internal.StringUtil;
|
||||
|
||||
class DumpTreeVisitor implements CTrieSubscriptionDirectory.IVisitor<String> {
|
||||
|
||||
String s = "";
|
||||
|
||||
@Override
|
||||
public void visit(CNode node, int deep) {
|
||||
String indentTabs = indentTabs(deep);
|
||||
s += indentTabs + (node.token == null ? "''" : node.token.toString()) + prettySubscriptions(node) + "\n";
|
||||
}
|
||||
|
||||
private String prettySubscriptions(CNode node) {
|
||||
if (node instanceof TNode) {
|
||||
return "TNode";
|
||||
}
|
||||
if (node.subscriptions.isEmpty()) {
|
||||
return StringUtil.EMPTY_STRING;
|
||||
}
|
||||
StringBuilder subScriptionsStr = new StringBuilder(" ~~[");
|
||||
int counter = 0;
|
||||
for (Subscription couple : node.subscriptions) {
|
||||
subScriptionsStr
|
||||
.append("{filter=").append(couple.topicFilter).append(", ")
|
||||
.append("client='").append(couple.clientId).append("'}");
|
||||
counter++;
|
||||
if (counter < node.subscriptions.size()) {
|
||||
subScriptionsStr.append(";");
|
||||
}
|
||||
}
|
||||
return subScriptionsStr.append("]").toString();
|
||||
}
|
||||
|
||||
private String indentTabs(int deep) {
|
||||
StringBuilder s = new StringBuilder();
|
||||
if (deep > 0) {
|
||||
s.append(" ");
|
||||
for (int i = 0; i < deep - 1; i++) {
|
||||
s.append("| ");
|
||||
}
|
||||
s.append("|-");
|
||||
}
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResult() {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2017 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.spi.impl.subscriptions;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
class SubscriptionCounterVisitor implements CTrieSubscriptionDirectory.IVisitor<Integer> {
|
||||
|
||||
private AtomicInteger accumulator = new AtomicInteger(0);
|
||||
|
||||
@Override
|
||||
public void visit(CNode node, int deep) {
|
||||
accumulator.addAndGet(node.subscriptions.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getResult() {
|
||||
return accumulator.get();
|
||||
}
|
||||
}
|
||||
@@ -35,11 +35,11 @@ public class Token {
|
||||
}
|
||||
|
||||
protected boolean match(Token t) {
|
||||
if (t == MULTI || t == SINGLE) {
|
||||
if (MULTI.equals(t) || SINGLE.equals(t)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this == MULTI || this == SINGLE) {
|
||||
if (MULTI.equals(this) || SINGLE.equals(this)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ public class Topic implements Serializable {
|
||||
int i = 0;
|
||||
for (; i < subscriptionTokens.size(); i++) {
|
||||
Token subToken = subscriptionTokens.get(i);
|
||||
if (subToken != Token.MULTI && subToken != Token.SINGLE) {
|
||||
if (!Token.MULTI.equals(subToken) && !Token.SINGLE.equals(subToken)) {
|
||||
if (i >= msgTokens.size()) {
|
||||
return false;
|
||||
}
|
||||
@@ -174,18 +174,14 @@ public class Topic implements Serializable {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (subToken == Token.MULTI) {
|
||||
if (Token.MULTI.equals(subToken)) {
|
||||
return true;
|
||||
}
|
||||
if (subToken == Token.SINGLE) {
|
||||
// skip a step forward
|
||||
}
|
||||
// if (Token.SINGLE.equals(subToken)) {
|
||||
// // skip a step forward
|
||||
// }
|
||||
}
|
||||
}
|
||||
// if last token was a SINGLE then treat it as an empty
|
||||
// if (subToken == Token.SINGLE && (i - msgTokens.size() == 1)) {
|
||||
// i--;
|
||||
// }
|
||||
return i == msgTokens.size();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user