Once the buffer is full, old messages are not deleted
+ *
+ * More information about these values can be found in the setter methods.
+ */
+ public DisconnectedBufferOptions() {
+ // Do Nothing.
+ }
+
+ public int getBufferSize() {
+ return bufferSize;
+ }
+
+ public void setBufferSize(int bufferSize) {
+ if (bufferSize < 1) {
+ throw new IllegalArgumentException();
+ }
+ this.bufferSize = bufferSize;
+ }
+
+ public boolean isBufferEnabled() {
+ return bufferEnabled;
+ }
+
+ public void setBufferEnabled(boolean bufferEnabled) {
+ this.bufferEnabled = bufferEnabled;
+ }
+
+ public boolean isPersistBuffer() {
+ return persistBuffer;
+ }
+
+ public void setPersistBuffer(boolean persistBuffer) {
+ this.persistBuffer = persistBuffer;
+ }
+
+ public boolean isDeleteOldestMessages() {
+ return deleteOldestMessages;
+ }
+
+ public void setDeleteOldestMessages(boolean deleteOldestMessages) {
+ this.deleteOldestMessages = deleteOldestMessages;
+ }
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/FunctionalMqttClient.java b/src/main/java/org/eclipse/paho/mqttv5/client/FunctionalMqttClient.java
new file mode 100644
index 0000000..f9c55d4
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/FunctionalMqttClient.java
@@ -0,0 +1,7 @@
+package org.eclipse.paho.mqttv5.client;
+
+@FunctionalInterface
+public interface FunctionalMqttClient {
+ int operation(int a, int b);
+
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/IMqttAsyncClient.java b/src/main/java/org/eclipse/paho/mqttv5/client/IMqttAsyncClient.java
new file mode 100644
index 0000000..6f64e0e
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/IMqttAsyncClient.java
@@ -0,0 +1,1151 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2018 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * James Sutton - MQTT V5 support
+ */
+
+package org.eclipse.paho.mqttv5.client;
+
+import org.eclipse.paho.mqttv5.client.util.Debug;
+import org.eclipse.paho.mqttv5.common.MqttException;
+import org.eclipse.paho.mqttv5.common.MqttMessage;
+import org.eclipse.paho.mqttv5.common.MqttPersistenceException;
+import org.eclipse.paho.mqttv5.common.MqttSecurityException;
+import org.eclipse.paho.mqttv5.common.MqttSubscription;
+import org.eclipse.paho.mqttv5.common.packet.MqttProperties;
+
+public interface IMqttAsyncClient {
+
+ /**
+ * Connects to an MQTT server using the default options.
+ *
+ * The default options are specified in {@link MqttConnectionOptions} class.
+ *
+ *
+ * @param userContext
+ * optional object used to pass context to the callback. Use null if
+ * not required.
+ * @param callback
+ * optional listener that will be notified when the connect
+ * completes. Use null if not required.
+ * @throws MqttSecurityException
+ * for security related problems
+ * @throws MqttException
+ * for non security related problems
+ * @return token used to track and wait for the connect to complete. The token
+ * will be passed to any callback that has been set.
+ * @see #connect(MqttConnectionOptions, Object, MqttActionListener)
+ */
+ public IMqttToken connect(Object userContext, MqttActionListener callback) throws MqttException, MqttSecurityException;
+
+ /**
+ * Connects to an MQTT server using the default options.
+ *
+ * The default options are specified in {@link MqttConnectionOptions} class.
+ *
+ *
+ * @throws MqttSecurityException
+ * for security related problems
+ * @throws MqttException
+ * for non security related problems
+ * @return token used to track and wait for the connect to complete. The token
+ * will be passed to the callback methods if a callback is set.
+ * @see #connect(MqttConnectionOptions, Object, MqttActionListener)
+ */
+ public IMqttToken connect() throws MqttException, MqttSecurityException;
+
+ /**
+ * Connects to an MQTT server using the provided connect options.
+ *
+ * The connection will be established using the options specified in the
+ * {@link MqttConnectionOptions} parameter.
+ *
+ *
+ * @param options
+ * a set of connection parameters that override the defaults.
+ * @throws MqttSecurityException
+ * for security related problems
+ * @throws MqttException
+ * for non security related problems
+ * @return token used to track and wait for the connect to complete. The token
+ * will be passed to any callback that has been set.
+ * @see #connect(MqttConnectionOptions, Object, MqttActionListener)
+ */
+ public IMqttToken connect(MqttConnectionOptions options) throws MqttException, MqttSecurityException;
+
+ /**
+ * Connects to an MQTT server using the specified options.
+ *
+ * The server to connect to is specified on the constructor. It is recommended
+ * to call {@link #setCallback(MqttCallback)} prior to connecting in order that
+ * messages destined for the client can be accepted as soon as the client is
+ * connected.
+ *
+ *
+ * The method returns control before the connect completes. Completion can be
+ * tracked by:
+ *
+ *
+ *
Waiting on the returned token {@link IMqttToken#waitForCompletion()}
+ * or
+ *
Passing in a callback {@link MqttActionListener}
+ *
+ *
+ * @param options
+ * a set of connection parameters that override the defaults.
+ * @param userContext
+ * optional object for used to pass context to the callback. Use null
+ * if not required.
+ * @param callback
+ * optional listener that will be notified when the connect
+ * completes. Use null if not required.
+ * @return token used to track and wait for the connect to complete. The token
+ * will be passed to any callback that has been set.
+ * @throws MqttSecurityException
+ * for security related problems
+ * @throws MqttException
+ * for non security related problems including communication errors
+ */
+ public IMqttToken connect(MqttConnectionOptions options, Object userContext, MqttActionListener callback)
+ throws MqttException, MqttSecurityException;
+
+ /**
+ * Disconnects from the server.
+ *
+ * An attempt is made to quiesce the client allowing outstanding work to
+ * complete before disconnecting. It will wait for a maximum of 30 seconds for
+ * work to quiesce before disconnecting. This method must not be called from
+ * inside {@link MqttCallback} methods.
+ *
+ *
+ * @param userContext
+ * optional object used to pass context to the callback. Use null if
+ * not required.
+ * @param callback
+ * optional listener that will be notified when the disconnect
+ * completes. Use null if not required.
+ * @return token used to track and wait for the disconnect to complete. The
+ * token will be passed to any callback that has been set.
+ * @throws MqttException
+ * for problems encountered while disconnecting
+ * @see #disconnect(long, Object, MqttActionListener, int, MqttProperties)
+ *
+ */
+ public IMqttToken disconnect(Object userContext, MqttActionListener callback) throws MqttException;
+
+ /**
+ * Disconnects from the server.
+ *
+ * An attempt is made to quiesce the client allowing outstanding work to
+ * complete before disconnecting. It will wait for a maximum of 30 seconds for
+ * work to quiesce before disconnecting. This method must not be called from
+ * inside {@link MqttCallback} methods.
+ *
+ *
+ * @return token used to track and wait for disconnect to complete. The token
+ * will be passed to any callback that has been set.
+ * @throws MqttException
+ * for problems encountered while disconnecting
+ * @see #disconnect(long, Object, MqttActionListener, int, MqttProperties)
+ */
+ public IMqttToken disconnect() throws MqttException;
+
+ /**
+ * Disconnects from the server.
+ *
+ * An attempt is made to quiesce the client allowing outstanding work to
+ * complete before disconnecting. It will wait for a maximum of the specified
+ * quiesce time for work to complete before disconnecting. This method must not
+ * be called from inside {@link MqttCallback} methods.
+ *
+ *
+ * @param quiesceTimeout
+ * the amount of time in milliseconds to allow for existing work to
+ * finish before disconnecting. A value of zero or less means the
+ * client will not quiesce.
+ * @return token used to track and wait for disconnect to complete. The token
+ * will be passed to the callback methods if a callback is set.
+ * @throws MqttException
+ * for problems encountered while disconnecting
+ * @see #disconnect(long, Object, MqttActionListener, int, MqttProperties)
+ */
+ public IMqttToken disconnect(long quiesceTimeout) throws MqttException;
+
+ /**
+ * Disconnects from the server.
+ *
+ * The client will wait for {@link MqttCallback} methods to complete. It will
+ * then wait for up to the quiesce timeout to allow for work which has already
+ * been initiated to complete. For instance when a QoS 2 message has started
+ * flowing to the server but the QoS 2 flow has not completed.It prevents new
+ * messages being accepted and does not send any messages that have been
+ * accepted but not yet started delivery across the network to the server. When
+ * work has completed or after the quiesce timeout, the client will disconnect
+ * from the server. If the cleanStart flag was set to false and is set to
+ * false the next time a connection is made QoS 1 and 2 messages that were not
+ * previously delivered will be delivered.
+ *
+ *
+ * This method must not be called from inside {@link MqttCallback} methods.
+ *
+ *
+ * The method returns control before the disconnect completes. Completion can be
+ * tracked by:
+ *
+ *
+ *
Waiting on the returned token {@link IMqttToken#waitForCompletion()}
+ * or
+ *
Passing in a callback {@link MqttActionListener}
+ *
+ *
+ * @param quiesceTimeout
+ * the amount of time in milliseconds to allow for existing work to
+ * finish before disconnecting. A value of zero or less means the
+ * client will not quiesce.
+ * @param userContext
+ * optional object used to pass context to the callback. Use null if
+ * not required.
+ * @param callback
+ * optional listener that will be notified when the disconnect
+ * completes. Use null if not required.
+ * @param reasonCode
+ * the disconnection reason code.
+ * @param disconnectProperties
+ * The {@link MqttProperties} to be sent.
+ * @return token used to track and wait for the connect to complete. The token
+ * will be passed to any callback that has been set.
+ * @throws MqttException
+ * for problems encountered while disconnecting
+ */
+ public IMqttToken disconnect(long quiesceTimeout, Object userContext, MqttActionListener callback, int reasonCode,
+ MqttProperties disconnectProperties) throws MqttException;
+
+ /**
+ * Disconnects from the server forcibly to reset all the states. Could be useful
+ * when disconnect attempt failed.
+ *
+ * Because the client is able to establish the TCP/IP connection to a none MQTT
+ * server and it will certainly fail to send the disconnect packet. It will wait
+ * for a maximum of 30 seconds for work to quiesce before disconnecting and wait
+ * for a maximum of 10 seconds for sending the disconnect packet to server.
+ *
+ * @throws MqttException
+ * if any unexpected error
+ * @since 0.4.1
+ */
+ public void disconnectForcibly() throws MqttException;
+
+ /**
+ * Disconnects from the server forcibly to reset all the states. Could be useful
+ * when disconnect attempt failed.
+ *
+ * Because the client is able to establish the TCP/IP connection to a none MQTT
+ * server and it will certainly fail to send the disconnect packet. It will wait
+ * for a maximum of 30 seconds for work to quiesce before disconnecting.
+ *
+ * @param disconnectTimeout
+ * the amount of time in milliseconds to allow send disconnect packet
+ * to server.
+ * @throws MqttException
+ * if any unexpected error
+ * @since 0.4.1
+ */
+ public void disconnectForcibly(long disconnectTimeout) throws MqttException;
+
+ /**
+ * Disconnects from the server forcibly to reset all the states. Could be useful
+ * when disconnect attempt failed.
+ *
+ * Because the client is able to establish the TCP/IP connection to a none MQTT
+ * server and it will certainly fail to send the disconnect packet.
+ *
+ * @param quiesceTimeout
+ * the amount of time in milliseconds to allow for existing work to
+ * finish before disconnecting. A value of zero or less means the
+ * client will not quiesce.
+ * @param disconnectTimeout
+ * the amount of time in milliseconds to allow send disconnect packet
+ * to server.
+ * @param reasonCode
+ * the disconnection reason code.
+ * @param disconnectProperties
+ * The {@link MqttProperties} to be sent.
+ * @throws MqttException
+ * if any unexpected error
+ * @since 0.4.1
+ */
+ public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout, int reasonCode,
+ MqttProperties disconnectProperties) throws MqttException;
+
+ /**
+ * Disconnects from the server forcibly to reset all the states. Could be useful
+ * when disconnect attempt failed.
+ *
+ * Because the client is able to establish the TCP/IP connection to a none MQTT
+ * server and it will certainly fail to send the disconnect packet.
+ *
+ * @param quiesceTimeout
+ * the amount of time in milliseconds to allow for existing work to
+ * finish before disconnecting. A value of zero or less means the
+ * client will not quiesce.
+ * @param disconnectTimeout
+ * the amount of time in milliseconds to allow send disconnect packet
+ * to server.
+ * @param sendDisconnectPacket
+ * if true, will send the disconnect packet to the server
+ * @throws MqttException
+ * if any unexpected error
+ */
+ public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout, boolean sendDisconnectPacket)
+ throws MqttException;
+
+ /**
+ * Determines if this client is currently connected to the server.
+ *
+ * @return true if connected, false otherwise.
+ */
+ public boolean isConnected();
+
+ /**
+ * Returns the client ID used by this client.
+ *
+ * All clients connected to the same server or server farm must have a unique
+ * ID.
+ *
+ *
+ * @return the client ID used by this client.
+ */
+ public String getClientId();
+
+ public void setClientId(String clientId);
+
+ /**
+ * Returns the address of the server used by this client.
+ *
+ * The format of the returned String is the same as that used on the
+ * constructor.
+ *
+ *
+ * @return the server's address, as a URI String.
+ * @see MqttAsyncClient#MqttAsyncClient(String, String)
+ */
+ public String getServerURI();
+
+ /**
+ * Returns the currently connected Server URI Implemented due to:
+ * https://bugs.eclipse.org/bugs/show_bug.cgi?id=481097
+ *
+ * Where getServerURI only returns the URI that was provided in
+ * MqttAsyncClient's constructor, getCurrentServerURI returns the URI of the
+ * Server that the client is currently connected to. This would be different in
+ * scenarios where multiple server URIs have been provided to the
+ * MqttConnectOptions.
+ *
+ * @return the currently connected server URI
+ */
+ public String getCurrentServerURI();
+
+ /*
+ * (non-Javadoc) Check and send a ping if needed.
By default, client sends
+ * PingReq to server to keep the connection to server. For some platforms which
+ * cannot use this mechanism, such as Android, developer needs to handle the
+ * ping request manually with this method.
+ *
+ * @throws MqttException for other errors encountered while publishing the
+ * message.
+ */
+ public IMqttToken checkPing(Object userContext, MqttActionListener callback) throws MqttException;
+
+ /**
+ * Subscribe to a topic, which may include wildcards.
+ *
+ * @see #subscribe(MqttSubscription[], Object, MqttActionListener,
+ * MqttProperties) throws MqttException
+ *
+ * @param topicFilter
+ * the topic to subscribe to, which can include wildcards.
+ * @param qos
+ * the maximum quality of service at which to subscribe. Messages
+ * published at a lower quality of service will be received at the
+ * published QoS. Messages published at a higher quality of service
+ * will be received using the QoS specified on the subscribe.
+ * @param userContext
+ * optional object used to pass context to the callback. Use null if
+ * not required.
+ * @param callback
+ * optional listener that will be notified when subscribe has
+ * completed
+ * @return token used to track and wait for the subscribe to complete. The token
+ * will be passed to callback methods if set.
+ * @throws MqttException
+ * if there was an error registering the subscription.
+ */
+ public IMqttToken subscribe(String topicFilter, int qos, Object userContext, MqttActionListener callback)
+ throws MqttException;
+
+ public IMqttToken subscribe(String[] topicFilters, int[] qos, Object userContext, MqttActionListener callback)
+ throws MqttException;
+
+ /**
+ * Subscribe to a topic, which may include wildcards.
+ *
+ * @see #subscribe(MqttSubscription[], Object, MqttActionListener,
+ * MqttProperties)
+ *
+ * @param topicFilter
+ * the topic to subscribe to, which can include wildcards.
+ * @param qos
+ * the maximum quality of service at which to subscribe. Messages
+ * published at a lower quality of service will be received at the
+ * published QoS. Messages published at a higher quality of service
+ * will be received using the QoS specified on the subscribe.
+ * @return token used to track and wait for the subscribe to complete. The token
+ * will be passed to callback methods if set.
+ * @throws MqttException
+ * if there was an error registering the subscription.
+ */
+ public IMqttToken subscribe(String topicFilter, int qos) throws MqttException;
+
+ public IMqttToken subscribe(String[] topicFilters, int[] qos) throws MqttException;
+
+ /**
+ * Subscribe to multiple topics, each of which may include wildcards.
+ *
+ *
+ * Provides an optimized way to subscribe to multiple topics compared to
+ * subscribing to each one individually.
+ *
+ *
+ * @see #subscribe(MqttSubscription[], Object, MqttActionListener,
+ * MqttProperties)
+ *
+ * @param subscriptions
+ * one or more {@link MqttSubscription} defining the subscription to
+ * be made.
+ * @return token used to track and wait for the subscribe to complete. The token
+ * will be passed to callback methods if set.
+ * @throws MqttException
+ * if there was an error registering the subscription.
+ */
+ public IMqttToken subscribe(MqttSubscription[] subscriptions) throws MqttException;
+
+ /**
+ * Subscribes to multiple topics, each of which may include wildcards.
+ *
+ * Provides an optimized way to subscribe to multiple topics compared to
+ * subscribing to each one individually.
+ *
+ *
+ * The {@link #setCallback(MqttCallback)} method should be called before this
+ * method, otherwise any received messages will be discarded.
+ *
+ *
+ * If (@link MqttConnectOptions#setCleanStart(boolean)} was set to true when
+ * when connecting to the server then the subscription remains in place until
+ * either:
+ *
+ *
+ *
+ *
The client disconnects
+ *
An unsubscribe method is called to un-subscribe the topic
+ *
+ *
+ *
+ * If (@link MqttConnectOptions#setCleanStart(boolean)} was set to false when
+ * connecting to the server then the subscription remains in place until either:
+ *
+ *
+ *
An unsubscribe method is called to unsubscribe the topic
+ *
The next time the client connects with cleanStart set to true
+ *
+ *
+ * With cleanStart set to false the MQTT server will store messages on behalf
+ * of the client when the client is not connected. The next time the client
+ * connects with the same client ID the server will deliver the stored
+ * messages to the client.
+ *
+ *
+ *
+ * The "topic filter" string used when subscribing may contain special
+ * characters, which allow you to subscribe to multiple topics at once.
+ *
+ *
+ * The topic level separator is used to introduce structure into the topic, and
+ * can therefore be specified within the topic for that purpose. The multi-level
+ * wildcard and single-level wildcard can be used for subscriptions, but they
+ * cannot be used within a topic by the publisher of a message.
+ *
+ *
Topic level separator
+ *
The forward slash (/) is used to separate each level within a topic tree
+ * and provide a hierarchical structure to the topic space. The use of the topic
+ * level separator is significant when the two wildcard characters are
+ * encountered in topics specified by subscribers.
+ *
+ *
Multi-level wildcard
+ *
+ *
+ * The number sign (#) is a wildcard character that matches any number of levels
+ * within a topic. For example, if you subscribe to
+ * finance/stock/ibm/#, you receive
+ * messages on these topics:
+ *
+ *
+ *
finance/stock/ibm
+ *
finance/stock/ibm/closingprice
+ *
finance/stock/ibm/currentprice
+ *
+ *
+ * The multi-level wildcard can represent zero or more levels. Therefore,
+ * finance/# can also match the singular finance, where
+ * # represents zero levels. The topic level separator is meaningless
+ * in this context, because there are no levels to separate.
+ *
+ *
+ *
+ * The multi-level wildcard can be specified only on its own or
+ * next to the topic level separator character. Therefore, # and
+ * finance/# are both valid, but finance# is not valid.
+ * The multi-level wildcard must be the last character used within the
+ * topic tree. For example, finance/# is valid but
+ * finance/#/closingprice is not valid.
+ *
+ *
+ *
+ *
Single-level wildcard
+ *
+ *
+ * The plus sign (+) is a wildcard character that matches only one topic level.
+ * For example, finance/stock/+ matches finance/stock/ibm and
+ * finance/stock/xyz, but not finance/stock/ibm/closingprice.
+ * Also, because the single-level wildcard matches only a single level,
+ * finance/+ does not match finance.
+ *
+ *
+ *
+ * Use the single-level wildcard at any level in the topic tree, and in
+ * conjunction with the multilevel wildcard. Specify the single-level wildcard
+ * next to the topic level separator, except when it is specified on its own.
+ * Therefore, + and finance/+ are both valid, but
+ * finance+ is not valid. The single-level wildcard can be used
+ * at the end of the topic tree or within the topic tree. For example,
+ * finance/+ and finance/+/ibm are both valid.
+ *
+ *
+ *
+ *
+ * The method returns control before the subscribe completes. Completion can be
+ * tracked by:
+ *
+ *
+ *
Waiting on the supplied token {@link MqttToken#waitForCompletion()}
+ * or
+ *
Passing in a callback {@link MqttActionListener} to this method
+ *
+ *
+ * @param subscriptions
+ * one or more {@link MqttSubscription} defining the subscription to
+ * be made.
+ * @param userContext
+ * optional object used to pass context to the callback. Use null if
+ * not required.
+ * @param callback
+ * optional listener that will be notified when subscribe has
+ * completed
+ * @param subscriptionProperties
+ * The {@link MqttProperties} to be sent.
+ * @return token used to track and wait for the subscribe to complete. The token
+ * will be passed to callback methods if set.
+ * @throws MqttException
+ * if there was an error registering the subscription.
+ * @throws IllegalArgumentException
+ * if the two supplied arrays are not the same size.
+ */
+ public IMqttToken subscribe(MqttSubscription[] subscriptions, Object userContext, MqttActionListener callback,
+ MqttProperties subscriptionProperties) throws MqttException;
+
+ /**
+ * Subscribe to a topic, which may include wildcards.
+ *
+ * @see #subscribe(MqttSubscription[], Object, MqttActionListener,
+ * MqttProperties)
+ *
+ * @param mqttSubscription
+ * a {@link MqttSubscription} defining the subscription to be made.
+ * @param userContext
+ * optional object used to pass context to the callback. Use null if
+ * not required.
+ * @param callback
+ * optional listener that will be notified when subscribe has
+ * completed
+ * @param messageListener
+ * a callback to handle incoming messages
+ * @param subscriptionProperties
+ * The {@link MqttProperties} to be sent.
+ * @return token used to track and wait for the subscribe to complete. The token
+ * will be passed to callback methods if set.
+ * @throws MqttException
+ * if there was an error registering the subscription.
+ */
+ public IMqttToken subscribe(MqttSubscription mqttSubscription, Object userContext, MqttActionListener callback,
+ IMqttMessageListener messageListener, MqttProperties subscriptionProperties) throws MqttException;
+
+ /**
+ * Subscribe to a topic, which may include wildcards.
+ *
+ * @see #subscribe(MqttSubscription[], Object, MqttActionListener,
+ * MqttProperties)
+ *
+ * @param subscription
+ * a {@link MqttSubscription} defining the subscription to be made.
+ * @param messageListener
+ * a callback to handle incoming messages
+ * @return token used to track and wait for the subscribe to complete. The token
+ * will be passed to callback methods if set.
+ * @throws MqttException
+ * if there was an error registering the subscription.
+ */
+ public IMqttToken subscribe(MqttSubscription subscription, IMqttMessageListener messageListener) throws MqttException;
+
+ /**
+ * Subscribe to multiple topics, each of which may include wildcards.
+ *
+ *
+ * Provides an optimized way to subscribe to multiple topics compared to
+ * subscribing to each one individually.
+ *
+ *
+ * @see #subscribe(MqttSubscription[], Object, MqttActionListener,
+ * MqttProperties)
+ *
+ ** @param subscriptions
+ * one or more {@link MqttSubscription} defining the subscription to
+ * be made.
+ * @param messageListener
+ * a callback to handle incoming messages
+ * @return token used to track and wait for the subscribe to complete. The token
+ * will be passed to callback methods if set.
+ * @throws MqttException
+ * if there was an error registering the subscription.
+ */
+ public IMqttToken subscribe(MqttSubscription[] subscriptions, IMqttMessageListener messageListener) throws MqttException;
+
+ /**
+ * Subscribe to multiple topics, each of which may include wildcards.
+ *
+ *
+ * Provides an optimized way to subscribe to multiple topics compared to
+ * subscribing to each one individually.
+ *
+ *
+ * @see #subscribe(MqttSubscription[], Object, MqttActionListener,
+ * MqttProperties)
+ *
+ * @param subscriptions
+ * one or more {@link MqttSubscription} defining the subscription to
+ * be made.
+ * @param userContext
+ * optional object used to pass context to the callback. Use null if
+ * not required.
+ * @param callback
+ * optional listener that will be notified when subscribe has
+ * completed
+ * @param messageListeners
+ * one or more callbacks to handle incoming messages.
+ * @param subscriptionProperties
+ * The {@link MqttProperties} to be sent.
+ * @return token used to track and wait for the subscribe to complete. The token
+ * will be passed to callback methods if set.
+ * @throws MqttException
+ * if there was an error registering the subscription.
+ */
+ public IMqttToken subscribe(MqttSubscription[] subscriptions, Object userContext, MqttActionListener callback,
+ IMqttMessageListener[] messageListeners, MqttProperties subscriptionProperties) throws MqttException;
+
+ /**
+ * Subscribe to multiple topics, each of which may include wildcards.
+ *
+ *
+ * Provides an optimized way to subscribe to multiple topics compared to
+ * subscribing to each one individually.
+ *
+ *
+ * @see #subscribe(MqttSubscription[], Object, MqttActionListener,
+ * MqttProperties)
+ *
+ * @param subscriptions
+ * one or more {@link MqttSubscription} defining the subscription to
+ * be made.
+ * @param userContext
+ * optional object used to pass context to the callback. Use null if
+ * not required.
+ * @param callback
+ * optional listener that will be notified when subscribe has
+ * completed
+ * @param messageListener
+ * a callback to handle incoming messages.
+ * @param subscriptionProperties
+ * The {@link MqttProperties} to be sent.
+ * @return token used to track and wait for the subscribe to complete. The token
+ * will be passed to callback methods if set.
+ * @throws MqttException
+ * if there was an error registering the subscription.
+ */
+ public IMqttToken subscribe(MqttSubscription[] subscriptions, Object userContext, MqttActionListener callback,
+ IMqttMessageListener messageListener, MqttProperties subscriptionProperties) throws MqttException;
+
+ /**
+ * Requests the server unsubscribe the client from a topics.
+ *
+ * @see #unsubscribe(String[], Object, MqttActionListener, MqttProperties)
+ *
+ * @param topicFilter
+ * the topic to unsubscribe from. It must match a topicFilter
+ * specified on an earlier subscribe.
+ * @param userContext
+ * optional object used to pass context to the callback. Use null if
+ * not required.
+ * @param callback
+ * optional listener that will be notified when unsubscribe has
+ * completed
+ * @return token used to track and wait for the unsubscribe to complete. The
+ * token will be passed to callback methods if set.
+ * @throws MqttException
+ * if there was an error unregistering the subscription.
+ */
+ public IMqttToken unsubscribe(String topicFilter, Object userContext, MqttActionListener callback) throws MqttException;
+
+ /**
+ * Requests the server unsubscribe the client from a topic.
+ *
+ * @see #unsubscribe(String[], Object, MqttActionListener, MqttProperties)
+ * @param topicFilter
+ * the topic to unsubscribe from. It must match a topicFilter
+ * specified on an earlier subscribe.
+ * @return token used to track and wait for the unsubscribe to complete. The
+ * token will be passed to callback methods if set.
+ * @throws MqttException
+ * if there was an error unregistering the subscription.
+ */
+ public IMqttToken unsubscribe(String topicFilter) throws MqttException;
+
+ /**
+ * Requests the server unsubscribe the client from one or more topics.
+ *
+ * @see #unsubscribe(String[], Object, MqttActionListener, MqttProperties)
+ *
+ * @param topicFilters
+ * one or more topics to unsubscribe from. Each topicFilter must
+ * match one specified on an earlier subscribe. *
+ * @return token used to track and wait for the unsubscribe to complete. The
+ * token will be passed to callback methods if set.
+ * @throws MqttException
+ * if there was an error unregistering the subscription.
+ */
+ public IMqttToken unsubscribe(String[] topicFilters) throws MqttException;
+
+ /**
+ * Requests the server unsubscribe the client from one or more topics.
+ *
+ * Unsubcribing is the opposite of subscribing. When the server receives the
+ * unsubscribe request it looks to see if it can find a matching subscription
+ * for the client and then removes it. After this point the server will send no
+ * more messages to the client for this subscription.
+ *
+ *
+ * The topic(s) specified on the unsubscribe must match the topic(s) specified
+ * in the original subscribe request for the unsubscribe to succeed
+ *
+ *
+ * The method returns control before the unsubscribe completes. Completion can
+ * be tracked by:
+ *
+ *
+ *
+ *
Waiting on the returned token {@link MqttToken#waitForCompletion()}
+ * or
+ *
Passing in a callback {@link MqttActionListener} to this method
+ *
+ *
+ * @param topicFilters
+ * one or more topics to unsubscribe from. Each topicFilter must
+ * match one specified on an earlier subscribe.
+ * @param userContext
+ * optional object used to pass context to the callback. Use null if
+ * not required.
+ * @param callback
+ * optional listener that will be notified when unsubscribe has
+ * completed
+ * @param unsubscribeProperties
+ * The {@link MqttProperties} to be sent.
+ * @return token used to track and wait for the unsubscribe to complete. The
+ * token will be passed to callback methods if set.
+ * @throws MqttException
+ * if there was an error unregistering the subscription.
+ */
+ public IMqttToken unsubscribe(String[] topicFilters, Object userContext, MqttActionListener callback,
+ MqttProperties unsubscribeProperties) throws MqttException;
+
+ /**
+ * Sets a callback listener to use for events that happen asynchronously.
+ *
+ * There are a number of events that the listener will be notified about. These
+ * include:
+ *
+ *
+ *
A new message has arrived and is ready to be processed
+ *
The connection to the server has been lost
+ *
Delivery of a message to the server has completed
+ *
+ *
+ * Other events that track the progress of an individual operation such as
+ * connect and subscribe can be tracked using the {@link MqttToken} returned
+ * from each non-blocking method or using setting a {@link MqttActionListener}
+ * on the non-blocking method.
+ *
+ *
+ * @see MqttCallback
+ * @param callback
+ * which will be invoked for certain asynchronous events
+ */
+ public void setCallback(MqttCallback callback);
+
+ /**
+ * If manualAcks is set to true, then on completion of the messageArrived
+ * callback the MQTT acknowledgements are not sent. You must call
+ * messageArrivedComplete to send those acknowledgements. This allows finer
+ * control over when the acks are sent. The default behaviour, when manualAcks
+ * is false, is to send the MQTT acknowledgements automatically at the
+ * successful completion of the messageArrived callback method.
+ *
+ * @param manualAcks
+ * if set to true MQTT acknowledgements are not sent
+ */
+ public void setManualAcks(boolean manualAcks);
+
+ /**
+ * Indicate that the application has completed processing the message with id
+ * messageId. This will cause the MQTT acknowledgement to be sent to the server.
+ *
+ * @param messageId
+ * the MQTT message id to be acknowledged
+ * @param qos
+ * the MQTT QoS of the message to be acknowledged
+ * @throws MqttException
+ * if there was a problem sending the acknowledgement
+ */
+ public void messageArrivedComplete(int messageId, int qos) throws MqttException;
+
+ /**
+ * Returns the delivery tokens for any outstanding publish operations.
+ *
+ * If a client has been restarted and there are messages that were in the
+ * process of being delivered when the client stopped this method returns a
+ * token for each in-flight message enabling the delivery to be tracked
+ * Alternately the {@link MqttCallback#deliveryComplete(IMqttToken)}
+ * callback can be used to track the delivery of outstanding messages.
+ *
+ *
+ * If a client connects with cleanStart true then there will be no delivery
+ * tokens as the cleanStart option deletes all earlier state. For state to be
+ * remembered the client must connect with cleanStart set to false
+ *
+ *
+ * @return zero or more delivery tokens
+ */
+ public IMqttToken[] getPendingTokens();
+
+ /**
+ * Publishes a message to a topic on the server.
+ *
+ * A convenience method, which will create a new {@link MqttMessage} object with
+ * a byte array payload and the specified QoS, and then publish it.
+ *
+ *
+ * @param topic
+ * to deliver the message to, for example "finance/stock/ibm".
+ * @param payload
+ * the byte array to use as the payload
+ * @param qos
+ * the Quality of Service to deliver the message at. Valid values are
+ * 0, 1 or 2.
+ * @param retained
+ * whether or not this message should be retained by the server.
+ * @param userContext
+ * optional object used to pass context to the callback. Use null if
+ * not required.
+ * @param callback
+ * optional listener that will be notified when message delivery hsa
+ * completed to the requested quality of service
+ * @return token used to track and wait for the publish to complete. The token
+ * will be passed to any callback that has been set.
+ * @throws MqttPersistenceException
+ * when a problem occurs storing the message
+ * @throws IllegalArgumentException
+ * if value of QoS is not 0, 1 or 2.
+ * @throws MqttException
+ * for other errors encountered while publishing the message. For
+ * instance client not connected.
+ * @see #publish(String, MqttMessage, Object, MqttActionListener)
+ * @see MqttMessage#setQos(int)
+ * @see MqttMessage#setRetained(boolean)
+ */
+ public IMqttToken publish(String topic, byte[] payload, int qos, boolean retained, Object userContext,
+ MqttActionListener callback) throws MqttException, MqttPersistenceException;
+
+ /**
+ * Publishes a message to a topic on the server.
+ *
+ * A convenience method, which will create a new {@link MqttMessage} object with
+ * a byte array payload and the specified QoS, and then publish it.
+ *
+ *
+ * @param topic
+ * to deliver the message to, for example "finance/stock/ibm".
+ * @param payload
+ * the byte array to use as the payload
+ * @param qos
+ * the Quality of Service to deliver the message at. Valid values are
+ * 0, 1 or 2.
+ * @param retained
+ * whether or not this message should be retained by the server.
+ * @return token used to track and wait for the publish to complete. The token
+ * will be passed to any callback that has been set.
+ * @throws MqttPersistenceException
+ * when a problem occurs storing the message
+ * @throws IllegalArgumentException
+ * if value of QoS is not 0, 1 or 2.
+ * @throws MqttException
+ * for other errors encountered while publishing the message. For
+ * instance if too many messages are being processed.
+ * @see #publish(String, MqttMessage, Object, MqttActionListener)
+ * @see MqttMessage#setQos(int)
+ * @see MqttMessage#setRetained(boolean)
+ */
+ public IMqttToken publish(String topic, byte[] payload, int qos, boolean retained)
+ throws MqttException, MqttPersistenceException;
+
+ /**
+ * Publishes a message to a topic on the server. Takes an {@link MqttMessage}
+ * message and delivers it to the server at the requested quality of service.
+ *
+ * @param topic
+ * to deliver the message to, for example "finance/stock/ibm".
+ * @param message
+ * to deliver to the server
+ * @return token used to track and wait for the publish to complete. The token
+ * will be passed to any callback that has been set.
+ * @throws MqttPersistenceException
+ * when a problem occurs storing the message
+ * @throws IllegalArgumentException
+ * if value of QoS is not 0, 1 or 2.
+ * @throws MqttException
+ * for other errors encountered while publishing the message. For
+ * instance client not connected.
+ * @see #publish(String, MqttMessage, Object, MqttActionListener)
+ */
+ public IMqttToken publish(String topic, MqttMessage message) throws MqttException, MqttPersistenceException;
+
+ /**
+ * Publishes a message to a topic on the server.
+ *
+ * Once this method has returned cleanly, the message has been accepted for
+ * publication by the client and will be delivered on a background thread. In
+ * the event the connection fails or the client stops. Messages will be
+ * delivered to the requested quality of service once the connection is
+ * re-established to the server on condition that:
+ *
+ *
+ *
The connection is re-established with the same clientID
+ *
The original connection was made with (@link
+ * MqttConnectOptions#setCleanStart(boolean)} set to false
+ *
The connection is re-established with (@link
+ * MqttConnectOptions#setCleanStart(boolean)} set to false
+ *
Depending when the failure occurs QoS 0 messages may not be delivered.
+ *
+ *
+ *
+ * When building an application, the design of the topic tree should take into
+ * account the following principles of topic name syntax and semantics:
+ *
+ *
+ *
+ *
A topic must be at least one character long.
+ *
Topic names are case sensitive. For example, ACCOUNTS and
+ * Accounts are two different topics.
+ *
Topic names can include the space character. For example, Accounts
+ * payable is a valid topic.
+ *
A leading "/" creates a distinct topic. For example, /finance is
+ * different from finance. /finance matches "+/+" and "/+",
+ * but not "+".
+ *
Do not include the null character (Unicode
+ *
+ *
+ * \x0000
+ *
+ *
+ * ) in any topic.
+ *
+ *
+ *
+ * The following principles apply to the construction and content of a topic
+ * tree:
+ *
+ *
+ *
The length is limited to 64k but within that there are no limits to the
+ * number of levels in a topic tree.
+ *
There can be any number of root nodes; that is, there can be any number
+ * of topic trees.
+ *
+ *
+ *
+ * The method returns control before the publish completes. Completion can be
+ * tracked by:
+ *
+ *
+ *
Setting an {@link IMqttAsyncClient#setCallback(MqttCallback)} where the
+ * {@link MqttCallback#deliveryComplete(IMqttToken)} method will be
+ * called.
+ *
Waiting on the returned token {@link MqttToken#waitForCompletion()}
+ * or
+ *
Passing in a callback {@link MqttActionListener} to this method
+ *
+ *
+ * @param topic
+ * to deliver the message to, for example "finance/stock/ibm".
+ * @param message
+ * to deliver to the server
+ * @param userContext
+ * optional object used to pass context to the callback. Use null if
+ * not required.
+ * @param callback
+ * optional listener that will be notified when message delivery has
+ * completed to the requested quality of service.
+ * @return token used to track and wait for the publish to complete. The token
+ * will be passed to callback methods if set.
+ * @throws MqttPersistenceException
+ * when a problem occurs storing the message
+ * @throws IllegalArgumentException
+ * if value of QoS is not 0, 1 or 2.
+ * @throws MqttException
+ * for other errors encountered while publishing the message. For
+ * instance client not connected.
+ * @see MqttMessage
+ */
+ public IMqttToken publish(String topic, MqttMessage message, Object userContext, MqttActionListener callback)
+ throws MqttException, MqttPersistenceException;
+
+ /**
+ * An AUTH Packet is sent from Client to Server or Server to Client as part of
+ * an extended authentication exchange, such as challenge / response
+ * authentication. It is a protocol error for the Client or Server to send an
+ * AUTH packet if the CONNECT packet did not contain the same Authentication
+ * Method.
+ *
+ * @param reasonCode
+ * The Reason code, can be Success (0), Continue authentication (24)
+ * or Re-authenticate (25).
+ * @param userContext
+ * optional object used to pass context to the callback. Use null if
+ * not required.
+ * @param properties
+ * The {@link MqttProperties} to be sent, containing the
+ * Authentication Method, Authentication Data and any required User
+ * Defined Properties.
+ * @return token used to track and wait for the authentication to complete.
+ * @throws MqttException if an exception occurs whilst sending the authenticate packet.
+ */
+ public IMqttToken authenticate(int reasonCode, Object userContext, MqttProperties properties) throws MqttException;
+
+ /**
+ * User triggered attempt to reconnect
+ *
+ * @throws MqttException
+ * if there is an issue with reconnecting
+ */
+ public void reconnect() throws MqttException;
+
+ /**
+ * Sets the DisconnectedBufferOptions for this client
+ *
+ * @param bufferOpts
+ * the {@link DisconnectedBufferOptions}
+ */
+ public void setBufferOpts(DisconnectedBufferOptions bufferOpts);
+
+ /**
+ * Returns the number of messages in the Disconnected Message Buffer
+ *
+ * @return Count of messages in the buffer
+ */
+ public int getBufferedMessageCount();
+
+ /**
+ * Returns a message from the Disconnected Message Buffer
+ *
+ * @param bufferIndex
+ * the index of the message to be retrieved.
+ * @return the message located at the bufferIndex
+ */
+ public MqttMessage getBufferedMessage(int bufferIndex);
+
+ /**
+ * Deletes a message from the Disconnected Message Buffer
+ *
+ * @param bufferIndex
+ * the index of the message to be deleted.
+ */
+ public void deleteBufferedMessage(int bufferIndex);
+
+ /**
+ * Returns the current number of outgoing in-flight messages being sent by the
+ * client. Note that this number cannot be guaranteed to be 100% accurate as
+ * some messages may have been sent or queued in the time taken for this method
+ * to return.
+ *
+ * @return the current number of in-flight messages.
+ */
+ public int getInFlightMessageCount();
+
+ /**
+ * Close the client Releases all resource associated with the client. After the
+ * client has been closed it cannot be reused. For instance attempts to connect
+ * will fail.
+ *
+ * @throws MqttException
+ * if the client is not disconnected.
+ */
+ public void close() throws MqttException;
+
+ /**
+ * Close the client Releases all resource associated with the client. After the
+ * client has been closed it cannot be reused. For instance attempts to connect
+ * will fail.
+ *
+ * @param force
+ * - Will force the connection to close.
+ *
+ * @throws MqttException
+ * if the client is not disconnected.
+ */
+ public void close(boolean force) throws MqttException;
+
+ /**
+ * Return a debug object that can be used to help solve problems.
+ *
+ * @return the {@link Debug} object
+ */
+ public Debug getDebug();
+
+ public IMqttToken subscribe(MqttSubscription subscription) throws MqttException;
+
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/IMqttClient.java b/src/main/java/org/eclipse/paho/mqttv5/client/IMqttClient.java
new file mode 100644
index 0000000..c8735ac
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/IMqttClient.java
@@ -0,0 +1,657 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2019 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * James Sutton - MQTT V5 support
+ */
+
+package org.eclipse.paho.mqttv5.client;
+
+import org.eclipse.paho.mqttv5.common.MqttException;
+import org.eclipse.paho.mqttv5.common.MqttMessage;
+import org.eclipse.paho.mqttv5.common.MqttPersistenceException;
+import org.eclipse.paho.mqttv5.common.MqttSecurityException;
+import org.eclipse.paho.mqttv5.common.MqttSubscription;
+
+/**
+ * Enables an application to communicate with an MQTT server using blocking methods.
+ *
+ * This interface allows applications to utilize all features of the MQTT version 3.1
+ * specification including:
+ *
+ *
connect
+ *
publish
+ *
subscribe
+ *
unsubscribe
+ *
disconnect
+ *
+ *
+ * There are two styles of MQTT client, this one and {@link IMqttAsyncClient}.
+ *
+ *
IMqttClient provides a set of methods that block and return control to the application
+ * program once the MQTT action has completed.
+ *
IMqttAsyncClient provides a set of non-blocking methods that return control to the
+ * invoking application after initial validation of parameters and state. The main processing is
+ * performed in the background so as not to block the application programs thread. This non
+ * blocking approach is handy when the application wants to carry on processing while the
+ * MQTT action takes place. For instance connecting to an MQTT server can take time, using
+ * the non-blocking connect method allows an application to display a busy indicator while the
+ * connect action is occurring. Non-blocking methods are particularly useful in event-oriented
+ * programs and graphical programs where issuing methods that take time to complete on the the
+ * main or GUI thread can cause problems.
+ *
+ *
+ * The non-blocking client can also be used in a blocking form by turning a non-blocking
+ * method into a blocking invocation using the following pattern:
+ * Using the non-blocking client allows an application to use a mixture of blocking and
+ * non-blocking styles. Using the blocking client only allows an application to use one
+ * style. The blocking client provides compatibility with earlier versions
+ * of the MQTT client.
+ */
+public interface IMqttClient { //extends IMqttAsyncClient {
+ /**
+ * Connects to an MQTT server using the default options.
+ *
The default options are specified in {@link MqttConnectionOptions} class.
+ *
+ *
+ * @throws MqttSecurityException when the server rejects the connect for security
+ * reasons
+ * @throws MqttException for non security related problems
+ * @see #connect(MqttConnectionOptions)
+ */
+ public void connect() throws MqttSecurityException, MqttException;
+
+ /**
+ * Connects to an MQTT server using the specified options.
+ *
The server to connect to is specified on the constructor.
+ * It is recommended to call {@link #setCallback(MqttCallback)} prior to
+ * connecting in order that messages destined for the client can be accepted
+ * as soon as the client is connected.
+ *
+ *
This is a blocking method that returns once connect completes
+ *
+ * @param options a set of connection parameters that override the defaults.
+ * @throws MqttSecurityException when the server rejects the connect for security
+ * reasons
+ * @throws MqttException for non security related problems including communication errors
+ */
+ public void connect(MqttConnectionOptions options) throws MqttSecurityException, MqttException;
+
+ /**
+ * Connects to an MQTT server using the specified options.
+ *
The server to connect to is specified on the constructor.
+ * It is recommended to call {@link #setCallback(MqttCallback)} prior to
+ * connecting in order that messages destined for the client can be accepted
+ * as soon as the client is connected.
+ *
+ *
This is a blocking method that returns once connect completes
+ *
+ * @param options a set of connection parameters that override the defaults.
+ * @return the MqttToken used for the call. Can be used to obtain the session present flag
+ * @throws MqttSecurityException when the server rejects the connect for security
+ * reasons
+ * @throws MqttException for non security related problems including communication errors
+ */
+ public IMqttToken connectWithResult(MqttConnectionOptions options) throws MqttSecurityException, MqttException;
+
+ /**
+ * Disconnects from the server.
+ *
An attempt is made to quiesce the client allowing outstanding
+ * work to complete before disconnecting. It will wait
+ * for a maximum of 30 seconds for work to quiesce before disconnecting.
+ * This method must not be called from inside {@link MqttCallback} methods.
+ *
+ *
+ *
This is a blocking method that returns once disconnect completes
+ *
+ * @throws MqttException if a problem is encountered while disconnecting
+ */
+ public void disconnect() throws MqttException;
+
+ /**
+ * Disconnects from the server.
+ *
+ * The client will wait for all {@link MqttCallback} methods to
+ * complete. It will then wait for up to the quiesce timeout to allow for
+ * work which has already been initiated to complete - for example, it will
+ * wait for the QoS 2 flows from earlier publications to complete. When work has
+ * completed or after the quiesce timeout, the client will disconnect from
+ * the server. If the cleanStart flag was set to false and is set to false the
+ * next time a connection is made QoS 1 and 2 messages that
+ * were not previously delivered will be delivered.
+ *
+ *
This is a blocking method that returns once disconnect completes
+ *
+ * @param quiesceTimeout the amount of time in milliseconds to allow for
+ * existing work to finish before disconnecting. A value of zero or less
+ * means the client will not quiesce.
+ * @throws MqttException if a problem is encountered while disconnecting
+ */
+ public void disconnect(long quiesceTimeout) throws MqttException;
+
+ /**
+ * Disconnects from the server forcibly to reset all the states. Could be useful when disconnect attempt failed.
+ *
+ * Because the client is able to establish the TCP/IP connection to a none MQTT server and it will certainly fail to
+ * send the disconnect packet. It will wait for a maximum of 30 seconds for work to quiesce before disconnecting and
+ * wait for a maximum of 10 seconds for sending the disconnect packet to server.
+ *
+ * @throws MqttException if any unexpected error
+ * @since 0.4.1
+ */
+ public void disconnectForcibly() throws MqttException;
+
+ /**
+ * Disconnects from the server forcibly to reset all the states. Could be useful when disconnect attempt failed.
+ *
+ * Because the client is able to establish the TCP/IP connection to a none MQTT server and it will certainly fail to
+ * send the disconnect packet. It will wait for a maximum of 30 seconds for work to quiesce before disconnecting.
+ *
+ * @param disconnectTimeout the amount of time in milliseconds to allow send disconnect packet to server.
+ * @throws MqttException if any unexpected error
+ * @since 0.4.1
+ */
+ public void disconnectForcibly(long disconnectTimeout) throws MqttException;
+
+ /**
+ * Disconnects from the server forcibly to reset all the states. Could be useful when disconnect attempt failed.
+ *
+ * Because the client is able to establish the TCP/IP connection to a none MQTT server and it will certainly fail to
+ * send the disconnect packet.
+ *
+ * @param quiesceTimeout the amount of time in milliseconds to allow for existing work to finish before
+ * disconnecting. A value of zero or less means the client will not quiesce.
+ * @param disconnectTimeout the amount of time in milliseconds to allow send disconnect packet to server.
+ * @throws MqttException if any unexpected error
+ * @since 0.4.1
+ */
+ public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout) throws MqttException;
+
+ /**
+ * Subscribe to a topic, which may include wildcards.
+ *
+ * @see #subscribe(String[], int[])
+ *
+ * @param topicFilter the topic to subscribe to, which can include wildcards.
+ * @param qos the maximum quality of service at which to subscribe. Messages
+ * published at a lower quality of service will be received at the published
+ * QoS. Messages published at a higher quality of service will be received using
+ * the QoS specified on the subscribe.
+ * @return a token
+ * @throws MqttException if there was an error registering the subscription.
+ */
+ public IMqttToken subscribe(String topicFilter, int qos) throws MqttException;
+
+ /**
+ * Subscribes to multiple topics, each of which may include wildcards.
+ *
The {@link #setCallback(MqttCallback)} method
+ * should be called before this method, otherwise any received messages
+ * will be discarded.
+ *
+ *
+ * If (@link MqttConnectOptions#setCleanStart(boolean)} was set to true
+ * when when connecting to the server then the subscription remains in place
+ * until either:
+ *
+ *
+ *
The client disconnects
+ *
An unsubscribe method is called to un-subscribe the topic
+ *
+ *
+ * If (@link MqttConnectOptions#setCleanStart(boolean)} was set to false
+ * when when connecting to the server then the subscription remains in place
+ * until either:
+ *
+ *
An unsubscribe method is called to unsubscribe the topic
+ *
The client connects with cleanStart set to true
+ *
+ *
+ * With cleanStart set to false the MQTT server will store messages on
+ * behalf of the client when the client is not connected. The next time the
+ * client connects with the same client ID the server will
+ * deliver the stored messages to the client.
+ *
+ *
+ *
The "topic filter" string used when subscribing
+ * may contain special characters, which allow you to subscribe to multiple topics
+ * at once.
+ *
The topic level separator is used to introduce structure into the topic, and
+ * can therefore be specified within the topic for that purpose. The multi-level
+ * wildcard and single-level wildcard can be used for subscriptions, but they
+ * cannot be used within a topic by the publisher of a message.
+ *
+ *
Topic level separator
+ *
The forward slash (/) is used to separate each level within
+ * a topic tree and provide a hierarchical structure to the topic space. The
+ * use of the topic level separator is significant when the two wildcard characters
+ * are encountered in topics specified by subscribers.
+ *
+ *
Multi-level wildcard
+ *
The number sign (#) is a wildcard character that matches
+ * any number of levels within a topic. For example, if you subscribe to
+ * finance/stock/ibm/#, you receive
+ * messages on these topics:
+ *
+ *
finance/stock/ibm
+ *
finance/stock/ibm/closingprice
+ *
finance/stock/ibm/currentprice
+ *
+ *
+ *
The multi-level wildcard
+ * can represent zero or more levels. Therefore, finance/# can also match
+ * the singular finance, where # represents zero levels. The topic
+ * level separator is meaningless in this context, because there are no levels
+ * to separate.
+ *
+ *
The multi-level wildcard can
+ * be specified only on its own or next to the topic level separator character.
+ * Therefore, # and finance/# are both valid, but finance# is
+ * not valid. The multi-level wildcard must be the last character
+ * used within the topic tree. For example, finance/# is valid but
+ * finance/#/closingprice is not valid.
+ *
+ *
Single-level wildcard
+ *
The plus sign (+) is a wildcard character that matches only one topic
+ * level. For example, finance/stock/+ matches
+ * finance/stock/ibm and finance/stock/xyz,
+ * but not finance/stock/ibm/closingprice. Also, because the single-level
+ * wildcard matches only a single level, finance/+ does not match finance.
+ *
+ *
Use
+ * the single-level wildcard at any level in the topic tree, and in conjunction
+ * with the multilevel wildcard. Specify the single-level wildcard next to the
+ * topic level separator, except when it is specified on its own. Therefore,
+ * + and finance/+ are both valid, but finance+ is
+ * not valid. The single-level wildcard can be used at the end of the
+ * topic tree or within the topic tree.
+ * For example, finance/+ and finance/+/ibm are both valid.
+ *
+ *
+ *
+ *
This is a blocking method that returns once subscribe completes
+ *
+ * @param topicFilters one or more topics to subscribe to, which can include wildcards.
+ * @param qos the maximum quality of service to subscribe each topic at.Messages
+ * published at a lower quality of service will be received at the published
+ * QoS. Messages published at a higher quality of service will be received using
+ * the QoS specified on the subscribe.
+ * @return a token
+ * @throws MqttException if there was an error registering the subscription.
+ * @throws IllegalArgumentException if the two supplied arrays are not the same size.
+ */
+ public IMqttToken subscribe(String[] topicFilters, int[] qos) throws MqttException;
+
+ /**
+ * Subscribes to a one or more topics, which may include wildcards using a QoS of 1.
+ *
+ * @see #subscribe(String[], int[])
+ *
+ * @param topicFilter the topic to subscribe to, which can include wildcards.
+ * @param qos QoS
+ * @param messageListener one callbacks to handle incoming messages
+ * @return a token
+ * @throws MqttException if there was an error registering the subscription.
+ */
+ public IMqttToken subscribe(String topicFilter, int qos, IMqttMessageListener messageListener) throws MqttException;
+
+ /**
+ * Subscribes to multiple topics, each of which may include wildcards.
+ *
The {@link #setCallback(MqttCallback)} method
+ * should be called before this method, otherwise any received messages
+ * will be discarded.
+ *
+ *
+ * If (@link MqttConnectOptions#setCleanStart(boolean)} was set to true
+ * when when connecting to the server then the subscription remains in place
+ * until either:
+ *
+ *
The client disconnects
+ *
An unsubscribe method is called to un-subscribe the topic
+ *
+ *
+ * If (@link MqttConnectOptions#setCleanStart(boolean)} was set to false
+ * when when connecting to the server then the subscription remains in place
+ * until either:
+ *
+ *
An unsubscribe method is called to unsubscribe the topic
+ *
The client connects with cleanStart set to true
+ *
+ *
+ * With cleanStart set to false the MQTT server will store messages on
+ * behalf of the client when the client is not connected. The next time the
+ * client connects with the same client ID the server will
+ * deliver the stored messages to the client.
+ *
+ *
+ *
The "topic filter" string used when subscribing
+ * may contain special characters, which allow you to subscribe to multiple topics
+ * at once.
+ *
The topic level separator is used to introduce structure into the topic, and
+ * can therefore be specified within the topic for that purpose. The multi-level
+ * wildcard and single-level wildcard can be used for subscriptions, but they
+ * cannot be used within a topic by the publisher of a message.
+ *
+ *
Topic level separator
+ *
The forward slash (/) is used to separate each level within
+ * a topic tree and provide a hierarchical structure to the topic space. The
+ * use of the topic level separator is significant when the two wildcard characters
+ * are encountered in topics specified by subscribers.
+ *
+ *
Multi-level wildcard
+ *
The number sign (#) is a wildcard character that matches
+ * any number of levels within a topic. For example, if you subscribe to
+ * finance/stock/ibm/#, you receive
+ * messages on these topics:
+ *
+ *
finance/stock/ibm
+ *
finance/stock/ibm/closingprice
+ *
finance/stock/ibm/currentprice
+ *
+ *
The multi-level wildcard
+ * can represent zero or more levels. Therefore, finance/# can also match
+ * the singular finance, where # represents zero levels. The topic
+ * level separator is meaningless in this context, because there are no levels
+ * to separate.
+ *
+ *
The multi-level wildcard can
+ * be specified only on its own or next to the topic level separator character.
+ * Therefore, # and finance/# are both valid, but finance# is
+ * not valid. The multi-level wildcard must be the last character
+ * used within the topic tree. For example, finance/# is valid but
+ * finance/#/closingprice is not valid.
+ *
+ *
Single-level wildcard
+ *
The plus sign (+) is a wildcard character that matches only one topic
+ * level. For example, finance/stock/+ matches
+ * finance/stock/ibm and finance/stock/xyz,
+ * but not finance/stock/ibm/closingprice. Also, because the single-level
+ * wildcard matches only a single level, finance/+ does not match finance.
+ *
+ *
Use
+ * the single-level wildcard at any level in the topic tree, and in conjunction
+ * with the multilevel wildcard. Specify the single-level wildcard next to the
+ * topic level separator, except when it is specified on its own. Therefore,
+ * + and finance/+ are both valid, but finance+ is
+ * not valid. The single-level wildcard can be used at the end of the
+ * topic tree or within the topic tree.
+ * For example, finance/+ and finance/+/ibm are both valid.
+ *
+ *
+ *
+ *
This is a blocking method that returns once subscribe completes
+ *
+ * @param topicFilters one or more topics to subscribe to, which can include wildcards.
+ * @param qos the maximum quality of service to subscribe each topic at.Messages
+ * published at a lower quality of service will be received at the published
+ * QoS. Messages published at a higher quality of service will be received using
+ * the QoS specified on the subscribe.
+ * @param messageListeners one or more callbacks to handle incoming messages
+ * @return a token
+ * @throws MqttException if there was an error registering the subscription.
+ * @throws IllegalArgumentException if the two supplied arrays are not the same size.
+ */
+ public IMqttToken subscribe(String[] topicFilters, int[] qos, IMqttMessageListener[] messageListeners) throws MqttException;
+
+ /**
+ * Requests the server unsubscribe the client from a topic.
+ *
+ * @see #unsubscribe(String[])
+ * @param topicFilter the topic to unsubscribe from. It must match a topicFilter
+ * specified on the subscribe.
+ * @throws MqttException if there was an error unregistering the subscription.
+ */
+ public void unsubscribe(String topicFilter) throws MqttException;
+
+ /**
+ * Requests the server unsubscribe the client from one or more topics.
+ *
+ * Unsubcribing is the opposite of subscribing. When the server receives the
+ * unsubscribe request it looks to see if it can find a subscription for the
+ * client and then removes it. After this point the server will send no more
+ * messages to the client for this subscription.
+ *
+ *
The topic(s) specified on the unsubscribe must match the topic(s)
+ * specified in the original subscribe request for the subscribe to succeed
+ *
+ *
+ *
This is a blocking method that returns once unsubscribe completes
+ *
+ * @param topicFilters one or more topics to unsubscribe from. Each topicFilter
+ * must match one specified on a subscribe
+ * @throws MqttException if there was an error unregistering the subscription.
+ */
+ public void unsubscribe(String[] topicFilters) throws MqttException;
+
+
+ /**
+ * Publishes a message to a topic on the server and return once it is delivered.
+ *
This is a convenience method, which will
+ * create a new {@link MqttMessage} object with a byte array payload and the
+ * specified QoS, and then publish it. All other values in the
+ * message will be set to the defaults.
+ *
+ *
+ * @param topic to deliver the message to, for example "finance/stock/ibm".
+ * @param payload the byte array to use as the payload
+ * @param qos the Quality of Service to deliver the message at. Valid values are 0, 1 or 2.
+ * @param retained whether or not this message should be retained by the server.
+ * @throws MqttPersistenceException when a problem with storing the message
+ * @throws IllegalArgumentException if value of QoS is not 0, 1 or 2.
+ * @throws MqttException for other errors encountered while publishing the message.
+ * For instance client not connected.
+ * @see #publish(String, MqttMessage)
+ * @see MqttMessage#setQos(int)
+ * @see MqttMessage#setRetained(boolean)
+ */
+ public void publish(String topic, byte[] payload, int qos, boolean retained) throws MqttException, MqttPersistenceException;
+
+ /**
+ * Publishes a message to a topic on the server.
+ *
+ * Delivers a message to the server at the requested quality of service and returns control
+ * once the message has been delivered. In the event the connection fails or the client
+ * stops, any messages that are in the process of being delivered will be delivered once
+ * a connection is re-established to the server on condition that:
+ *
+ *
The connection is re-established with the same clientID
+ *
The original connection was made with (@link MqttConnectOptions#setCleanStart(boolean)}
+ * set to false
+ *
The connection is re-established with (@link MqttConnectOptions#setCleanStart(boolean)}
+ * set to false
+ *
+ *
In the event that the connection breaks or the client stops it is still possible to determine
+ * when the delivery of the message completes. Prior to re-establishing the connection to the server:
+ *
+ *
Register a {@link #setCallback(MqttCallback)} callback on the client and the delivery complete
+ * callback will be notified once a delivery of a message completes
+ *
or call {@link #getPendingTokens()} which will return a token for each message that
+ * is in-flight. The token can be used to wait for delivery to complete.
+ *
+ *
+ *
When building an application,
+ * the design of the topic tree should take into account the following principles
+ * of topic name syntax and semantics:
+ *
+ *
+ *
A topic must be at least one character long.
+ *
Topic names are case sensitive. For example, ACCOUNTS and Accounts are
+ * two different topics.
+ *
Topic names can include the space character. For example, Accounts
+ * payable is a valid topic.
+ *
A leading "/" creates a distinct topic. For example, /finance is
+ * different from finance. /finance matches "+/+" and "/+", but
+ * not "+".
+ *
Do not include the null character (Unicode
\x0000
) in
+ * any topic.
+ *
+ *
+ *
The following principles apply to the construction and content of a topic
+ * tree:
+ *
+ *
+ *
The length is limited to 64k but within that there are no limits to the
+ * number of levels in a topic tree.
+ *
There can be any number of root nodes; that is, there can be any number
+ * of topic trees.
+ *
+ *
+ *
+ *
This is a blocking method that returns once publish completes
*
+ *
+ * @param topic to deliver the message to, for example "finance/stock/ibm".
+ * @param message to delivery to the server
+ * @throws MqttPersistenceException when a problem with storing the message
+ * @throws MqttException for other errors encountered while publishing the message.
+ * For instance client not connected.
+ */
+ public void publish(String topic, MqttMessage message) throws MqttException, MqttPersistenceException;
+
+ /**
+ * Sets the callback listener to use for events that happen asynchronously.
+ *
There are a number of events that listener will be notified about. These include:
+ *
+ *
A new message has arrived and is ready to be processed
+ *
The connection to the server has been lost
+ *
Delivery of a message to the server has completed.
+ *
+ *
Other events that track the progress of an individual operation such
+ * as connect and subscribe can be tracked using the {@link MqttToken} passed to the
+ * operation
+ * @see MqttCallback
+ * @param callback the class to callback when for events related to the client
+ */
+ public void setCallback(MqttCallback callback);
+
+ /**
+ * Get a topic object which can be used to publish messages.
+ *
An alternative method that should be used in preference to this one when publishing a message is:
+ *
+ *
{@link MqttClient#publish(String, MqttMessage)} to publish a message in a blocking manner
+ *
or use publish methods on the non-blocking client like {@link IMqttAsyncClient#publish(String, MqttMessage, Object, MqttActionListener)}
+ *
+ *
When building an application,
+ * the design of the topic tree should take into account the following principles
+ * of topic name syntax and semantics:
+ *
+ *
+ *
A topic must be at least one character long.
+ *
Topic names are case sensitive. For example, ACCOUNTS and Accounts are
+ * two different topics.
+ *
Topic names can include the space character. For example, Accounts
+ * payable is a valid topic.
+ *
A leading "/" creates a distinct topic. For example, /finance is
+ * different from finance. /finance matches "+/+" and "/+", but
+ * not "+".
+ *
Do not include the null character (Unicode
\x0000
) in
+ * any topic.
+ *
+ *
+ *
The following principles apply to the construction and content of a topic
+ * tree:
+ *
+ *
+ *
The length is limited to 64k but within that there are no limits to the
+ * number of levels in a topic tree.
+ *
There can be any number of root nodes; that is, there can be any number
+ * of topic trees.
+ *
+ *
+ * @param topic the topic to use, for example "finance/stock/ibm".
+ * @return an MqttTopic object, which can be used to publish messages to
+ * the topic.
+ * @throws IllegalArgumentException if the topic contains a '+' or '#'
+ * wildcard character.
+ */
+ public MqttTopic getTopic(String topic);
+
+ /**
+ * Determines if this client is currently connected to the server.
+ *
+ * @return true if connected, false otherwise.
+ */
+ public boolean isConnected();
+
+ /**
+ * Returns the client ID used by this client.
+ *
All clients connected to the
+ * same server or server farm must have a unique ID.
+ *
+ *
+ * @return the client ID used by this client.
+ */
+ public String getClientId();
+
+ /**
+ * Returns the address of the server used by this client, as a URI.
+ *
The format is the same as specified on the constructor.
+ *
+ *
+ * @return the server's address, as a URI String.
+ * @see MqttAsyncClient#MqttAsyncClient(String, String)
+ */
+ public String getServerURI();
+
+ /**
+ * Returns the delivery tokens for any outstanding publish operations.
+ *
If a client has been restarted and there are messages that were in the
+ * process of being delivered when the client stopped this method will
+ * return a token for each message enabling the delivery to be tracked
+ * Alternately the {@link MqttCallback#deliveryComplete(IMqttToken)}
+ * callback can be used to track the delivery of outstanding messages.
+ *
+ *
If a client connects with cleanStart true then there will be no
+ * delivery tokens as the cleanStart option deletes all earlier state.
+ * For state to be remembered the client must connect with cleanStart
+ * set to false
+ * @return zero or more delivery tokens
+ */
+ public IMqttToken[] getPendingTokens();
+
+ /**
+ * If manualAcks is set to true, then on completion of the messageArrived callback
+ * the MQTT acknowledgements are not sent. You must call messageArrivedComplete
+ * to send those acknowledgements. This allows finer control over when the acks are
+ * sent. The default behaviour, when manualAcks is false, is to send the MQTT
+ * acknowledgements automatically at the successful completion of the messageArrived
+ * callback method.
+ * @param manualAcks if set to true, MQTT acknowledgements are not sent.
+ */
+ public void setManualAcks(boolean manualAcks);
+
+ /**
+ * Will attempt to reconnect to the server after the client has lost connection.
+ * @throws MqttException if an error occurs attempting to reconnect
+ */
+ public void reconnect() throws MqttException;
+
+ /**
+ * Indicate that the application has completed processing the message with id messageId.
+ * This will cause the MQTT acknowledgement to be sent to the server.
+ * @param messageId the MQTT message id to be acknowledged
+ * @param qos the MQTT QoS of the message to be acknowledged
+ * @throws MqttException if there was a problem sending the acknowledgement
+ */
+ public void messageArrivedComplete(int messageId, int qos) throws MqttException;
+
+ /**
+ * Close the client
+ * Releases all resource associated with the client. After the client has
+ * been closed it cannot be reused. For instance attempts to connect will fail.
+ * @throws MqttException if the client is not disconnected.
+ */
+ public void close() throws MqttException;
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/IMqttDeliveryToken.java b/src/main/java/org/eclipse/paho/mqttv5/client/IMqttDeliveryToken.java
new file mode 100644
index 0000000..af78bfd
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/IMqttDeliveryToken.java
@@ -0,0 +1,44 @@
+package org.eclipse.paho.mqttv5.client;
+
+import org.eclipse.paho.mqttv5.common.MqttException;
+import org.eclipse.paho.mqttv5.common.MqttMessage;
+
+/**
+ * Provides a mechanism for tracking the delivery of a message.
+ *
+ *
A subclass of IMqttToken that allows the delivery of a message to be tracked.
+ * Unlike instances of IMqttToken delivery tokens can be used across connection
+ * and client restarts. This enables the delivery of a messages to be tracked
+ * after failures. There are two approaches
+ *
+ *
A list of delivery tokens for in-flight messages can be obtained using
+ * {@link IMqttAsyncClient#getPendingTokens()}. The waitForCompletion
+ * method can then be used to block until the delivery is complete.
+ *
A {@link MqttCallback} can be set on the client. Once a message has been
+ * delivered the {@link MqttCallback#deliveryComplete(IMqttToken)} method will
+ * be called withe delivery token being passed as a parameter.
+ *
+ *
+ * An action is in progress until either:
+ *
+ *
isComplete() returns true or
+ *
getException() is not null. If a client shuts down before delivery is complete
+ * an exception is returned. As long as the Java Runtime is not stopped a delivery token
+ * is valid across a connection disconnect and reconnect. In the event the client
+ * is shut down the getPendingTokens method can be used once the client is
+ * restarted to obtain a list of delivery tokens for inflight messages.
+ *
+ *
+ */
+
+public interface IMqttDeliveryToken extends IMqttToken {
+ /**
+ * Returns the message associated with this token.
+ *
Until the message has been delivered, the message being delivered will
+ * be returned. Once the message has been delivered null will be
+ * returned.
+ * @return the message associated with this token or null if already delivered.
+ * @throws MqttException if there was a problem completing retrieving the message
+ */
+ MqttMessage getMessage() throws MqttException;
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/IMqttMessageListener.java b/src/main/java/org/eclipse/paho/mqttv5/client/IMqttMessageListener.java
new file mode 100644
index 0000000..bab5dca
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/IMqttMessageListener.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2015 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Ian Craggs - initial API and implementation and/or initial documentation
+ */
+
+package org.eclipse.paho.mqttv5.client;
+
+import org.eclipse.paho.mqttv5.common.MqttMessage;
+
+/**
+ * Implementers of this interface will be notified when a message arrives.
+ *
+ */
+public interface IMqttMessageListener {
+ /**
+ * This method is called when a message arrives from the server.
+ *
+ *
+ * This method is invoked synchronously by the MQTT client. An
+ * acknowledgment is not sent back to the server until this
+ * method returns cleanly.
+ *
+ * If an implementation of this method throws an Exception, then the
+ * client will be shut down. When the client is next re-connected, any QoS
+ * 1 or 2 messages will be redelivered by the server.
+ *
+ * Any additional messages which arrive while an
+ * implementation of this method is running, will build up in memory, and
+ * will then back up on the network.
+ *
+ * If an application needs to persist data, then it
+ * should ensure the data is persisted prior to returning from this method, as
+ * after returning from this method, the message is considered to have been
+ * delivered, and will not be reproducible.
+ *
+ * It is possible to send a new message within an implementation of this callback
+ * (for example, a response to this message), but the implementation must not
+ * disconnect the client, as it will be impossible to send an acknowledgment for
+ * the message being processed, and a deadlock will occur.
+ *
+ * @param topic name of the topic on the message was published to
+ * @param message the actual message.
+ * @throws Exception if a terminal error has occurred, and the client should be
+ * shut down.
+ */
+ public void messageArrived(String topic, MqttMessage message) throws Exception;
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/IMqttToken.java b/src/main/java/org/eclipse/paho/mqttv5/client/IMqttToken.java
new file mode 100644
index 0000000..57747f7
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/IMqttToken.java
@@ -0,0 +1,206 @@
+/**************************************************************************
+ * Copyright (c) 2009, 2012 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ * Ian Craggs - MQTT 3.1.1 support
+ */
+package org.eclipse.paho.mqttv5.client;
+
+import org.eclipse.paho.mqttv5.common.MqttException;
+import org.eclipse.paho.mqttv5.common.MqttMessage;
+import org.eclipse.paho.mqttv5.common.packet.MqttProperties;
+import org.eclipse.paho.mqttv5.common.packet.MqttWireMessage;
+
+/**
+ * Provides a mechanism for tracking the completion of an asynchronous task.
+ *
+ *
When using the asynchronous/non-blocking MQTT programming interface all
+ * methods/operations that take any time (and in particular those that involve
+ * any network operation) return control to the caller immediately. The operation
+ * then proceeds to run in the background so as not to block the invoking thread.
+ * An IMqttToken is used to track the state of the operation. An application can use the
+ * token to wait for an operation to complete. A token is passed to callbacks
+ * once the operation completes and provides context linking it to the original
+ * request. A token is associated with a single operation.
+ *
+ * An action is in progress until either:
+ *
+ *
isComplete() returns true or
+ *
getException() is not null.
+ *
+ *
+ */
+public interface IMqttToken {
+
+ /**
+ * Blocks the current thread until the action this token is associated with has
+ * completed.
+ *
+ * @throws MqttException if there was a problem with the action associated with the token.
+ * @see #waitForCompletion(long)
+ */
+ public void waitForCompletion() throws MqttException;
+
+ /**
+ * Blocks the current thread until the action this token is associated with has
+ * completed.
+ *
The timeout specifies the maximum time it will block for. If the action
+ * completes before the timeout then control returns immediately, if not
+ * it will block until the timeout expires.
+ *
If the action being tracked fails or the timeout expires an exception will
+ * be thrown. In the event of a timeout the action may complete after timeout.
+ *
+ *
+ * @param timeout the maximum amount of time to wait for, in milliseconds.
+ * @throws MqttException if there was a problem with the action associated with the token.
+ */
+ public void waitForCompletion(long timeout) throws MqttException;
+
+ /**
+ * Returns whether or not the action has finished.
+ *
True will be returned both in the case where the action finished successfully
+ * and in the case where it failed. If the action failed {@link #getException()} will
+ * be non null.
+ *
+ * @return whether or not the action has finished.
+ */
+ public boolean isComplete();
+
+ /**
+ * Returns an exception providing more detail if an operation failed.
+ *
While an action in in progress and when an action completes successfully
+ * null will be returned. Certain errors like timeout or shutting down will not
+ * set the exception as the action has not failed or completed at that time
+ *
+ * @return exception may return an exception if the operation failed. Null will be
+ * returned while action is in progress and if action completes successfully.
+ */
+ public MqttException getException();
+
+ /**
+ * Register a listener to be notified when an action completes.
+ *
Once a listener is registered it will be invoked when the action the token
+ * is associated with either succeeds or fails.
+ *
+ * @param listener to be invoked once the action completes
+ */
+ public void setActionCallback(MqttActionListener listener);
+
+ /**
+ * Return the async listener for this token.
+ * @return listener that is set on the token or null if a listener is not registered.
+ */
+ public MqttActionListener getActionCallback();
+
+ /**
+ * Returns the MQTT client that is responsible for processing the asynchronous
+ * action
+ * @return the client
+ */
+ public MqttClientInterface getClient();
+
+ /**
+ * Returns the topic string(s) for the action being tracked by this
+ * token. If the action has not been initiated or the action has not
+ * topic associated with it such as connect then null will be returned.
+ *
+ * @return the topic string(s) for the subscribe being tracked by this token or null
+ */
+ public String[] getTopics();
+
+ /**
+ * Store some context associated with an action.
+ *
Allows the caller of an action to store some context that can be
+ * accessed from within the ActionListener associated with the action. This
+ * can be useful when the same ActionListener is associated with multiple
+ * actions
+ * @param userContext to associate with an action
+ */
+ public void setUserContext(Object userContext);
+
+ /**
+ * Retrieve the context associated with an action.
+ *
Allows the ActionListener associated with an action to retrieve any context
+ * that was associated with the action when the action was invoked. If not
+ * context was provided null is returned.
+
+ * @return Object context associated with an action or null if there is none.
+ */
+ public Object getUserContext();
+
+ /**
+ * Returns the message ID of the message that is associated with the token.
+ * A message id of zero will be returned for tokens associated with
+ * connect, disconnect and ping operations as there can only ever
+ * be one of these outstanding at a time. For other operations
+ * the MQTT message id flowed over the network.
+ * @return the message ID of the message that is associated with the token
+ */
+ public int getMessageId();
+
+ /**
+ * @return the granted QoS list from a suback
+ */
+ public int[] getGrantedQos();
+
+ /**
+ * Returns a list of reason codes that were returned as a result of this token's action.
+ * You will receive reason codes from the following MQTT actions:
+ *
+ *
CONNECT - in the corresponding CONNACK Packet.
+ *
PUBLISH - in the corresponding PUBACK, PUBREC, PUBCOMP, PUBREL packets
+ *
SUBSCRIBE - in the corresponding SUBACK Packet.
+ *
UNSUBSCRIBE - in the corresponding UNSUBACK Packet.
+ *
AUTH - in the returned AUTH Packet.
+ *
+ * @return the reason code(s) from the response for this token's action.
+ *
+ */
+ public int[] getReasonCodes();
+
+ /**
+ * @return the session present flag from a connack
+ */
+ public boolean getSessionPresent();
+
+ /**
+ * @return the response wire message
+ */
+ public MqttWireMessage getResponse();
+
+ /**
+ * @return the response wire message properties
+ */
+ public MqttProperties getResponseProperties();
+
+ /**
+ * Returns the message associated with this token.
+ *
Until the message has been delivered, the message being delivered will
+ * be returned. Once the message has been delivered null will be
+ * returned.
+ * @return the message associated with this token or null if already delivered.
+ * @throws MqttException if there was a problem completing retrieving the message
+ */
+ public MqttMessage getMessage() throws MqttException;
+
+ /**
+ * @return the request wire message
+ */
+ public MqttWireMessage getRequestMessage();
+
+ /**
+ * @return the request wire message properties
+ */
+ public MqttProperties getRequestProperties();
+
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/MqttActionListener.java b/src/main/java/org/eclipse/paho/mqttv5/client/MqttActionListener.java
new file mode 100644
index 0000000..7e14ac6
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/MqttActionListener.java
@@ -0,0 +1,30 @@
+package org.eclipse.paho.mqttv5.client;
+
+/**
+ * Implementors of this interface will be notified when an asynchronous action completes.
+ *
+ *
A listener is registered on an MqttToken and a token is associated
+ * with an action like connect or publish. When used with tokens on the MqttAsyncClient
+ * the listener will be called back on the MQTT client's thread. The listener will be informed
+ * if the action succeeds or fails. It is important that the listener returns control quickly
+ * otherwise the operation of the MQTT client will be stalled.
+ *
+ */
+public interface MqttActionListener {
+ /**
+ * This method is invoked when an action has completed successfully.
+ * @param asyncActionToken associated with the action that has completed
+ */
+ void onSuccess(IMqttToken asyncActionToken);
+ /**
+ * This method is invoked when an action fails.
+ * If a client is disconnected while an action is in progress
+ * onFailure will be called. For connections
+ * that use cleanStart set to false, any QoS 1 and 2 messages that
+ * are in the process of being delivered will be delivered to the requested
+ * quality of service next time the client connects.
+ * @param asyncActionToken associated with the action that has failed
+ * @param exception thrown by the action that has failed
+ */
+ void onFailure(IMqttToken asyncActionToken, Throwable exception);
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/MqttAsyncClient.java b/src/main/java/org/eclipse/paho/mqttv5/client/MqttAsyncClient.java
new file mode 100644
index 0000000..ced752d
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/MqttAsyncClient.java
@@ -0,0 +1,1813 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2019 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * James Sutton - initial API and implementation and/or initial documentation
+ */
+
+package org.eclipse.paho.mqttv5.client;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+
+import javax.net.SocketFactory;
+import org.eclipse.paho.mqttv5.client.internal.ClientComms;
+import org.eclipse.paho.mqttv5.client.internal.ConnectActionListener;
+import org.eclipse.paho.mqttv5.client.internal.DisconnectedMessageBuffer;
+import org.eclipse.paho.mqttv5.client.internal.MqttConnectionState;
+import org.eclipse.paho.mqttv5.client.internal.MqttSessionState;
+import org.eclipse.paho.mqttv5.client.internal.NetworkModule;
+import org.eclipse.paho.mqttv5.client.internal.NetworkModuleService;
+import org.eclipse.paho.mqttv5.client.persist.MemoryPersistence;
+import org.eclipse.paho.mqttv5.client.persist.MqttDefaultFilePersistence;
+import org.eclipse.paho.mqttv5.client.util.Debug;
+import org.eclipse.paho.mqttv5.client.logging.Logger;
+import org.eclipse.paho.mqttv5.client.logging.LoggerFactory;
+import org.eclipse.paho.mqttv5.common.ExceptionHelper;
+import org.eclipse.paho.mqttv5.common.MqttException;
+import org.eclipse.paho.mqttv5.common.MqttMessage;
+import org.eclipse.paho.mqttv5.common.MqttPersistenceException;
+import org.eclipse.paho.mqttv5.common.MqttSecurityException;
+import org.eclipse.paho.mqttv5.common.MqttSubscription;
+import org.eclipse.paho.mqttv5.common.packet.MqttAuth;
+import org.eclipse.paho.mqttv5.common.packet.MqttDataTypes;
+import org.eclipse.paho.mqttv5.common.packet.MqttDisconnect;
+import org.eclipse.paho.mqttv5.common.packet.MqttProperties;
+import org.eclipse.paho.mqttv5.common.packet.MqttPublish;
+import org.eclipse.paho.mqttv5.common.packet.MqttReturnCode;
+import org.eclipse.paho.mqttv5.common.packet.MqttSubscribe;
+import org.eclipse.paho.mqttv5.common.packet.MqttUnsubscribe;
+import org.eclipse.paho.mqttv5.common.util.MqttTopicValidator;
+
+/**
+ * Lightweight client for talking to an MQTT server using non-blocking methods
+ * that allow an operation to run in the background.
+ *
+ *
+ * This class implements the non-blocking {@link IMqttAsyncClient} client
+ * interface allowing applications to initiate MQTT actions and then carry on
+ * working while the MQTT action completes on a background thread. This
+ * implementation is compatible with all Java SE runtimes from 1.7 and up.
+ *
+ *
+ * An application can connect to an MQTT server using:
+ *
+ *
+ *
A plain TCP socket
+ *
A secure SSL/TLS socket
+ *
+ *
+ *
+ * To enable messages to be delivered even across network and client restarts
+ * messages need to be safely stored until the message has been delivered at the
+ * requested quality of service. A pluggable persistence mechanism is provided
+ * to store the messages.
+ *
+ *
+ * By default {@link MqttDefaultFilePersistence} is used to store messages to a
+ * file. If persistence is set to null then messages are stored in memory and
+ * hence can be lost if the client, Java runtime or device shuts down.
+ *
+ *
+ * If connecting with {@link MqttConnectionOptions#setCleanStart(boolean)} set
+ * to true it is safe to use memory persistence as all state is cleared when a
+ * client disconnects. If connecting with cleanStart set to false in order to
+ * provide reliable message delivery then a persistent message store such as the
+ * default one should be used.
+ *
+ *
+ * The message store interface is pluggable. Different stores can be used by
+ * implementing the {@link MqttClientPersistence} interface and passing it to
+ * the clients constructor.
+ *
+ *
+ * TODO - Class docs taken from IMqttAsyncClient, review for v5 Enables an
+ * application to communicate with an MQTT server using non-blocking methods.
+ *
+ * It provides applications a simple programming interface to all features of
+ * the MQTT version 3.1 specification including:
+ *
+ *
+ *
connect
+ *
publish
+ *
subscribe
+ *
unsubscribe
+ *
disconnect
+ *
+ *
+ * There are two styles of MQTT client, this one and {@link IMqttClient}.
+ *
+ *
+ *
IMqttAsyncClient provides a set of non-blocking methods that return
+ * control to the invoking application after initial validation of parameters
+ * and state. The main processing is performed in the background so as not to
+ * block the application program's thread. This non- blocking approach is handy
+ * when the application needs to carry on processing while the MQTT action takes
+ * place. For instance connecting to an MQTT server can take time, using the
+ * non-blocking connect method allows an application to display a busy indicator
+ * while the connect action takes place in the background. Non blocking methods
+ * are particularly useful in event oriented programs and graphical programs
+ * where invoking methods that take time to complete on the the main or GUI
+ * thread can cause problems. The non-blocking interface can also be used in
+ * blocking form.
+ *
IMqttClient provides a set of methods that block and return control to
+ * the application program once the MQTT action has completed. It is a thin
+ * layer that sits on top of the IMqttAsyncClient implementation and is provided
+ * mainly for compatibility with earlier versions of the MQTT client. In most
+ * circumstances it is recommended to use IMqttAsyncClient based clients which
+ * allow an application to mix both non-blocking and blocking calls.
+ *
+ *
+ * An application is not restricted to using one style if an IMqttAsyncClient
+ * based client is used as both blocking and non-blocking methods can be used in
+ * the same application. If an IMqttClient based client is used then only
+ * blocking methods are available to the application. For more details on the
+ * blocking client see {@link IMqttClient}
+ *
+ *
+ *
+ * There are two forms of non-blocking method:
+ *
+ * In this form the method returns a token that can be used to track the
+ * progress of the action (method). The method provides a waitForCompletion()
+ * method that once invoked will block until the action completes. Once
+ * completed there are method on the token that can be used to check if the
+ * action completed successfully or not. For example to wait until a connect
+ * completes:
+ *
+ *
+ *
+ * IMqttToken conToken;
+ * conToken = asyncClient.client.connect(conToken);
+ * ... do some work...
+ * conToken.waitForCompletion();
+ *
+ *
+ * To turn a method into a blocking invocation the following form can be used:
+ *
+ * In this form a callback is registered with the method. The callback will be
+ * notified when the action succeeds or fails. The callback is invoked on the
+ * thread managed by the MQTT client so it is important that processing is
+ * minimised in the callback. If not the operation of the MQTT client will be
+ * inhibited. For example to be notified (called back) when a connect completes:
+ *
+ * An optional context object can be passed into the method which will then be
+ * made available in the callback. The context is stored by the MQTT client) in
+ * the token which is then returned to the invoker. The token is provided to the
+ * callback methods where the context can then be accessed.
+ *
+ *
+ *
+ *
+ * To understand when the delivery of a message is complete either of the two
+ * methods above can be used to either wait on or be notified when the publish
+ * completes. An alternative is to use the
+ * {@link MqttCallback#deliveryComplete(IMqttToken)} method which will
+ * also be notified when a message has been delivered to the requested quality
+ * of service.
+ *
+ *
+ * @see IMqttAsyncClient
+ */
+public class MqttAsyncClient implements MqttClientInterface, IMqttAsyncClient {
+ private static final String CLASS_NAME = MqttAsyncClient.class.getName();
+ private Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT, CLASS_NAME);
+
+ private static final long QUIESCE_TIMEOUT = 30000; // ms
+ private static final long DISCONNECT_TIMEOUT = 10000; // ms
+ private static final char MIN_HIGH_SURROGATE = '\uD800';
+ private static final char MAX_HIGH_SURROGATE = '\uDBFF';
+ // private String clientId;
+ private String serverURI;
+ protected ClientComms comms;
+ private Hashtable topics;
+ private MqttClientPersistence persistence;
+ private MqttCallback mqttCallback;
+ private MqttConnectionOptions connOpts;
+ private Object userContext;
+ private Timer reconnectTimer; // Automatic reconnect timer
+ private static int reconnectDelay = 1000; // Reconnect delay, starts at 1
+ // second
+ private boolean reconnecting = false;
+ private static final Object clientLock = new Object(); // Simple lock
+
+ // Variables that exist within the life of an MQTT session
+ private MqttSessionState mqttSession = new MqttSessionState();
+
+ // Variables that exist within the life of an MQTT connection.
+ private MqttConnectionState mqttConnection;
+
+ private ScheduledExecutorService executorService;
+ private MqttPingSender pingSender;
+
+ /**
+ * Create an MqttAsyncClient that is used to communicate with an MQTT server.
+ *
+ * The address of a server can be specified on the constructor. Alternatively a
+ * list containing one or more servers can be specified using the
+ * {@link MqttConnectionOptions#setServerURIs(String[]) setServerURIs} method on
+ * MqttConnectOptions.
+ *
+ *
+ * The serverURI parameter is typically used with the the
+ * clientId parameter to form a key. The key is used to store and
+ * reference messages while they are being delivered. Hence the serverURI
+ * specified on the constructor must still be specified even if a list of
+ * servers is specified on an MqttConnectOptions object. The serverURI on the
+ * constructor must remain the same across restarts of the client for delivery
+ * of messages to be maintained from a given client to a given server or set of
+ * servers.
+ *
+ *
+ * The address of the server to connect to is specified as a URI. Two types of
+ * connection are supported tcp:// for a TCP connection and
+ * ssl:// for a TCP connection secured by SSL/TLS. For example:
+ *
+ *
+ *
tcp://localhost:1883
+ *
ssl://localhost:8883
+ *
+ *
+ * If the port is not specified, it will default to 1883 for
+ * tcp://" URIs, and 8883 for ssl:// URIs.
+ *
+ *
+ *
+ * A client identifier clientId must be specified and be less that
+ * 65535 characters. It must be unique across all clients connecting to the same
+ * server. The clientId is used by the server to store data related to the
+ * client, hence it is important that the clientId remain the same when
+ * connecting to a server if durable subscriptions or reliable messaging are
+ * required.
+ *
+ * As the client identifier is used by the server to identify a client when it
+ * reconnects, the client must use the same identifier between connections if
+ * durable subscriptions or reliable delivery of messages is required.
+ *
+ *
+ * In Java SE, SSL can be configured in one of several ways, which the client
+ * will use in the following order:
+ *
+ *
+ *
Supplying an SSLSocketFactory -
+ * applications can use
+ * {@link MqttConnectionOptions#setSocketFactory(SocketFactory)} to supply a
+ * factory with the appropriate SSL settings.
+ *
SSL Properties - applications can supply SSL settings as
+ * a simple Java Properties using
+ * {@link MqttConnectionOptions#setSSLProperties(Properties)}.
+ *
Use JVM settings - There are a number of standard Java
+ * system properties that can be used to configure key and trust stores.
+ *
+ *
+ *
+ * In Java ME, the platform settings are used for SSL connections.
+ *
+ *
+ *
+ * An instance of the default persistence mechanism
+ * {@link MqttDefaultFilePersistence} is used by the client. To specify a
+ * different persistence mechanism or to turn off persistence, use the
+ * {@link #MqttAsyncClient(String, String, MqttClientPersistence)} constructor.
+ *
+ * @param serverURI
+ * the address of the server to connect to, specified as a URI. Can
+ * be overridden using
+ * {@link MqttConnectionOptions#setServerURIs(String[])}
+ * @param clientId
+ * a client identifier that is unique on the server being connected
+ * to
+ * @throws IllegalArgumentException
+ * if the URI does not start with "tcp://", "ssl://" or "local://".
+ * @throws IllegalArgumentException
+ * if the clientId is null or is greater than 65535 characters in
+ * length
+ * @throws MqttException
+ * if any other problem was encountered
+ */
+ public MqttAsyncClient(String serverURI, String clientId) throws MqttException {
+ this(serverURI, clientId, new MqttDefaultFilePersistence());
+ }
+
+ /**
+ * Create an MqttAsyncClient that is used to communicate with an MQTT server.
+ *
+ * The address of a server can be specified on the constructor. Alternatively a
+ * list containing one or more servers can be specified using the
+ * {@link MqttConnectionOptions#setServerURIs(String[]) setServerURIs} method on
+ * MqttConnectOptions.
+ *
+ *
+ * The serverURI parameter is typically used with the the
+ * clientId parameter to form a key. The key is used to store and
+ * reference messages while they are being delivered. Hence the serverURI
+ * specified on the constructor must still be specified even if a list of
+ * servers is specified on an MqttConnectOptions object. The serverURI on the
+ * constructor must remain the same across restarts of the client for delivery
+ * of messages to be maintained from a given client to a given server or set of
+ * servers.
+ *
+ *
+ * The address of the server to connect to is specified as a URI. Two types of
+ * connection are supported tcp:// for a TCP connection and
+ * ssl:// for a TCP connection secured by SSL/TLS. For example:
+ *
+ *
+ *
tcp://localhost:1883
+ *
ssl://localhost:8883
+ *
+ *
+ * If the port is not specified, it will default to 1883 for
+ * tcp://" URIs, and 8883 for ssl:// URIs.
+ *
+ *
+ *
+ * A client identifier clientId must be specified and be less that
+ * 65535 characters. It must be unique across all clients connecting to the same
+ * server. The clientId is used by the server to store data related to the
+ * client, hence it is important that the clientId remain the same when
+ * connecting to a server if durable subscriptions or reliable messaging are
+ * required.
+ *
+ * As the client identifier is used by the server to identify a client when it
+ * reconnects, the client must use the same identifier between connections if
+ * durable subscriptions or reliable delivery of messages is required.
+ *
+ *
+ * In Java SE, SSL can be configured in one of several ways, which the client
+ * will use in the following order:
+ *
+ *
+ *
Supplying an SSLSocketFactory -
+ * applications can use
+ * {@link MqttConnectionOptions#setSocketFactory(SocketFactory)} to supply a
+ * factory with the appropriate SSL settings.
+ *
SSL Properties - applications can supply SSL settings as
+ * a simple Java Properties using
+ * {@link MqttConnectionOptions#setSSLProperties(Properties)}.
+ *
Use JVM settings - There are a number of standard Java
+ * system properties that can be used to configure key and trust stores.
+ *
+ *
+ *
+ * In Java ME, the platform settings are used for SSL connections.
+ *
+ *
+ * A persistence mechanism is used to enable reliable messaging. For messages
+ * sent at qualities of service (QoS) 1 or 2 to be reliably delivered, messages
+ * must be stored (on both the client and server) until the delivery of the
+ * message is complete. If messages are not safely stored when being delivered
+ * then a failure in the client or server can result in lost messages. A
+ * pluggable persistence mechanism is supported via the
+ * {@link MqttClientPersistence} interface. An implementer of this interface
+ * that safely stores messages must be specified in order for delivery of
+ * messages to be reliable. In addition
+ * {@link MqttConnectionOptions#setCleanStart(boolean)} must be set to false. In
+ * the event that only QoS 0 messages are sent or received or cleanStart is set
+ * to true then a safe store is not needed.
+ *
+ *
+ * An implementation of file-based persistence is provided in class
+ * {@link MqttDefaultFilePersistence} which will work in all Java SE based
+ * systems. If no persistence is needed, the persistence parameter can be
+ * explicitly set to null.
+ *
+ *
+ * @param serverURI
+ * the address of the server to connect to, specified as a URI. Can
+ * be overridden using
+ * {@link MqttConnectionOptions#setServerURIs(String[])}
+ * @param clientId
+ * a client identifier that is unique on the server being connected
+ * to
+ * @param persistence
+ * the persistence class to use to store in-flight message. If null
+ * then the default persistence mechanism is used
+ * @throws MqttException
+ * if any other problem was encountered
+ */
+ public MqttAsyncClient(String serverURI, String clientId, MqttClientPersistence persistence) throws MqttException {
+ this(serverURI, clientId, persistence, null, null);
+ }
+
+ /**
+ * Create an MqttAsyncClient that is used to communicate with an MQTT server.
+ *
+ * The address of a server can be specified on the constructor. Alternatively a
+ * list containing one or more servers can be specified using the
+ * {@link MqttConnectionOptions#setServerURIs(String[]) setServerURIs} method on
+ * MqttConnectOptions.
+ *
+ *
+ * The serverURI parameter is typically used with the the
+ * clientId parameter to form a key. The key is used to store and
+ * reference messages while they are being delivered. Hence the serverURI
+ * specified on the constructor must still be specified even if a list of
+ * servers is specified on an MqttConnectOptions object. The serverURI on the
+ * constructor must remain the same across restarts of the client for delivery
+ * of messages to be maintained from a given client to a given server or set of
+ * servers.
+ *
+ *
+ * The address of the server to connect to is specified as a URI. Two types of
+ * connection are supported tcp:// for a TCP connection and
+ * ssl:// for a TCP connection secured by SSL/TLS. For example:
+ *
+ *
+ *
tcp://localhost:1883
+ *
ssl://localhost:8883
+ *
+ *
+ * If the port is not specified, it will default to 1883 for
+ * tcp://" URIs, and 8883 for ssl:// URIs.
+ *
+ *
+ *
+ * A client identifier clientId must be specified and be less that
+ * 65535 characters. It must be unique across all clients connecting to the same
+ * server. The clientId is used by the server to store data related to the
+ * client, hence it is important that the clientId remain the same when
+ * connecting to a server if durable subscriptions or reliable messaging are
+ * required.
+ *
+ * As the client identifier is used by the server to identify a client when it
+ * reconnects, the client must use the same identifier between connections if
+ * durable subscriptions or reliable delivery of messages is required.
+ *
+ *
+ * In Java SE, SSL can be configured in one of several ways, which the client
+ * will use in the following order:
+ *
+ *
+ *
Supplying an SSLSocketFactory -
+ * applications can use
+ * {@link MqttConnectionOptions#setSocketFactory(SocketFactory)} to supply a
+ * factory with the appropriate SSL settings.
+ *
SSL Properties - applications can supply SSL settings as
+ * a simple Java Properties using
+ * {@link MqttConnectionOptions#setSSLProperties(Properties)}.
+ *
Use JVM settings - There are a number of standard Java
+ * system properties that can be used to configure key and trust stores.
+ *
+ *
+ *
+ * In Java ME, the platform settings are used for SSL connections.
+ *
+ *
+ * A persistence mechanism is used to enable reliable messaging. For messages
+ * sent at qualities of service (QoS) 1 or 2 to be reliably delivered, messages
+ * must be stored (on both the client and server) until the delivery of the
+ * message is complete. If messages are not safely stored when being delivered
+ * then a failure in the client or server can result in lost messages. A
+ * pluggable persistence mechanism is supported via the
+ * {@link MqttClientPersistence} interface. An implementer of this interface
+ * that safely stores messages must be specified in order for delivery of
+ * messages to be reliable. In addition
+ * {@link MqttConnectionOptions#setCleanStart(boolean)} must be set to false. In
+ * the event that only QoS 0 messages are sent or received or cleanStart is set
+ * to true then a safe store is not needed.
+ *
+ *
+ * An implementation of file-based persistence is provided in class
+ * {@link MqttDefaultFilePersistence} which will work in all Java SE based
+ * systems. If no persistence is needed, the persistence parameter can be
+ * explicitly set to null.
+ *
+ *
+ * @param serverURI
+ * the address of the server to connect to, specified as a URI. Can
+ * be overridden using
+ * {@link MqttConnectionOptions#setServerURIs(String[])}
+ * @param clientId
+ * a client identifier that is unique on the server being connected
+ * to
+ * @param persistence
+ * the persistence class to use to store in-flight message. If null
+ * then the default persistence mechanism is used
+ * @param pingSender
+ * the {@link MqttPingSender} Implementation to handle timing and
+ * sending Ping messages to the server.
+ * @param executorService
+ * used for managing threads. If null then a newScheduledThreadPool
+ * is used.
+ * @throws IllegalArgumentException
+ * if the URI does not start with "tcp://", "ssl://" or "local://"
+ * @throws IllegalArgumentException
+ * if the clientId is null or is greater than 65535 characters in
+ * length
+ * @throws MqttException
+ * if any other problem was encountered
+ */
+ public MqttAsyncClient(String serverURI, String clientId, MqttClientPersistence persistence,
+ MqttPingSender pingSender, ScheduledExecutorService executorService) throws MqttException {
+ final String methodName = "MqttAsyncClient";
+
+ log.setResourceName(clientId);
+
+ if (clientId != null) {
+ // Verify that the client ID is not too long
+ // Encode it ourselves to check
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(baos);
+ MqttDataTypes.encodeUTF8(dos, clientId);
+ // Remove the two size bytes.
+ if (dos.size() - 2 > 65535) {
+ throw new IllegalArgumentException("ClientId longer than 65535 characters");
+ }
+
+ } else {
+ clientId = "";
+ }
+
+ mqttConnection = new MqttConnectionState(clientId);
+
+ NetworkModuleService.validateURI(serverURI);
+
+ this.serverURI = serverURI;
+ this.mqttSession.setClientId(clientId);
+
+ this.persistence = persistence;
+ if (this.persistence == null) {
+ this.persistence = new MemoryPersistence();
+ }
+
+ this.executorService = executorService;
+
+ this.pingSender = pingSender;
+ if (this.pingSender == null) {
+ this.pingSender = new TimerPingSender(this.executorService);
+ }
+
+ // @TRACE 101= ClientID={0} ServerURI={1} PersistenceType={2}
+ log.fine(CLASS_NAME, methodName, "101", new Object[] { clientId, serverURI, persistence });
+
+ this.persistence.open(clientId);
+ this.comms = new ClientComms(this, this.persistence, this.pingSender, this.executorService, this.mqttSession,
+ this.mqttConnection);
+ this.persistence.close();
+ this.topics = new Hashtable();
+
+ }
+
+ /**
+ * @param ch
+ * the character to check.
+ * @return returns 'true' if the character is a high-surrogate code unit
+ */
+ protected static boolean Character_isHighSurrogate(char ch) {
+ return (ch >= MIN_HIGH_SURROGATE) && (ch <= MAX_HIGH_SURROGATE);
+ }
+
+ /**
+ * Factory method to create an array of network modules, one for each of the
+ * supplied URIs
+ *
+ * @param address
+ * the URI for the server.
+ * @param options
+ * the {@link MqttConnectionOptions} for the connection.
+ * @return a network module appropriate to the specified address.
+ * @throws MqttException
+ * if an exception occurs creating the network Modules
+ * @throws MqttSecurityException
+ * if an issue occurs creating an SSL / TLS Socket
+ */
+ protected NetworkModule[] createNetworkModules(String address, MqttConnectionOptions options)
+ throws MqttException, MqttSecurityException {
+ final String methodName = "createNetworkModules";
+ // @TRACE 116=URI={0}
+ log.fine(CLASS_NAME, methodName, "116", new Object[] { address });
+
+ NetworkModule[] networkModules = null;
+ String[] serverURIs = options.getServerURIs();
+ String[] array = null;
+ if (serverURIs == null) {
+ array = new String[] { address };
+ } else if (serverURIs.length == 0) {
+ array = new String[] { address };
+ } else {
+ array = serverURIs;
+ }
+
+ networkModules = new NetworkModule[array.length];
+ for (int i = 0; i < array.length; i++) {
+ networkModules[i] = createNetworkModule(array[i], options);
+ }
+
+ log.fine(CLASS_NAME, methodName, "108");
+ return networkModules;
+ }
+
+ /**
+ * Factory method to create the correct network module, based on the supplied
+ * address URI.
+ *
+ * @param address
+ * the URI for the server.
+ * @param options
+ * Connect options
+ * @return a network module appropriate to the specified address.
+ */
+ private NetworkModule createNetworkModule(String address, MqttConnectionOptions options)
+ throws MqttException, MqttSecurityException {
+ final String methodName = "createNetworkModule";
+ // @TRACE 115=URI={0}
+ log.fine(CLASS_NAME, methodName, "115", new Object[] { address });
+
+
+ NetworkModule netModule = NetworkModuleService.createInstance(address, options, mqttSession.getClientId());
+
+ return netModule;
+ }
+
+ private String getHostName(String uri) {
+ int portIndex = uri.indexOf(':');
+ if (portIndex == -1) {
+ portIndex = uri.indexOf('/');
+ }
+ if (portIndex == -1) {
+ portIndex = uri.length();
+ }
+ return uri.substring(0, portIndex);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#connect(java.lang.Object,
+ * org.eclipse.paho.mqttv5.client.MqttActionListener)
+ */
+ @Override
+ public IMqttToken connect(Object userContext, MqttActionListener callback)
+ throws MqttException, MqttSecurityException {
+ return this.connect(new MqttConnectionOptions(), userContext, callback);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttAsyncClient#connect()
+ */
+ @Override
+ public IMqttToken connect() throws MqttException, MqttSecurityException {
+ return this.connect(null, null);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#connect(org.eclipse.paho.
+ * mqttv5.client.MqttConnectionOptions)
+ */
+ @Override
+ public IMqttToken connect(MqttConnectionOptions options) throws MqttException, MqttSecurityException {
+ return this.connect(options, null, null);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#connect(org.eclipse.paho.
+ * mqttv5.client.MqttConnectionOptions, java.lang.Object,
+ * org.eclipse.paho.mqttv5.client.MqttActionListener)
+ */
+ @Override
+ public IMqttToken connect(MqttConnectionOptions options, Object userContext, MqttActionListener callback)
+ throws MqttException, MqttSecurityException {
+ final String methodName = "connect";
+ if (comms.isConnected()) {
+ throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_CLIENT_CONNECTED);
+ }
+ if (comms.isConnecting()) {
+ throw new MqttException(MqttClientException.REASON_CODE_CONNECT_IN_PROGRESS);
+ }
+ if (comms.isDisconnecting()) {
+ throw new MqttException(MqttClientException.REASON_CODE_CLIENT_DISCONNECTING);
+ }
+ if (comms.isClosed()) {
+ throw new MqttException(MqttClientException.REASON_CODE_CLIENT_CLOSED);
+ }
+ if (options == null) {
+ options = new MqttConnectionOptions();
+ }
+ this.connOpts = options;
+ this.userContext = userContext;
+ final boolean automaticReconnect = options.isAutomaticReconnect();
+
+ // @TRACE 103=cleanStart={0} connectionTimeout={1} TimekeepAlive={2}
+ // userName={3} password={4} will={5} userContext={6} callback={7}
+ log.fine(CLASS_NAME, methodName, "103",
+ new Object[] { Boolean.valueOf(options.isCleanStart()), Integer.valueOf(options.getConnectionTimeout()),
+ Integer.valueOf(options.getKeepAliveInterval()), options.getUserName(),
+ ((null == options.getPassword()) ? "[null]" : "[notnull]"),
+ ((null == options.getWillMessage()) ? "[null]" : "[notnull]"), userContext, callback });
+ comms.setNetworkModules(createNetworkModules(serverURI, options));
+ comms.setReconnectCallback(new MqttReconnectCallback(automaticReconnect));
+
+ // Insert our own callback to iterate through the URIs till the connect
+ // succeeds
+ MqttToken userToken = new MqttToken(getClientId());
+ ConnectActionListener connectActionListener = new ConnectActionListener(this, persistence, comms, options,
+ userToken, userContext, callback, reconnecting, mqttSession, this.mqttConnection);
+ userToken.setActionCallback(connectActionListener);
+ userToken.setUserContext(this);
+
+ this.mqttConnection.setSendReasonMessages(this.connOpts.isSendReasonMessages());
+
+ // If we are using the MqttCallbackExtended, set it on the
+ // connectActionListener
+ if (this.mqttCallback instanceof MqttCallback) {
+ connectActionListener.setMqttCallbackExtended((MqttCallback) this.mqttCallback);
+ }
+
+ if (this.connOpts.isCleanStart()) {
+ this.mqttSession.clearSessionState();
+ }
+ this.mqttConnection.clearConnectionState();
+
+ this.mqttConnection.setIncomingTopicAliasMax(this.connOpts.getTopicAliasMaximum());
+
+ comms.setNetworkModuleIndex(0);
+ connectActionListener.connect();
+
+ return userToken;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#disconnect(java.lang.Object,
+ * org.eclipse.paho.mqttv5.client.MqttActionListener)
+ */
+ @Override
+ public IMqttToken disconnect(Object userContext, MqttActionListener callback) throws MqttException {
+ return this.disconnect(QUIESCE_TIMEOUT, userContext, callback, MqttReturnCode.RETURN_CODE_SUCCESS,
+ new MqttProperties());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttAsyncClient#disconnect()
+ */
+ @Override
+ public IMqttToken disconnect() throws MqttException {
+ return this.disconnect(null, null);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttAsyncClient#disconnect(long)
+ */
+ @Override
+ public IMqttToken disconnect(long quiesceTimeout) throws MqttException {
+ return this.disconnect(quiesceTimeout, null, null, MqttReturnCode.RETURN_CODE_SUCCESS, new MqttProperties());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttAsyncClient#disconnect(long,
+ * java.lang.Object, org.eclipse.paho.mqttv5.client.MqttActionListener, int,
+ * org.eclipse.paho.mqttv5.common.packet.MqttProperties)
+ */
+ @Override
+ public IMqttToken disconnect(long quiesceTimeout, Object userContext, MqttActionListener callback, int reasonCode,
+ MqttProperties disconnectProperties) throws MqttException {
+ final String methodName = "disconnect";
+ // @TRACE 104=> quiesceTimeout={0} userContext={1} callback={2}
+ log.fine(CLASS_NAME, methodName, "104", new Object[] { Long.valueOf(quiesceTimeout), userContext, callback });
+
+ MqttToken token = new MqttToken(getClientId());
+ token.setActionCallback(callback);
+ token.setUserContext(userContext);
+
+ MqttDisconnect disconnect = new MqttDisconnect(reasonCode, disconnectProperties);
+
+ try {
+ comms.disconnect(disconnect, quiesceTimeout, token);
+ Thread.sleep(100);
+ } catch (MqttException ex) {
+ // @TRACE 105=< exception
+ log.fine(CLASS_NAME, methodName, "105", null, ex);
+ throw ex;
+ } catch (Exception tex) {}
+ // @TRACE 108=<
+ log.fine(CLASS_NAME, methodName, "108");
+
+ return token;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttAsyncClient#disconnectForcibly()
+ */
+ @Override
+ public void disconnectForcibly() throws MqttException {
+ disconnectForcibly(QUIESCE_TIMEOUT, DISCONNECT_TIMEOUT, MqttReturnCode.RETURN_CODE_SUCCESS,
+ new MqttProperties());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttAsyncClient#disconnectForcibly(long)
+ */
+ @Override
+ public void disconnectForcibly(long disconnectTimeout) throws MqttException {
+ disconnectForcibly(QUIESCE_TIMEOUT, disconnectTimeout, MqttReturnCode.RETURN_CODE_SUCCESS,
+ new MqttProperties());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttAsyncClient#disconnectForcibly(long,
+ * long, int, org.eclipse.paho.mqttv5.common.packet.MqttProperties)
+ */
+ @Override
+ public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout, int reasonCode,
+ MqttProperties disconnectProperties) throws MqttException {
+ final String methodName = "disconnectForcibly";
+ try {
+ comms.disconnectForcibly(quiesceTimeout, disconnectTimeout, reasonCode, disconnectProperties);
+ Thread.sleep(100);
+ } catch (MqttException ex) {
+ // @TRACE 105=< exception
+ log.fine(CLASS_NAME, methodName, "105", null, ex);
+ throw ex;
+ } catch (Exception tex) {}
+ // @TRACE 108=<
+ log.fine(CLASS_NAME, methodName, "108");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttAsyncClient#disconnectForcibly(long,
+ * long, boolean)
+ */
+ @Override
+ public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout, boolean sendDisconnectPacket)
+ throws MqttException {
+ comms.disconnectForcibly(quiesceTimeout, disconnectTimeout, sendDisconnectPacket,
+ MqttReturnCode.RETURN_CODE_SUCCESS, new MqttProperties());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttAsyncClient#isConnected()
+ */
+ @Override
+ public boolean isConnected() {
+ return comms.isConnected();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttAsyncClient#getClientId()
+ */
+ @Override
+ public String getClientId() {
+ return this.mqttSession.getClientId();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#setClientId(java.lang.String)
+ */
+ @Override
+ public void setClientId(String clientId) {
+ this.mqttSession.setClientId(clientId);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttAsyncClient#getServerURI()
+ */
+ @Override
+ public String getServerURI() {
+ return serverURI;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttAsyncClient#getCurrentServerURI()
+ */
+ @Override
+ public String getCurrentServerURI() {
+ return comms.getNetworkModules()[comms.getNetworkModuleIndex()].getServerURI();
+ }
+
+ /**
+ * Get a topic object which can be used to publish messages.
+ *
+ * There are two alternative methods that should be used in preference to this
+ * one when publishing a message:
+ *
+ *
+ *
{@link MqttAsyncClient#publish(String, MqttMessage)} to publish a message
+ * in a non-blocking manner or
+ *
{@link MqttClient#publish(String, MqttMessage)} to publish
+ * a message in a blocking manner
+ *
+ *
+ * When you build an application, the design of the topic tree should take into
+ * account the following principles of topic name syntax and semantics:
+ *
+ *
+ *
+ *
A topic must be at least one character long.
+ *
Topic names are case sensitive. For example, ACCOUNTS and
+ * Accounts are two different topics.
+ *
Topic names can include the space character. For example, Accounts
+ * payable is a valid topic.
+ *
A leading "/" creates a distinct topic. For example, /finance is
+ * different from finance. /finance matches "+/+" and "/+",
+ * but not "+".
+ *
Do not include the null character (Unicode \x0000) in any topic.
+ *
+ *
+ *
+ * The following principles apply to the construction and content of a topic
+ * tree:
+ *
+ *
+ *
+ *
The length is limited to 64k but within that there are no limits to the
+ * number of levels in a topic tree.
+ *
There can be any number of root nodes; that is, there can be any number
+ * of topic trees.
+ *
+ *
+ * @param topic
+ * the topic to use, for example "finance/stock/ibm".
+ * @return an MqttTopic object, which can be used to publish messages to the
+ * topic.
+ * @throws IllegalArgumentException
+ * if the topic contains a '+' or '#' wildcard character.
+ */
+ protected MqttTopic getTopic(String topic) {
+ MqttTopicValidator.validate(topic, false/* wildcards NOT allowed */, true);
+
+ MqttTopic result = (MqttTopic) topics.get(topic);
+ if (result == null) {
+ result = new MqttTopic(topic, comms);
+ topics.put(topic, result);
+ }
+ return result;
+ }
+
+ /*
+ * (non-Javadoc) Check and send a ping if needed.
By default, client sends
+ * PingReq to server to keep the connection to server. For some platforms which
+ * cannot use this mechanism, such as Android, developer needs to handle the
+ * ping request manually with this method.
+ *
+ * @throws MqttException for other errors encountered while publishing the
+ * message.
+ */
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#checkPing(java.lang.Object,
+ * org.eclipse.paho.mqttv5.client.MqttActionListener)
+ */
+ @Override
+ public IMqttToken checkPing(Object userContext, MqttActionListener callback) throws MqttException {
+ final String methodName = "ping";
+ MqttToken token;
+ // @TRACE 117=>
+ log.fine(CLASS_NAME, methodName, "117");
+
+ token = comms.checkForActivity(callback);
+ // @TRACE 118=<
+ log.fine(CLASS_NAME, methodName, "118");
+
+ return token;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#subscribe(java.lang.String,
+ * int, java.lang.Object, org.eclipse.paho.mqttv5.client.MqttActionListener)
+ */
+ @Override
+ public IMqttToken subscribe(String topicFilter, int qos, Object userContext, MqttActionListener callback)
+ throws MqttException {
+ return this.subscribe(new MqttSubscription[] { new MqttSubscription(topicFilter, qos) }, userContext, callback,
+ new MqttProperties());
+ }
+
+ @Override
+ public IMqttToken subscribe(String[] topicFilters, int[] qoss, Object userContext, MqttActionListener callback)
+ throws MqttException {
+ MqttSubscription[] subs = new MqttSubscription[topicFilters.length];
+ for (int i = 0; i < topicFilters.length; ++ i) {
+ subs[i] = new MqttSubscription(topicFilters[i], qoss[i]);
+ }
+ return this.subscribe(subs, userContext, callback, new MqttProperties());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#subscribe(java.lang.String,
+ * int)
+ */
+ @Override
+ public IMqttToken subscribe(String topicFilter, int qos) throws MqttException {
+ return this.subscribe(new MqttSubscription[] { new MqttSubscription(topicFilter, qos) }, null, null,
+ new MqttProperties());
+ }
+
+ @Override
+ public IMqttToken subscribe(String[] topicFilters, int[] qoss) throws MqttException {
+ return this.subscribe(topicFilters, qoss, null, null);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#subscribe(java.lang.String,
+ * int)
+ */
+ @Override
+ public IMqttToken subscribe(MqttSubscription subscription) throws MqttException {
+ return this.subscribe(new MqttSubscription[] { subscription }, null, null, new MqttProperties());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#subscribe(org.eclipse.paho.
+ * mqttv5.common.MqttSubscription[])
+ */
+ @Override
+ public IMqttToken subscribe(MqttSubscription[] subscriptions) throws MqttException {
+ return this.subscribe(subscriptions, null, null, new MqttProperties());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#subscribe(org.eclipse.paho.
+ * mqttv5.common.MqttSubscription[], java.lang.Object,
+ * org.eclipse.paho.mqttv5.client.MqttActionListener,
+ * org.eclipse.paho.mqttv5.common.packet.MqttProperties)
+ */
+ @Override
+ public IMqttToken subscribe(MqttSubscription[] subscriptions, Object userContext, MqttActionListener callback,
+ MqttProperties subscriptionProperties) throws MqttException {
+
+ // remove any message handlers for individual topics and validate Topics
+ for (MqttSubscription subscription : subscriptions) {
+ this.comms.removeMessageListener(subscription.getTopic());
+ // Check if the topic filter is valid before subscribing
+ MqttTopicValidator.validate(subscription.getTopic(),
+ this.mqttConnection.isWildcardSubscriptionsAvailable(),
+ this.mqttConnection.isSharedSubscriptionsAvailable());
+ }
+
+ return this.subscribeBase(subscriptions, userContext, callback, subscriptionProperties);
+ }
+
+ private IMqttToken subscribeBase(MqttSubscription[] subscriptions, Object userContext, MqttActionListener callback,
+ MqttProperties subscriptionProperties) throws MqttException {
+ final String methodName = "subscribe";
+
+ // Only Generate Log string if we are logging at FINE level
+ if (log.isLoggable(Logger.FINE)) {
+ StringBuffer subs = new StringBuffer();
+ for (int i = 0; i < subscriptions.length; i++) {
+ if (i > 0) {
+ subs.append(", ");
+ }
+ subs.append(subscriptions[i].toString());
+ }
+ // @TRACE 106=Subscribe topicFilter={0} userContext={1} callback={2}
+ log.fine(CLASS_NAME, methodName, "106", new Object[] { subs.toString(), userContext, callback });
+ }
+
+ MqttToken token = new MqttToken(getClientId());
+ token.setActionCallback(callback);
+ token.setUserContext(userContext);
+ // TODO - Somehow refactor this....
+ // token.internalTok.setTopics(topicFilters);
+ // TODO - Build up MQTT Subscriptions properly here
+
+ MqttSubscribe register = new MqttSubscribe(subscriptions, subscriptionProperties);
+ token.setRequestMessage(register);
+ comms.sendNoWait(register, token);
+ // @TRACE 109=<
+ log.fine(CLASS_NAME, methodName, "109");
+
+ return token;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#subscribe(org.eclipse.paho.
+ * mqttv5.common.MqttSubscription, java.lang.Object,
+ * org.eclipse.paho.mqttv5.client.MqttActionListener,
+ * org.eclipse.paho.mqttv5.client.IMqttMessageListener,
+ * org.eclipse.paho.mqttv5.common.packet.MqttProperties)
+ */
+ @Override
+ public IMqttToken subscribe(MqttSubscription mqttSubscription, Object userContext, MqttActionListener callback,
+ IMqttMessageListener messageListener, MqttProperties subscriptionProperties) throws MqttException {
+
+ return this.subscribe(new MqttSubscription[] { mqttSubscription }, userContext, callback, messageListener,
+ subscriptionProperties);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#subscribe(org.eclipse.paho.
+ * mqttv5.common.MqttSubscription,
+ * org.eclipse.paho.mqttv5.client.IMqttMessageListener)
+ */
+ @Override
+ public IMqttToken subscribe(MqttSubscription subscription, IMqttMessageListener messageListener)
+ throws MqttException {
+ return this.subscribe(new MqttSubscription[] { subscription }, null, null, messageListener,
+ new MqttProperties());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#subscribe(org.eclipse.paho.
+ * mqttv5.common.MqttSubscription[],
+ * org.eclipse.paho.mqttv5.client.IMqttMessageListener)
+ */
+ @Override
+ public IMqttToken subscribe(MqttSubscription[] subscriptions, IMqttMessageListener messageListener)
+ throws MqttException {
+ return this.subscribe(subscriptions, null, null, messageListener, new MqttProperties());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#subscribe(org.eclipse.paho.
+ * mqttv5.common.MqttSubscription[], java.lang.Object,
+ * org.eclipse.paho.mqttv5.client.MqttActionListener,
+ * org.eclipse.paho.mqttv5.client.IMqttMessageListener[],
+ * org.eclipse.paho.mqttv5.common.packet.MqttProperties)
+ */
+ @Override
+ public IMqttToken subscribe(MqttSubscription[] subscriptions, Object userContext, MqttActionListener callback,
+ IMqttMessageListener[] messageListeners, MqttProperties subscriptionProperties) throws MqttException {
+
+ // add message handlers to the list for this client
+ for (int i = 0; i < subscriptions.length; ++i) {
+ MqttTopicValidator.validate(subscriptions[i].getTopic(),
+ this.mqttConnection.isWildcardSubscriptionsAvailable(),
+ this.mqttConnection.isSharedSubscriptionsAvailable());
+ if (messageListeners == null || messageListeners[i] == null) {
+ this.comms.removeMessageListener(subscriptions[i].getTopic());
+ } else {
+ this.comms.setMessageListener(null, subscriptions[i].getTopic(), messageListeners[i]);
+ }
+ }
+
+ IMqttToken token = null;
+ try {
+ token = this.subscribeBase(subscriptions, userContext, callback, subscriptionProperties);
+ } catch(Exception e) {
+ // if the subscribe fails, then we have to remove the message handlers
+ for (MqttSubscription subscription : subscriptions) {
+ this.comms.removeMessageListener(subscription.getTopic());
+ }
+ throw e;
+ }
+ return token;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#subscribe(org.eclipse.paho.
+ * mqttv5.common.MqttSubscription[], java.lang.Object,
+ * org.eclipse.paho.mqttv5.client.MqttActionListener,
+ * org.eclipse.paho.mqttv5.client.IMqttMessageListener,
+ * org.eclipse.paho.mqttv5.common.packet.MqttProperties)
+ */
+ @Override
+ public IMqttToken subscribe(MqttSubscription[] subscriptions, Object userContext, MqttActionListener callback,
+ IMqttMessageListener messageListener, MqttProperties subscriptionProperties) throws MqttException {
+
+ int subId = 0;
+ try {
+ subId = subscriptionProperties.getSubscriptionIdentifiers().get(0);
+ } catch (IndexOutOfBoundsException e) {
+ log.fine(CLASS_NAME, "subscribe", "No sub subscription property(s)");
+ }
+ // Automatic Subscription Identifier Assignment is enabled
+ if (connOpts.useSubscriptionIdentifiers() && this.mqttConnection.isSubscriptionIdentifiersAvailable()) {
+
+ // Application is overriding the subscription Identifier
+ if (subId != 0) {
+ // Check that we are not already using this ID, else throw Illegal Argument
+ // Exception
+ if (this.comms.doesSubscriptionIdentifierExist(subId)) {
+ throw new IllegalArgumentException(
+ String.format("The Subscription Identifier %s already exists.", subId));
+ }
+
+ } else {
+ // Automatically assign new ID and link to callback.
+ subId = this.mqttSession.getNextSubscriptionIdentifier();
+ }
+ }
+
+ // add message handlers to the list for this client
+ for (MqttSubscription subscription : subscriptions) {
+ MqttTopicValidator.validate(subscription.getTopic(),
+ this.mqttConnection.isWildcardSubscriptionsAvailable(),
+ this.mqttConnection.isSharedSubscriptionsAvailable());
+ if (messageListener == null) {
+ this.comms.removeMessageListener(subscription.getTopic());
+ } else {
+ this.comms.setMessageListener(subId, subscription.getTopic(), messageListener);
+ }
+ }
+
+ IMqttToken token = null;
+ try {
+ token = this.subscribeBase(subscriptions, userContext, callback, subscriptionProperties);
+ } catch(Exception e) {
+ // if the subscribe fails, then we have to remove the message handlers
+ for (MqttSubscription subscription : subscriptions) {
+ this.comms.removeMessageListener(subscription.getTopic());
+ }
+ throw e;
+ }
+ return token;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#unsubscribe(java.lang.String,
+ * java.lang.Object, org.eclipse.paho.mqttv5.client.MqttActionListener)
+ */
+ @Override
+ public IMqttToken unsubscribe(String topicFilter, Object userContext, MqttActionListener callback)
+ throws MqttException {
+ return unsubscribe(new String[] { topicFilter }, userContext, callback, new MqttProperties());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#unsubscribe(java.lang.String)
+ */
+ @Override
+ public IMqttToken unsubscribe(String topicFilter) throws MqttException {
+ return unsubscribe(new String[] { topicFilter }, null, null, new MqttProperties());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#unsubscribe(java.lang.String[
+ * ])
+ */
+ @Override
+ public IMqttToken unsubscribe(String[] topicFilters) throws MqttException {
+ return unsubscribe(topicFilters, null, null, new MqttProperties());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#unsubscribe(java.lang.String[
+ * ], java.lang.Object, org.eclipse.paho.mqttv5.client.MqttActionListener,
+ * org.eclipse.paho.mqttv5.common.packet.MqttProperties)
+ */
+ @Override
+ public IMqttToken unsubscribe(String[] topicFilters, Object userContext, MqttActionListener callback,
+ MqttProperties unsubscribeProperties) throws MqttException {
+ final String methodName = "unsubscribe";
+
+ // Only Generate Log string if we are logging at FINE level
+ if (log.isLoggable(Logger.FINE)) {
+ String subs = "";
+ for (int i = 0; i < topicFilters.length; i++) {
+ if (i > 0) {
+ subs += ", ";
+ }
+ subs += topicFilters[i];
+ }
+
+ // @TRACE 107=Unsubscribe topic={0} userContext={1} callback={2}
+ log.fine(CLASS_NAME, methodName, "107", new Object[] { subs, userContext, callback });
+ }
+
+ for (String topicFilter : topicFilters) {
+ // Check if the topic filter is valid before unsubscribing
+ // Although we already checked when subscribing, but invalid
+ // topic filter is meanless for unsubscribing, just prohibit it
+ // to reduce unnecessary control packet send to broker.
+ MqttTopicValidator.validate(topicFilter, true/* allow wildcards */, this.mqttConnection.isSharedSubscriptionsAvailable());
+ }
+
+ // remove message handlers from the list for this client
+ for (String topicFilter : topicFilters) {
+ this.comms.removeMessageListener(topicFilter);
+ }
+
+ MqttToken token = new MqttToken(getClientId());
+ token.setActionCallback(callback);
+ token.setUserContext(userContext);
+ token.internalTok.setTopics(topicFilters);
+
+ MqttUnsubscribe unregister = new MqttUnsubscribe(topicFilters, unsubscribeProperties);
+ token.setRequestMessage(unregister);
+
+ comms.sendNoWait(unregister, token);
+ // @TRACE 110=<
+ log.fine(CLASS_NAME, methodName, "110");
+
+ return token;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#setCallback(org.eclipse.paho.
+ * mqttv5.client.MqttCallback)
+ */
+ @Override
+ public void setCallback(MqttCallback callback) {
+ this.mqttCallback = callback;
+ comms.setCallback(callback);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttAsyncClient#setManualAcks(boolean)
+ */
+ @Override
+ public void setManualAcks(boolean manualAcks) {
+ comms.setManualAcks(manualAcks);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#messageArrivedComplete(int,
+ * int)
+ */
+ @Override
+ public void messageArrivedComplete(int messageId, int qos) throws MqttException {
+ comms.messageArrivedComplete(messageId, qos);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#getPendingTokens()
+ */
+ @Override
+ public IMqttToken[] getPendingTokens() {
+ return comms.getPendingTokens();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#publish(java.lang.String,
+ * byte[], int, boolean, java.lang.Object,
+ * org.eclipse.paho.mqttv5.client.MqttActionListener)
+ */
+ @Override
+ public IMqttToken publish(String topic, byte[] payload, int qos, boolean retained, Object userContext,
+ MqttActionListener callback) throws MqttException, MqttPersistenceException {
+ MqttMessage message = new MqttMessage(payload);
+ message.setProperties(new MqttProperties());
+ message.setQos(qos);
+ message.setRetained(retained);
+ return this.publish(topic, message, userContext, callback);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#publish(java.lang.String,
+ * byte[], int, boolean)
+ */
+ @Override
+ public IMqttToken publish(String topic, byte[] payload, int qos, boolean retained)
+ throws MqttException, MqttPersistenceException {
+ return this.publish(topic, payload, qos, retained, null, null);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#publish(java.lang.String,
+ * org.eclipse.paho.mqttv5.common.MqttMessage)
+ */
+ @Override
+ public IMqttToken publish(String topic, MqttMessage message)
+ throws MqttException, MqttPersistenceException {
+ return this.publish(topic, message, null, null);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#publish(java.lang.String,
+ * org.eclipse.paho.mqttv5.common.MqttMessage, java.lang.Object,
+ * org.eclipse.paho.mqttv5.client.MqttActionListener,
+ * org.eclipse.paho.mqttv5.common.packet.MqttProperties)
+ */
+ @Override
+ public IMqttToken publish(String topic, MqttMessage message, Object userContext,
+ MqttActionListener callback) throws MqttException, MqttPersistenceException {
+ final String methodName = "publish";
+ // @TRACE 111=< topic={0} message={1}userContext={1} callback={2}
+ log.fine(CLASS_NAME, methodName, "111", new Object[] { topic, userContext, callback });
+
+ // Checks if a topic is valid when publishing a message.
+ MqttTopicValidator.validate(topic, false/* wildcards NOT allowed */, true);
+
+ MqttToken token = new MqttToken(getClientId());
+ token.internalTok.setDeliveryToken(true);
+ token.setActionCallback(callback);
+ token.setUserContext(userContext);
+ token.setMessage(message);
+ token.internalTok.setTopics(new String[] { topic });
+
+ MqttPublish pubMsg = new MqttPublish(topic, message, message.getProperties());
+ token.setRequestMessage(pubMsg);
+ comms.sendNoWait(pubMsg, token);
+
+ // @TRACE 112=<
+ log.fine(CLASS_NAME, methodName, "112");
+
+ return token;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttAsyncClient#reconnect()
+ */
+ @Override
+ public void reconnect() throws MqttException {
+ final String methodName = "reconnect";
+ // @Trace 500=Attempting to reconnect client: {0}
+ log.fine(CLASS_NAME, methodName, "500", new Object[] { this.mqttSession.getClientId() });
+ // Some checks to make sure that we're not attempting to reconnect an
+ // already connected client
+ if (comms.isConnected()) {
+ throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_CLIENT_CONNECTED);
+ }
+ if (comms.isConnecting()) {
+ throw new MqttException(MqttClientException.REASON_CODE_CONNECT_IN_PROGRESS);
+ }
+ if (comms.isDisconnecting()) {
+ throw new MqttException(MqttClientException.REASON_CODE_CLIENT_DISCONNECTING);
+ }
+ if (comms.isClosed()) {
+ throw new MqttException(MqttClientException.REASON_CODE_CLIENT_CLOSED);
+ }
+ // We don't want to spam the server
+ stopReconnectCycle();
+
+ attemptReconnect();
+ }
+
+ /**
+ * Attempts to reconnect the client to the server. If successful it will make
+ * sure that there are no further reconnects scheduled. However if the connect
+ * fails, the delay will double up to 128 seconds and will re-schedule the
+ * reconnect for after the delay.
+ *
+ * Any thrown exceptions are logged but not acted upon as it is assumed that
+ * they are being thrown due to the server being offline and so reconnect
+ * attempts will continue.
+ */
+ private void attemptReconnect() {
+ final String methodName = "attemptReconnect";
+ // @Trace 500=Attempting to reconnect client: {0}
+ log.fine(CLASS_NAME, methodName, "500", new Object[] { this.mqttSession.getClientId() });
+ try {
+ connect(this.connOpts, this.userContext, new MqttReconnectActionListener(methodName));
+ } catch (MqttSecurityException ex) {
+ // @TRACE 804=exception
+ log.fine(CLASS_NAME, methodName, "804", null, ex);
+ } catch (MqttException ex) {
+ // @TRACE 804=exception
+ log.fine(CLASS_NAME, methodName, "804", null, ex);
+ }
+ }
+
+ private void startReconnectCycle() {
+ String methodName = "startReconnectCycle";
+ // @Trace 503=Start reconnect timer for client: {0}, delay: {1}
+ log.fine(CLASS_NAME, methodName, "503",
+ new Object[] { this.mqttSession.getClientId(), Long.valueOf(reconnectDelay) });
+ reconnectTimer = new Timer("MQTT Reconnect: " + this.mqttSession.getClientId());
+ reconnectTimer.schedule(new ReconnectTask(), reconnectDelay);
+ }
+
+ private void stopReconnectCycle() {
+ String methodName = "stopReconnectCycle";
+ // @Trace 504=Stop reconnect timer for client: {0}
+ log.fine(CLASS_NAME, methodName, "504", new Object[] { this.mqttSession.getClientId() });
+ synchronized (clientLock) {
+ if (this.connOpts.isAutomaticReconnect()) {
+ if (reconnectTimer != null) {
+ reconnectTimer.cancel();
+ reconnectTimer = null;
+ }
+ reconnectDelay = 1000; // Reset Delay Timer
+ }
+ }
+ }
+
+ private class ReconnectTask extends TimerTask {
+ private static final String methodName = "ReconnectTask.run";
+
+ public void run() {
+ // @Trace 506=Triggering Automatic Reconnect attempt.
+ log.fine(CLASS_NAME, methodName, "506");
+ attemptReconnect();
+ }
+ }
+
+ class MqttReconnectCallback implements MqttCallback {
+
+ final boolean automaticReconnect;
+
+ MqttReconnectCallback(boolean isAutomaticReconnect) {
+ automaticReconnect = isAutomaticReconnect;
+ }
+
+ public void messageArrived(String topic, MqttMessage message) throws Exception {
+ }
+
+ public void deliveryComplete(IMqttToken token) {
+ }
+
+ public void connectComplete(boolean reconnect, String serverURI) {
+ }
+
+ public void disconnected(MqttDisconnectResponse disconnectResponse) {
+ if (automaticReconnect) {
+ // Automatic reconnect is set so make sure comms is in resting
+ // state
+ comms.setRestingState(true);
+ reconnecting = true;
+ startReconnectCycle();
+ }
+ }
+
+ public void mqttErrorOccurred(MqttException exception) {
+ }
+
+ public void authPacketArrived(int reasonCode, MqttProperties properties) {
+
+ }
+
+ }
+
+ class MqttReconnectActionListener implements MqttActionListener {
+
+ final String methodName;
+
+ MqttReconnectActionListener(String methodName) {
+ this.methodName = methodName;
+ }
+
+ public void onSuccess(IMqttToken asyncActionToken) {
+ // @Trace 501=Automatic Reconnect Successful: {0}
+ log.fine(CLASS_NAME, methodName, "501", new Object[] { asyncActionToken.getClient().getClientId() });
+ comms.setRestingState(false);
+ stopReconnectCycle();
+ }
+
+ public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
+ // @Trace 502=Automatic Reconnect failed, rescheduling: {0}
+ log.fine(CLASS_NAME, methodName, "502", new Object[] { asyncActionToken.getClient().getClientId() });
+ if (reconnectDelay < connOpts.getMaxReconnectDelay()) {
+ reconnectDelay = reconnectDelay * 2;
+ }
+ rescheduleReconnectCycle(reconnectDelay);
+ }
+
+ private void rescheduleReconnectCycle(int delay) {
+ String reschedulemethodName = methodName + ":rescheduleReconnectCycle";
+ // @Trace 505=Rescheduling reconnect timer for client: {0}, delay:
+ // {1}
+ log.fine(CLASS_NAME, reschedulemethodName, "505",
+ new Object[] { MqttAsyncClient.this.mqttSession.getClientId(), String.valueOf(reconnectDelay) });
+ synchronized (clientLock) {
+ if (MqttAsyncClient.this.connOpts.isAutomaticReconnect()) {
+ if (reconnectTimer != null) {
+ reconnectTimer.schedule(new ReconnectTask(), delay);
+ } else {
+ // The previous reconnect timer was cancelled
+ reconnectDelay = delay;
+ startReconnectCycle();
+ }
+ }
+ }
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#setBufferOpts(org.eclipse.
+ * paho.mqttv5.client.DisconnectedBufferOptions)
+ */
+ @Override
+ public void setBufferOpts(DisconnectedBufferOptions bufferOpts) {
+ this.comms.setDisconnectedMessageBuffer(new DisconnectedMessageBuffer(bufferOpts));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#getBufferedMessageCount()
+ */
+ @Override
+ public int getBufferedMessageCount() {
+ return this.comms.getBufferedMessageCount();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttAsyncClient#getBufferedMessage(int)
+ */
+ @Override
+ public MqttMessage getBufferedMessage(int bufferIndex) {
+ return this.comms.getBufferedMessage(bufferIndex);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#deleteBufferedMessage(int)
+ */
+ @Override
+ public void deleteBufferedMessage(int bufferIndex) {
+ this.comms.deleteBufferedMessage(bufferIndex);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.IMqttAsyncClient#getInFlightMessageCount()
+ */
+ @Override
+ public int getInFlightMessageCount() {
+ return this.comms.getActualInFlight();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttAsyncClient#close()
+ */
+ @Override
+ public void close() throws MqttException {
+ close(false);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttAsyncClient#close(boolean)
+ */
+ @Override
+ public void close(boolean force) throws MqttException {
+ final String methodName = "close";
+ // @TRACE 113=<
+ log.fine(CLASS_NAME, methodName, "113");
+ comms.close(force);
+ // @TRACE 114=>
+ log.fine(CLASS_NAME, methodName, "114");
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttAsyncClient#getDebug()
+ */
+ @Override
+ public Debug getDebug() {
+ return new Debug(this.mqttSession.getClientId(), comms);
+ }
+
+ @Override
+ public IMqttToken authenticate(int reasonCode, Object userContext, MqttProperties properties) throws MqttException {
+ MqttToken token = new MqttToken(getClientId());
+ token.setUserContext(userContext);
+
+ MqttAuth auth = new MqttAuth(reasonCode, properties);
+ comms.sendNoWait(auth, token);
+ return null;
+ }
+
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/MqttCallback.java b/src/main/java/org/eclipse/paho/mqttv5/client/MqttCallback.java
new file mode 100644
index 0000000..1c72f28
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/MqttCallback.java
@@ -0,0 +1,132 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2018 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ * James Sutton - MQTT V5 implementation
+ */
+package org.eclipse.paho.mqttv5.client;
+
+import org.eclipse.paho.mqttv5.common.MqttException;
+import org.eclipse.paho.mqttv5.common.MqttMessage;
+import org.eclipse.paho.mqttv5.common.packet.MqttProperties;
+
+/**
+ * Enables an application to be notified when asynchronous events related to the
+ * client occur. Classes implementing this interface can be registered on both
+ * types of client: {@link IMqttClient#setCallback(MqttCallback)} and
+ * {@link IMqttAsyncClient#setCallback(MqttCallback)}
+ */
+public interface MqttCallback {
+
+ /**
+ * This method is called when the server gracefully disconnects from the client
+ * by sending a disconnect packet, or when the TCP connection is lost due to a
+ * network issue or if the client encounters an error.
+ *
+ * @param disconnectResponse
+ * a {@link MqttDisconnectResponse} containing relevant properties
+ * related to the cause of the disconnection.
+ */
+ void disconnected(MqttDisconnectResponse disconnectResponse);
+
+ /**
+ * This method is called when an exception is thrown within the MQTT client. The
+ * reasons for this may vary, from malformed packets, to protocol errors or even
+ * bugs within the MQTT client itself. This callback surfaces those errors to
+ * the application so that it may decide how best to deal with them.
+ *
+ * For example, The MQTT server may have sent a publish message with an invalid
+ * topic alias, the MQTTv5 specification suggests that the client should
+ * disconnect from the broker with the appropriate return code, however this is
+ * completely up to the application itself.
+ *
+ * @param exception
+ * - The exception thrown causing the error.
+ */
+ void mqttErrorOccurred(MqttException exception);
+
+ /**
+ * This method is called when a message arrives from the server.
+ *
+ *
+ * This method is invoked synchronously by the MQTT client. An acknowledgment is
+ * not sent back to the server until this method returns cleanly.
+ *
+ *
+ * If an implementation of this method throws an Exception, then
+ * the client will be shut down. When the client is next re-connected, any QoS 1
+ * or 2 messages will be redelivered by the server.
+ *
+ *
+ * Any additional messages which arrive while an implementation of this method
+ * is running, will build up in memory, and will then back up on the network.
+ *
+ *
+ * If an application needs to persist data, then it should ensure the data is
+ * persisted prior to returning from this method, as after returning from this
+ * method, the message is considered to have been delivered, and will not be
+ * reproducible.
+ *
+ *
+ * It is possible to send a new message within an implementation of this
+ * callback (for example, a response to this message), but the implementation
+ * must not disconnect the client, as it will be impossible to send an
+ * acknowledgment for the message being processed, and a deadlock will occur.
+ *
+ *
+ * @param topic
+ * name of the topic on the message was published to
+ * @param message
+ * the actual message.
+ * @throws Exception
+ * if a terminal error has occurred, and the client should be shut
+ * down.
+ */
+ void messageArrived(String topic, MqttMessage message) throws Exception;
+
+ /**
+ * Called when delivery for a message has been completed, and all
+ * acknowledgments have been received. For QoS 0 messages it is called once the
+ * message has been handed to the network for delivery. For QoS 1 it is called
+ * when PUBACK is received and for QoS 2 when PUBCOMP is received. The token
+ * will be the same token as that returned when the message was published.
+ *
+ * @param token
+ * the delivery token associated with the message.
+ */
+ void deliveryComplete(IMqttToken token);
+
+ /**
+ * Called when the connection to the server is completed successfully.
+ *
+ * @param reconnect
+ * If true, the connection was the result of automatic reconnect.
+ * @param serverURI
+ * The server URI that the connection was made to.
+ */
+ void connectComplete(boolean reconnect, String serverURI);
+
+ /**
+ * Called when an AUTH packet is received by the client.
+ *
+ * @param reasonCode
+ * The Reason code, can be Success (0), Continue authentication (24)
+ * or Re-authenticate (25).
+ * @param properties
+ * The {@link MqttProperties} to be sent, containing the
+ * Authentication Method, Authentication Data and any required User
+ * Defined Properties.
+ */
+ void authPacketArrived(int reasonCode, MqttProperties properties);
+
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/MqttClient.java b/src/main/java/org/eclipse/paho/mqttv5/client/MqttClient.java
new file mode 100644
index 0000000..a4708f8
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/MqttClient.java
@@ -0,0 +1,744 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2019 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ * Ian Craggs - MQTT 3.1.1 support
+ * Ian Craggs - per subscription message handlers (bug 466579)
+ * Ian Craggs - ack control (bug 472172)
+ */
+package org.eclipse.paho.mqttv5.client;
+
+import java.util.Properties;
+import java.util.concurrent.ScheduledExecutorService;
+
+import javax.net.SocketFactory;
+
+import org.eclipse.paho.mqttv5.client.persist.MqttDefaultFilePersistence;
+import org.eclipse.paho.mqttv5.client.util.Debug;
+import org.eclipse.paho.mqttv5.common.MqttException;
+import org.eclipse.paho.mqttv5.common.MqttMessage;
+import org.eclipse.paho.mqttv5.common.MqttPersistenceException;
+import org.eclipse.paho.mqttv5.common.MqttSecurityException;
+import org.eclipse.paho.mqttv5.common.MqttSubscription;
+import org.eclipse.paho.mqttv5.common.packet.MqttProperties;
+import org.eclipse.paho.mqttv5.common.packet.MqttReturnCode;
+
+/**
+ * Lightweight client for talking to an MQTT server using methods that block
+ * until an operation completes.
+ *
+ *
+ * This class implements the blocking {@link IMqttClient} client interface where
+ * all actions block until they have completed (or timed out). This
+ * implementation is compatible with all Java SE runtimes from 1.7 and up.
+ *
+ *
+ * An application can connect to an MQTT server using:
+ *
+ *
+ *
A plain TCP socket
+ *
An secure SSL/TLS socket
+ *
+ *
+ *
+ * To enable messages to be delivered even across network and client restarts
+ * messages need to be safely stored until the message has been delivered at the
+ * requested quality of service. A pluggable persistence mechanism is provided
+ * to store the messages.
+ *
+ *
+ * By default {@link MqttDefaultFilePersistence} is used to store messages to a
+ * file. If persistence is set to null then messages are stored in memory and
+ * hence can be lost if the client, Java runtime or device shuts down.
+ *
+ *
+ * If connecting with {@link MqttConnectionOptions#setCleanStart(boolean)} set
+ * to true it is safe to use memory persistence as all state it cleared when a
+ * client disconnects. If connecting with cleanStart set to false, to provide
+ * reliable message delivery then a persistent message store should be used such
+ * as the default one.
+ *
+ *
+ * The message store interface is pluggable. Different stores can be used by
+ * implementing the {@link MqttClientPersistence} interface and passing it to
+ * the clients constructor.
+ *
+ *
+ * @see IMqttClient
+ */
+public class MqttClient implements IMqttClient {
+
+ protected MqttAsyncClient aClient = null; // Delegate implementation to MqttAsyncClient
+ protected long timeToWait = -1; // How long each method should wait for action to complete
+
+ /**
+ * Create an MqttClient that can be used to communicate with an MQTT server.
+ *
+ * The address of a server can be specified on the constructor. Alternatively a
+ * list containing one or more servers can be specified using the
+ * {@link MqttConnectionOptions#setServerURIs(String[]) setServerURIs} method on
+ * MqttConnectOptions.
+ *
+ *
+ * The serverURI parameter is typically used with the the
+ * clientId parameter to form a key. The key is used to store and
+ * reference messages while they are being delivered. Hence the serverURI
+ * specified on the constructor must still be specified even if a list of
+ * servers is specified on an MqttConnectOptions object. The serverURI on the
+ * constructor must remain the same across restarts of the client for delivery
+ * of messages to be maintained from a given client to a given server or set of
+ * servers.
+ *
+ *
+ * The address of the server to connect to is specified as a URI. Two types of
+ * connection are supported tcp:// for a TCP connection and
+ * ssl:// for a TCP connection secured by SSL/TLS. For example:
+ *
+ *
+ *
tcp://localhost:1883
+ *
ssl://localhost:8883
+ *
+ *
+ * If the port is not specified, it will default to 1883 for
+ * tcp://" URIs, and 8883 for ssl:// URIs.
+ *
+ *
+ *
+ * A client identifier clientId must be specified and be less that
+ * 65535 characters. It must be unique across all clients connecting to the same
+ * server. The clientId is used by the server to store data related to the
+ * client, hence it is important that the clientId remain the same when
+ * connecting to a server if durable subscriptions or reliable messaging are
+ * required.
+ *
+ * As the client identifier
+ * is used by the server to identify a client when it reconnects, the client
+ * must use the same identifier between connections if durable subscriptions or
+ * reliable delivery of messages is required.
+ *
+ *
+ * In Java SE, SSL can be configured in one of several ways, which the client
+ * will use in the following order:
+ *
+ *
+ *
Supplying an SSLSocketFactory -
+ * applications can use
+ * {@link MqttConnectionOptions#setSocketFactory(SocketFactory)} to supply a
+ * factory with the appropriate SSL settings.
+ *
SSL Properties - applications can supply SSL settings as
+ * a simple Java Properties using
+ * {@link MqttConnectionOptions#setSSLProperties(Properties)}.
+ *
Use JVM settings - There are a number of standard Java
+ * system properties that can be used to configure key and trust stores.
+ *
+ *
+ *
+ * In Java ME, the platform settings are used for SSL connections.
+ *
+ *
+ *
+ * An instance of the default persistence mechanism
+ * {@link MqttDefaultFilePersistence} is used by the client. To specify a
+ * different persistence mechanism or to turn off persistence, use the
+ * {@link #MqttClient(String, String, MqttClientPersistence)}
+ * constructor.
+ *
+ * @param serverURI
+ * the address of the server to connect to, specified as a URI. Can
+ * be overridden using
+ * {@link MqttConnectionOptions#setServerURIs(String[])}
+ * @param clientId
+ * a client identifier that is unique on the server being connected
+ * to
+ * @throws IllegalArgumentException
+ * if the URI does not start with "tcp://", "ssl://" or "local://".
+ * @throws IllegalArgumentException
+ * if the clientId is null or is greater than 65535 characters in
+ * length
+ * @throws MqttException
+ * if any other problem was encountered
+ */
+ public MqttClient(String serverURI, String clientId) throws MqttException {
+ this(serverURI, clientId, new MqttDefaultFilePersistence());
+ }
+
+ /**
+ * Create an MqttClient that can be used to communicate with an MQTT server.
+ *
+ * The address of a server can be specified on the constructor. Alternatively a
+ * list containing one or more servers can be specified using the
+ * {@link MqttConnectionOptions#setServerURIs(String[]) setServerURIs} method on
+ * MqttConnectOptions.
+ *
+ *
+ * The serverURI parameter is typically used with the the
+ * clientId parameter to form a key. The key is used to store and
+ * reference messages while they are being delivered. Hence the serverURI
+ * specified on the constructor must still be specified even if a list of
+ * servers is specified on an MqttConnectOptions object. The serverURI on the
+ * constructor must remain the same across restarts of the client for delivery
+ * of messages to be maintained from a given client to a given server or set of
+ * servers.
+ *
+ *
+ * The address of the server to connect to is specified as a URI. Two types of
+ * connection are supported tcp:// for a TCP connection and
+ * ssl:// for a TCP connection secured by SSL/TLS. For example:
+ *
+ *
+ *
tcp://localhost:1883
+ *
ssl://localhost:8883
+ *
+ *
+ * If the port is not specified, it will default to 1883 for
+ * tcp://" URIs, and 8883 for ssl:// URIs.
+ *
+ *
+ *
+ * A client identifier clientId must be specified and be less that
+ * 65535 characters. It must be unique across all clients connecting to the same
+ * server. The clientId is used by the server to store data related to the
+ * client, hence it is important that the clientId remain the same when
+ * connecting to a server if durable subscriptions or reliable messaging are
+ * required.
+ *
+ * As the client identifier
+ * is used by the server to identify a client when it reconnects, the client
+ * must use the same identifier between connections if durable subscriptions or
+ * reliable delivery of messages is required.
+ *
+ *
+ * In Java SE, SSL can be configured in one of several ways, which the client
+ * will use in the following order:
+ *
+ *
+ *
Supplying an SSLSocketFactory -
+ * applications can use
+ * {@link MqttConnectionOptions#setSocketFactory(SocketFactory)} to supply a
+ * factory with the appropriate SSL settings.
+ *
SSL Properties - applications can supply SSL settings as
+ * a simple Java Properties using
+ * {@link MqttConnectionOptions#setSSLProperties(Properties)}.
+ *
Use JVM settings - There are a number of standard Java
+ * system properties that can be used to configure key and trust stores.
+ *
+ *
+ *
+ * In Java ME, the platform settings are used for SSL connections.
+ *
+ *
+ * A persistence mechanism is used to enable reliable messaging. For messages
+ * sent at qualities of service (QoS) 1 or 2 to be reliably delivered, messages
+ * must be stored (on both the client and server) until the delivery of the
+ * message is complete. If messages are not safely stored when being delivered
+ * then a failure in the client or server can result in lost messages. A
+ * pluggable persistence mechanism is supported via the
+ * {@link MqttClientPersistence} interface. An implementer of this interface
+ * that safely stores messages must be specified in order for delivery of
+ * messages to be reliable. In addition
+ * {@link MqttConnectionOptions#setCleanStart(boolean)} must be set to false.
+ * In the event that only QoS 0 messages are sent or received or cleanStart is
+ * set to true then a safe store is not needed.
+ *
+ *
+ * An implementation of file-based persistence is provided in class
+ * {@link MqttDefaultFilePersistence} which will work in all Java SE based
+ * systems. If no persistence is needed, the persistence parameter can be
+ * explicitly set to null.
+ *
+ *
+ * @param serverURI
+ * the address of the server to connect to, specified as a URI. Can
+ * be overridden using
+ * {@link MqttConnectionOptions#setServerURIs(String[])}
+ * @param clientId
+ * a client identifier that is unique on the server being connected
+ * to
+ * @param persistence
+ * the persistence class to use to store in-flight message. If null
+ * then the default persistence mechanism is used
+ * @throws IllegalArgumentException
+ * if the URI does not start with "tcp://", "ssl://" or "local://"
+ * @throws IllegalArgumentException
+ * if the clientId is null or is greater than 65535 characters in
+ * length
+ * @throws MqttException
+ * if any other problem was encountered
+ */
+ public MqttClient(String serverURI, String clientId, MqttClientPersistence persistence)
+ throws MqttException {
+ aClient = new MqttAsyncClient(serverURI, clientId, persistence);
+ }
+
+ /**
+ * Create an MqttClient that can be used to communicate with an MQTT server.
+ *
+ * The address of a server can be specified on the constructor. Alternatively a
+ * list containing one or more servers can be specified using the
+ * {@link MqttConnectionOptions#setServerURIs(String[]) setServerURIs} method on
+ * MqttConnectOptions.
+ *
+ *
+ * The serverURI parameter is typically used with the the
+ * clientId parameter to form a key. The key is used to store and
+ * reference messages while they are being delivered. Hence the serverURI
+ * specified on the constructor must still be specified even if a list of
+ * servers is specified on an MqttConnectOptions object. The serverURI on the
+ * constructor must remain the same across restarts of the client for delivery
+ * of messages to be maintained from a given client to a given server or set of
+ * servers.
+ *
+ *
+ * The address of the server to connect to is specified as a URI. Two types of
+ * connection are supported tcp:// for a TCP connection and
+ * ssl:// for a TCP connection secured by SSL/TLS. For example:
+ *
+ *
+ *
tcp://localhost:1883
+ *
ssl://localhost:8883
+ *
+ *
+ * If the port is not specified, it will default to 1883 for
+ * tcp://" URIs, and 8883 for ssl:// URIs.
+ *
+ *
+ *
+ * A client identifier clientId must be specified and be less that
+ * 65535 characters. It must be unique across all clients connecting to the same
+ * server. The clientId is used by the server to store data related to the
+ * client, hence it is important that the clientId remain the same when
+ * connecting to a server if durable subscriptions or reliable messaging are
+ * required.
+ *
+ * As the client identifier
+ * is used by the server to identify a client when it reconnects, the client
+ * must use the same identifier between connections if durable subscriptions or
+ * reliable delivery of messages is required.
+ *
+ *
+ * In Java SE, SSL can be configured in one of several ways, which the client
+ * will use in the following order:
+ *
+ *
+ *
Supplying an SSLSocketFactory -
+ * applications can use
+ * {@link MqttConnectionOptions#setSocketFactory(SocketFactory)} to supply a
+ * factory with the appropriate SSL settings.
+ *
SSL Properties - applications can supply SSL settings as
+ * a simple Java Properties using
+ * {@link MqttConnectionOptions#setSSLProperties(Properties)}.
+ *
Use JVM settings - There are a number of standard Java
+ * system properties that can be used to configure key and trust stores.
+ *
+ *
+ *
+ * In Java ME, the platform settings are used for SSL connections.
+ *
+ *
+ * A persistence mechanism is used to enable reliable messaging. For messages
+ * sent at qualities of service (QoS) 1 or 2 to be reliably delivered, messages
+ * must be stored (on both the client and server) until the delivery of the
+ * message is complete. If messages are not safely stored when being delivered
+ * then a failure in the client or server can result in lost messages. A
+ * pluggable persistence mechanism is supported via the
+ * {@link MqttClientPersistence} interface. An implementer of this interface
+ * that safely stores messages must be specified in order for delivery of
+ * messages to be reliable. In addition
+ * {@link MqttConnectionOptions#setCleanStart(boolean)} must be set to false.
+ * In the event that only QoS 0 messages are sent or received or cleanStart is
+ * set to true then a safe store is not needed.
+ *
+ *
+ * An implementation of file-based persistence is provided in class
+ * {@link MqttDefaultFilePersistence} which will work in all Java SE based
+ * systems. If no persistence is needed, the persistence parameter can be
+ * explicitly set to null.
+ *
+ *
+ * @param serverURI
+ * the address of the server to connect to, specified as a URI. Can
+ * be overridden using
+ * {@link MqttConnectionOptions#setServerURIs(String[])}
+ * @param clientId
+ * a client identifier that is unique on the server being connected
+ * to
+ * @param persistence
+ * the persistence class to use to store in-flight message. If null
+ * then the default persistence mechanism is used
+ * @param executorService
+ * used for managing threads. If null then a newScheduledThreadPool
+ * is used.
+ * @throws IllegalArgumentException
+ * if the URI does not start with "tcp://", "ssl://" or "local://"
+ * @throws IllegalArgumentException
+ * if the clientId is null or is greater than 65535 characters in
+ * length
+ * @throws MqttException
+ * if any other problem was encountered
+ */
+ public MqttClient(String serverURI, String clientId, MqttClientPersistence persistence,
+ ScheduledExecutorService executorService) throws MqttException {
+ aClient = new MqttAsyncClient(serverURI, clientId, persistence, null, executorService);
+ }
+
+ /*
+ * @see IMqttClient#connect()
+ */
+ public void connect() throws MqttSecurityException, MqttException {
+ this.connect(new MqttConnectionOptions());
+ }
+
+ /*
+ * @see IMqttClient#connect(MqttConnectOptions)
+ */
+ public void connect(MqttConnectionOptions options) throws MqttSecurityException, MqttException {
+ aClient.connect(options, null, null).waitForCompletion(getTimeToWait());
+ }
+
+ /*
+ * @see IMqttClient#connect(MqttConnectOptions)
+ */
+ public IMqttToken connectWithResult(MqttConnectionOptions options) throws MqttSecurityException, MqttException {
+ IMqttToken tok = aClient.connect(options, null, null);
+ tok.waitForCompletion(getTimeToWait());
+ return tok;
+ }
+
+ /*
+ * @see IMqttClient#disconnect()
+ */
+ public void disconnect() throws MqttException {
+ aClient.disconnect().waitForCompletion();
+ }
+
+ /*
+ * @see IMqttClient#disconnect(long)
+ */
+ public void disconnect(long quiesceTimeout) throws MqttException {
+ aClient.disconnect(quiesceTimeout, null, null, MqttReturnCode.RETURN_CODE_SUCCESS, new MqttProperties())
+ .waitForCompletion();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttAsyncClient#disconnectForcibly()
+ */
+ public void disconnectForcibly() throws MqttException {
+ aClient.disconnectForcibly();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttAsyncClient#disconnectForcibly(long)
+ */
+ public void disconnectForcibly(long disconnectTimeout) throws MqttException {
+ aClient.disconnectForcibly(disconnectTimeout);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttAsyncClient#disconnectForcibly(long,
+ * long)
+ */
+ public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout) throws MqttException {
+ aClient.disconnectForcibly(quiesceTimeout, disconnectTimeout, MqttReturnCode.RETURN_CODE_SUCCESS,
+ new MqttProperties());
+ }
+
+ /**
+ * Disconnects from the server forcibly to reset all the states. Could be useful
+ * when disconnect attempt failed.
+ *
+ * Because the client is able to establish the TCP/IP connection to a none MQTT
+ * server and it will certainly fail to send the disconnect packet.
+ *
+ * @param quiesceTimeout
+ * the amount of time in milliseconds to allow for existing work to
+ * finish before disconnecting. A value of zero or less means the
+ * client will not quiesce.
+ * @param disconnectTimeout
+ * the amount of time in milliseconds to allow send disconnect packet
+ * to server.
+ * @param sendDisconnectPacket
+ * if true, will send the disconnect packet to the server
+ * @throws MqttException
+ * if any unexpected error
+ */
+ public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout, boolean sendDisconnectPacket)
+ throws MqttException {
+ aClient.disconnectForcibly(quiesceTimeout, disconnectTimeout, sendDisconnectPacket);
+ }
+
+ /*
+ * @see IMqttClient#subscribe(String, int)
+ */
+ public IMqttToken subscribe(String topicFilter, int qos) throws MqttException {
+ return this.subscribe(new String[] { topicFilter }, new int[] { qos });
+ }
+
+ @Override
+ public IMqttToken subscribe(String[] topicFilters, int[] qos) throws MqttException {
+ if (topicFilters.length != qos.length) {
+ throw new MqttException(MqttClientException.REASON_CODE_UNEXPECTED_ERROR);
+ }
+
+ MqttSubscription[] subscriptions = new MqttSubscription[topicFilters.length];
+ for (int i = 0; i < topicFilters.length; ++i) {
+ subscriptions[i] = new MqttSubscription(topicFilters[i], qos[i]);
+ }
+
+ return this.subscribe(subscriptions);
+ }
+
+ /*
+ * @see IMqttClient#subscribe(String[], int[])
+ */
+ public IMqttToken subscribe(MqttSubscription[] subscriptions) throws MqttException {
+ return this.subscribe(subscriptions, null);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttClient#subscribe(java.lang.String,
+ * int)
+ */
+ public IMqttToken subscribe(String topicFilter, int qos, IMqttMessageListener messageListener)
+ throws MqttException {
+ MqttSubscription subscription = new MqttSubscription(topicFilter);
+ subscription.setQos(qos);
+ IMqttToken token = aClient.subscribe(subscription, messageListener);
+ token.waitForCompletion();
+ return token;
+ }
+
+ public IMqttToken subscribe(String[] topicFilters, int[] qos, IMqttMessageListener[] messageListeners)
+ throws MqttException {
+ if (topicFilters.length != qos.length) {
+ throw new MqttException(MqttClientException.REASON_CODE_UNEXPECTED_ERROR);
+ }
+
+ MqttSubscription[] subscriptions = new MqttSubscription[topicFilters.length];
+ for (int i = 0; i < topicFilters.length; ++i) {
+ subscriptions[i] = new MqttSubscription(topicFilters[i], qos[i]);
+ }
+
+ return this.subscribe(subscriptions, messageListeners);
+ }
+
+ public IMqttToken subscribe(MqttSubscription[] subscriptions, IMqttMessageListener[] messageListeners) throws MqttException {
+ IMqttToken tok = aClient.subscribe(subscriptions, null, null, messageListeners, new MqttProperties());
+ tok.waitForCompletion(getTimeToWait());
+ return tok;
+ }
+
+ /*
+ * @see IMqttClient#unsubscribe(String)
+ */
+ public void unsubscribe(String topicFilter) throws MqttException {
+ unsubscribe(new String[] { topicFilter });
+ }
+
+ /*
+ * @see IMqttClient#unsubscribe(String[])
+ */
+ public void unsubscribe(String[] topicFilters) throws MqttException {
+ // message handlers removed in the async client unsubscribe below
+ aClient.unsubscribe(topicFilters, null, null, new MqttProperties()).waitForCompletion(getTimeToWait());
+ }
+
+ /*
+ * @see IMqttClient#publishBlock(String, byte[], int, boolean)
+ */
+ public void publish(String topic, byte[] payload, int qos, boolean retained)
+ throws MqttException, MqttPersistenceException {
+ MqttMessage message = new MqttMessage(payload);
+ message.setQos(qos);
+ message.setRetained(retained);
+ this.publish(topic, message);
+ }
+
+ /*
+ * @see IMqttClient#publishBlock(String, MqttMessage)
+ */
+ public void publish(String topic, MqttMessage message) throws MqttException, MqttPersistenceException {
+ aClient.publish(topic, message, null, null).waitForCompletion(getTimeToWait());
+ }
+
+ /**
+ * Set the maximum time to wait for an action to complete.
+ *
+ * Set the maximum time to wait for an action to complete before returning
+ * control to the invoking application. Control is returned when:
+ *
+ *
+ *
the action completes
+ *
or when the timeout if exceeded
+ *
or when the client is disconnect/shutdown
+ *
+ *
+ * The default value is -1 which means the action will not timeout. In the event
+ * of a timeout the action carries on running in the background until it
+ * completes. The timeout is used on methods that block while the action is in
+ * progress.
+ *
+ *
+ * @param timeToWaitInMillis
+ * before the action times out. A value or 0 or -1 will wait until
+ * the action finishes and not timeout.
+ * @throws IllegalArgumentException
+ * if timeToWaitInMillis is invalid
+ */
+ public void setTimeToWait(long timeToWaitInMillis) throws IllegalArgumentException {
+ if (timeToWaitInMillis < -1) {
+ throw new IllegalArgumentException();
+ }
+ this.timeToWait = timeToWaitInMillis;
+ }
+
+ /**
+ * Return the maximum time to wait for an action to complete.
+ *
+ * @return the time to wait
+ * @see MqttClient#setTimeToWait(long)
+ */
+ public long getTimeToWait() {
+ return this.timeToWait;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttClient#close()
+ */
+ public void close() throws MqttException {
+ aClient.close(false);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttClient#close()
+ */
+ public void close(boolean force) throws MqttException {
+ aClient.close(force);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttClient#getClientId()
+ */
+ public String getClientId() {
+ return aClient.getClientId();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttClient#getPendingTokens()
+ */
+ public IMqttToken[] getPendingTokens() {
+ return aClient.getPendingTokens();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttClient#getServerURI()
+ */
+ public String getServerURI() {
+ return aClient.getServerURI();
+ }
+
+ /**
+ * Returns the currently connected Server URI Implemented due to:
+ * https://bugs.eclipse.org/bugs/show_bug.cgi?id=481097
+ *
+ * Where getServerURI only returns the URI that was provided in
+ * MqttAsyncClient's constructor, getCurrentServerURI returns the URI of the
+ * Server that the client is currently connected to. This would be different in
+ * scenarios where multiple server URIs have been provided to the
+ * MqttConnectOptions.
+ *
+ * @return the currently connected server URI
+ */
+ public String getCurrentServerURI() {
+ return aClient.getCurrentServerURI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttClient#getTopic(java.lang.String)
+ */
+ public MqttTopic getTopic(String topic) {
+ return aClient.getTopic(topic);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttClient#isConnected()
+ */
+ public boolean isConnected() {
+ return aClient.isConnected();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttClient#setCallback(org.eclipse.paho.
+ * mqttv5.client.MqttCallback)
+ */
+ public void setCallback(MqttCallback callback) {
+ aClient.setCallback(callback);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.IMqttClient#setCallback(org.eclipse.paho.
+ * mqttv5.client.MqttCallback)
+ */
+ public void setManualAcks(boolean manualAcks) {
+ aClient.setManualAcks(manualAcks);
+ }
+
+ public void messageArrivedComplete(int messageId, int qos) throws MqttException {
+ aClient.messageArrivedComplete(messageId, qos);
+ }
+
+ /**
+ * Will attempt to reconnect to the server after the client has lost connection.
+ *
+ * @throws MqttException
+ * if an error occurs attempting to reconnect
+ */
+ public void reconnect() throws MqttException {
+ aClient.reconnect();
+ }
+
+ /**
+ * Return a debug object that can be used to help solve problems.
+ *
+ * @return the {@link Debug} Object.
+ */
+ public Debug getDebug() {
+ return (aClient.getDebug());
+ }
+
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/MqttClientException.java b/src/main/java/org/eclipse/paho/mqttv5/client/MqttClientException.java
new file mode 100644
index 0000000..746d0b7
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/MqttClientException.java
@@ -0,0 +1,164 @@
+package org.eclipse.paho.mqttv5.client;
+
+import org.eclipse.paho.mqttv5.common.MqttException;
+import org.eclipse.paho.mqttv5.common.MqttMessage;
+
+public class MqttClientException {
+
+ /**
+ * The Server sent a publish message with an invalid topic alias.
+ */
+ public static final short REASON_CODE_INVALID_TOPIC_ALAS = 32301;
+
+ /**
+ * The Server sent a publish message with an unknown topic alias and no topic
+ * string.
+ */
+ public static final short REASON_CODE_UNKNOWN_TOPIC_ALIAS = 32302;
+
+ /**
+ * Client timed out while waiting for a response from the server. The server is
+ * no longer responding to keep-alive messages.
+ */
+ public static final short REASON_CODE_CLIENT_TIMEOUT = 32000;
+
+ /**
+ * Internal error, caused by no new message IDs being available.
+ */
+ public static final short REASON_CODE_NO_MESSAGE_IDS_AVAILABLE = 32001;
+
+ /**
+ * Client timed out while waiting to write messages to the server.
+ */
+ public static final short REASON_CODE_WRITE_TIMEOUT = 32002;
+
+ /**
+ * The client is already connected.
+ */
+ public static final short REASON_CODE_CLIENT_CONNECTED = 32100;
+
+ /**
+ * The client is already disconnected.
+ */
+ public static final short REASON_CODE_CLIENT_ALREADY_DISCONNECTED = 32101;
+ /**
+ * The client is currently disconnecting and cannot accept any new work. This
+ * can occur when waiting on a token, and then disconnecting the client. If the
+ * message delivery does not complete within the quiesce timeout period, then
+ * the waiting token will be notified with an exception.
+ */
+ public static final short REASON_CODE_CLIENT_DISCONNECTING = 32102;
+
+ /** Unable to connect to server */
+ public static final short REASON_CODE_SERVER_CONNECT_ERROR = 32103;
+
+ /**
+ * The client is not connected to the server. The {@link IMqttClient#connect()}
+ * or {@link IMqttClient#connect(MqttConnectionOptions)} method must be called
+ * first. It is also possible that the connection was lost - see
+ * {@link IMqttClient#setCallback(MqttCallback)} for a way to track lost
+ * connections.
+ */
+ public static final short REASON_CODE_CLIENT_NOT_CONNECTED = 32104;
+
+ /**
+ * Server URI and supplied SocketFactory do not match. URIs
+ * beginning tcp:// must use a
+ * javax.net.SocketFactory, and URIs beginning ssl://
+ * must use a javax.net.ssl.SSLSocketFactory.
+ */
+ public static final short REASON_CODE_SOCKET_FACTORY_MISMATCH = 32105;
+
+ /**
+ * SSL configuration error.
+ */
+ public static final short REASON_CODE_SSL_CONFIG_ERROR = 32106;
+
+ /**
+ * Thrown when an attempt to call {@link IMqttClient#disconnect()} has been made
+ * from within a method on {@link MqttCallback}. These methods are invoked by
+ * the client's thread, and must not be used to control disconnection.
+ *
+ * @see MqttCallback#messageArrived(String, MqttMessage)
+ */
+ public static final short REASON_CODE_CLIENT_DISCONNECT_PROHIBITED = 32107;
+
+ /**
+ * Protocol error: the message was not recognized as a valid MQTT packet.
+ * Possible reasons for this include connecting to a non-MQTT server, or
+ * connecting to an SSL server port when the client isn't using SSL.
+ */
+ public static final short REASON_CODE_INVALID_MESSAGE = 32108;
+
+ /**
+ * The client has been unexpectedly disconnected from the server. The
+ * {@link MqttException#getCause() cause} will provide more details.
+ */
+ public static final short REASON_CODE_CONNECTION_LOST = 32109;
+
+ /**
+ * A connect operation in already in progress, only one connect can happen at a
+ * time.
+ */
+ public static final short REASON_CODE_CONNECT_IN_PROGRESS = 32110;
+
+ /**
+ * The client is closed - no operations are permitted on the client in this
+ * state. New up a new client to continue.
+ */
+ public static final short REASON_CODE_CLIENT_CLOSED = 32111;
+
+ /**
+ * A request has been made to use a token that is already associated with
+ * another action. If the action is complete the reset() can ve called on the
+ * token to allow it to be reused.
+ */
+ public static final short REASON_CODE_TOKEN_INUSE = 32201;
+
+ /**
+ * A request has been made to send a message but the maximum number of inflight
+ * messages has already been reached. Once one or more messages have been moved
+ * then new messages can be sent.
+ */
+ public static final short REASON_CODE_MAX_INFLIGHT = 32202;
+
+ /**
+ * The Client has attempted to publish a message whilst in the 'resting' /
+ * offline state with Disconnected Publishing enabled, however the buffer is
+ * full and deleteOldestMessages is disabled, therefore no more messages can be
+ * published until the client reconnects, or the application deletes buffered
+ * message manually.
+ */
+ public static final short REASON_CODE_DISCONNECTED_BUFFER_FULL = 32203;
+
+ public static final short REASON_CODE_SERVER_DISCONNECTED = 32204;
+
+ /**
+ * The server has been sent an MQTT packet that was larger than the client
+ * defined value.
+ */
+ public static final int REASON_CODE_INCOMING_PACKET_TOO_LARGE = 51001;
+ public static final int REASON_CODE_OUTGOING_PACKET_TOO_LARGE = 51002;
+
+ // CONNACK return codes
+ /** The protocol version requested is not supported by the server. */
+ public static final short REASON_CODE_INVALID_PROTOCOL_VERSION = 0x01;
+ /** The server has rejected the supplied client ID */
+ public static final short REASON_CODE_INVALID_CLIENT_ID = 0x02;
+ /** The broker was not available to handle the request. */
+ public static final short REASON_CODE_BROKER_UNAVAILABLE = 0x03;
+ /**
+ * Authentication with the server has failed, due to a bad user name or
+ * password.
+ */
+ public static final short REASON_CODE_FAILED_AUTHENTICATION = 0x04;
+ /** Not authorized to perform the requested operation */
+ public static final short REASON_CODE_NOT_AUTHORIZED = 0x05;
+
+ /** An unexpected error has occurred. */
+ public static final short REASON_CODE_UNEXPECTED_ERROR = 0x06;
+
+ /** Error from subscribe - returned from the server. */
+ public static final short REASON_CODE_SUBSCRIBE_FAILED = 0x80;
+
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/MqttClientInterface.java b/src/main/java/org/eclipse/paho/mqttv5/client/MqttClientInterface.java
new file mode 100644
index 0000000..341e676
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/MqttClientInterface.java
@@ -0,0 +1,9 @@
+package org.eclipse.paho.mqttv5.client;
+
+public interface MqttClientInterface {
+
+ String getClientId();
+
+ String getServerURI();
+
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/MqttClientPersistence.java b/src/main/java/org/eclipse/paho/mqttv5/client/MqttClientPersistence.java
new file mode 100644
index 0000000..27c7bf1
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/MqttClientPersistence.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2019 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.mqttv5.client;
+
+import java.util.Enumeration;
+
+import org.eclipse.paho.mqttv5.common.MqttPersistable;
+import org.eclipse.paho.mqttv5.common.MqttPersistenceException;
+
+/**
+ * Represents a persistent data store, used to store outbound and inbound messages while they
+ * are in flight, enabling delivery to the QoS specified. You can specify an implementation
+ * of this interface using {@link MqttClient#MqttClient(String, String, MqttClientPersistence)},
+ * which the {@link MqttClient} will use to persist QoS 1 and 2 messages.
+ *
+ * If the methods defined throw the MqttPersistenceException then the state of the data persisted
+ * should remain as prior to the method being called. For example, if {@link #put(String, MqttPersistable)}
+ * throws an exception at any point then the data will be assumed to not be in the persistent store.
+ * Similarly if {@link #remove(String)} throws an exception then the data will be
+ * assumed to still be held in the persistent store.
+ *
+ * It is up to the persistence interface to log any exceptions or error information
+ * which may be required when diagnosing a persistence failure.
+ */
+public interface MqttClientPersistence {
+ /**
+ * Initialise the persistent store.
+ * If a persistent store exists for this client ID then open it, otherwise
+ * create a new one. If the persistent store is already open then just return.
+ * An application may use the same client ID to connect to many different
+ * servers, so the client ID in conjunction with the
+ * connection will uniquely identify the persistence store required.
+ *
+ * @param clientId The client for which the persistent store should be opened.
+ * @throws MqttPersistenceException if there was a problem opening the persistent store.
+ */
+ void open(String clientId) throws MqttPersistenceException;
+
+ /**
+ * Close the persistent store that was previously opened.
+ * This will be called when a client application disconnects from the broker.
+ * @throws MqttPersistenceException if an error occurs closing the persistence store.
+ */
+ void close() throws MqttPersistenceException;
+
+ /**
+ * Puts the specified data into the persistent store.
+ * @param key the key for the data, which will be used later to retrieve it.
+ * @param persistable the data to persist
+ * @throws MqttPersistenceException if there was a problem putting the data
+ * into the persistent store.
+ */
+ void put(String key, MqttPersistable persistable) throws MqttPersistenceException;
+
+ /**
+ * Gets the specified data out of the persistent store.
+ * @param key the key for the data, which was used when originally saving it.
+ * @return the un-persisted data
+ * @throws MqttPersistenceException if there was a problem getting the data
+ * from the persistent store.
+ */
+ MqttPersistable get(String key) throws MqttPersistenceException;
+
+ /**
+ * Remove the data for the specified key.
+ * @param key The key for the data to remove
+ * @throws MqttPersistenceException if there was a problem removing the data.
+ */
+ void remove(String key) throws MqttPersistenceException;
+
+ /**
+ * Returns an Enumeration over the keys in this persistent data store.
+ * @return an enumeration of {@link String} objects.
+ * @throws MqttPersistenceException if there was a problem getting they keys
+ */
+ Enumeration keys() throws MqttPersistenceException;
+
+ /**
+ * Clears persistence, so that it no longer contains any persisted data.
+ * @throws MqttPersistenceException if there was a problem clearing all data from the persistence store
+ */
+ void clear() throws MqttPersistenceException;
+
+ /**
+ * Returns whether or not data is persisted using the specified key.
+ * @param key the key for data, which was used when originally saving it.
+ * @return True if the persistence store contains the key
+ * @throws MqttPersistenceException if there was a problem checking whether they key existed.
+ */
+ boolean containsKey(String key) throws MqttPersistenceException;
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/MqttConnectionOptions.java b/src/main/java/org/eclipse/paho/mqttv5/client/MqttConnectionOptions.java
new file mode 100644
index 0000000..ff6283d
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/MqttConnectionOptions.java
@@ -0,0 +1,971 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2017 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ * Ian Craggs - MQTT 3.1.1 support
+ * James Sutton - Automatic Reconnect & Offline Buffering
+ * James Sutton - MQTT v5
+ */
+package org.eclipse.paho.mqttv5.client;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.net.SocketFactory;
+import javax.net.ssl.HostnameVerifier;
+
+import org.eclipse.paho.mqttv5.client.internal.NetworkModuleService;
+import org.eclipse.paho.mqttv5.client.util.Debug;
+import org.eclipse.paho.mqttv5.common.MqttMessage;
+import org.eclipse.paho.mqttv5.common.packet.MqttProperties;
+import org.eclipse.paho.mqttv5.common.packet.UserProperty;
+import org.eclipse.paho.mqttv5.common.util.MqttTopicValidator;
+
+/**
+ * Holds the set of options that control how the client connects to a server.
+ *
+ * Constructs a new {@link MqttConnectionOptions} object using the default
+ * values.
+ *
+ * The defaults are:
+ *
+ *
The keep alive interval is 60 seconds
+ *
Clean Session is true
+ *
The message delivery retry interval is 15 seconds
+ *
The connection timeout period is 30 seconds
+ *
No Will message is set
+ *
Automatic Reconnect is enabled, starting at 1 second and capped at two
+ * minutes
+ *
A standard SocketFactory is used
+ *
+ * More information about these values can be found in the setter methods.
+ */
+public class MqttConnectionOptions {
+
+ private static final String CLIENT_ID_PREFIX = "paho";
+
+ // Connection Behaviour Properties
+ private String[] serverURIs = null; // List of Servers to connect to in order
+ private boolean automaticReconnect = false; // Automatic Reconnect
+ private int automaticReconnectMinDelay = 1; // Time to wait before first automatic reconnection attempt in seconds.
+ private int automaticReconnectMaxDelay = 120; // Max time to wait for automatic reconnection attempts in seconds.
+ private boolean useSubscriptionIdentifiers = true; // Whether to automatically assign subscription identifiers.
+ private int keepAliveInterval = 60; // Keep Alive Interval
+ private int connectionTimeout = 30; // Connection timeout in seconds
+ private boolean httpsHostnameVerificationEnabled = true;
+ private int maxReconnectDelay = 128000;
+ private boolean sendReasonMessages = false;
+
+ public MqttProperties getConnectionProperties() {
+ MqttProperties connectionProperties = new MqttProperties();
+ connectionProperties.setSessionExpiryInterval(sessionExpiryInterval);
+ connectionProperties.setReceiveMaximum(receiveMaximum);
+ connectionProperties.setMaximumPacketSize(maximumPacketSize);
+ connectionProperties.setTopicAliasMaximum(topicAliasMaximum);
+ connectionProperties.setRequestResponseInfo(requestResponseInfo);
+ connectionProperties.setRequestProblemInfo(requestProblemInfo);
+ connectionProperties.setUserProperties(userProperties);
+ connectionProperties.setAuthenticationMethod(authMethod);
+ connectionProperties.setAuthenticationData(authData);
+ return connectionProperties;
+ }
+
+ public MqttProperties getWillMessageProperties() {
+ return willMessageProperties;
+ }
+
+ public void setWillMessageProperties(MqttProperties willMessageProperties) {
+ this.willMessageProperties = willMessageProperties;
+ }
+
+ MqttProperties willMessageProperties = new MqttProperties();
+
+ // Connection packet properties
+ private int mqttVersion = 5; // MQTT Version 5
+ private boolean cleanStart = true; // Clean Session
+ private String willDestination = null; // Will Topic
+ private MqttMessage willMessage = null; // Will Message
+ private String userName; // Username
+ private byte[] password; // Password
+ private Long sessionExpiryInterval = null; // The Session expiry Interval in seconds, null is the default of
+ // never.
+ private Integer receiveMaximum = null; // The Receive Maximum, null defaults to 65,535, cannot be 0.
+ private Long maximumPacketSize = null; // The Maximum packet size, null defaults to no limit.
+ private Integer topicAliasMaximum = null; // The Topic Alias Maximum, null defaults to 0.
+ private Boolean requestResponseInfo = null; // Request Response Information, null defaults to false.
+ private Boolean requestProblemInfo = null; // Request Problem Information, null defaults to true.
+ private List userProperties = null; // User Defined Properties.
+ private String authMethod = null; // Authentication Method, If null, Extended Authentication is not performed.
+ private byte[] authData = null; // Authentication Data.
+
+ // TLS Properties
+ private SocketFactory socketFactory; // SocketFactory to be used to connect
+ private Properties sslClientProps = null; // SSL Client Properties
+ private HostnameVerifier sslHostnameVerifier = null; // SSL Hostname Verifier
+ private Map customWebSocketHeaders;
+
+ // Client Operation Parameters
+ private int executorServiceTimeout = 1; // How long to wait in seconds when terminating the executor service.
+
+ /**
+ * Returns the MQTT version.
+ *
+ * @return the MQTT version.
+ */
+ public int getMqttVersion() {
+ return mqttVersion;
+ }
+
+ /**
+ * Returns the user name to use for the connection.
+ *
+ * @return the user name to use for the connection.
+ */
+ public String getUserName() {
+ return userName;
+ }
+
+ /**
+ * Sets the user name to use for the connection.
+ *
+ * @param userName
+ * The Username as a String
+ */
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ /**
+ * Returns the password to use for the connection.
+ *
+ * @return the password to use for the connection.
+ */
+ public byte[] getPassword() {
+ return password;
+ }
+
+ /**
+ * Sets the password to use for the connection.
+ *
+ * @param password
+ * A Char Array of the password
+ */
+ public void setPassword(byte[] password) {
+ this.password = password;
+ }
+
+ /**
+ * Returns the topic to be used for last will and testament (LWT).
+ *
+ * @return the MqttTopic to use, or null if LWT is not set.
+ * @see #setWill(String, MqttMessage)
+ */
+ public String getWillDestination() {
+ return willDestination;
+ }
+
+ /**
+ * Returns the message to be sent as last will and testament (LWT). The returned
+ * object is "read only". Calling any "setter" methods on the returned object
+ * will result in an IllegalStateException being thrown.
+ *
+ * @return the message to use, or null if LWT is not set.
+ */
+ public MqttMessage getWillMessage() {
+ return willMessage;
+ }
+
+ /**
+ * Sets the "Last Will and Testament" (LWT) for the connection. In the event
+ * that this client unexpectedly looses it's connection to the server, the
+ * server will publish a message to itself using the supplied details.
+ *
+ * @param topic
+ * the topic to publish to.
+ * @param message
+ * the {@link MqttMessage} to send
+ */
+ public void setWill(String topic, MqttMessage message) {
+ if (topic == null || message == null || message.getPayload() == null) {
+ throw new IllegalArgumentException();
+ }
+ MqttTopicValidator.validate(topic, false, true); // Wildcards are not allowed
+ this.willDestination = topic;
+ this.willMessage = message;
+ // Prevent any more changes to the will message
+ this.willMessage.setMutable(false);
+ }
+
+ /**
+ * Returns whether the client and server should remember state for the client
+ * across reconnects.
+ *
+ * @return the clean session flag
+ */
+ public boolean isCleanStart() {
+ return this.cleanStart;
+ }
+
+ /**
+ * Sets whether the client and server should remember state across restarts and
+ * reconnects. If set to false, the server will retain the session state until
+ * either:
+ *
+ *
A new connection is made with the client and with the cleanStart flag set
+ * to true.
+ *
The Session expiry interval is exceeded after the network connection is
+ * closed, see {@link MqttConnectionOptions#setSessionExpiryInterval}
+ *
+ *
+ * If set to true, the server will immediately drop any existing session state
+ * for the given client and will initiate a new session.
+ *
+ * In order to implement QoS 1 and QoS 2 protocol flows the Client and Server
+ * need to associate state with the Client Identifier, this is referred to as
+ * the Session State. The Server also stores the subscriptions as part of the
+ * Session State.
+ *
+ * The session can continue across a sequence of Network Connections. It lasts
+ * as long as the latest Network Connection plus the Session Expiry Interval.
+ *
+ * The Session State in the Client consists of:
+ *
+ *
+ *
QoS 1 and QoS 2 messages which have been sent to the Server, but have not
+ * been completely acknowledged.
+ *
QoS 2 messages which have been received from the Server, but have not
+ * been completely acknowledged.
+ *
+ *
+ * The Session State in the Server consists of:
+ *
+ *
The existence of a Session, even if the rest of the Session State is
+ * empty.
+ *
The Clients subscriptions, including any Subscription Identifiers.
+ *
QoS 1 and QoS 2 messages which have been sent to the Client, but have not
+ * been completely acknowledged.
+ *
QoS 1 and QoS 2 messages pending transmission to the Client and
+ * OPTIONALLY QoS 0 messages pending transmission to the Client.
+ *
QoS 2 messages which have been received from the Client, but have not
+ * been completely acknowledged.The Will Message and the Will Delay
+ * Interval
+ *
If the Session is currently not connected, the time at which the Session
+ * will end and Session State will be discarded.
+ *
+ *
+ * Retained messages do not form part of the Session State in the Server, they
+ * are not deleted as a result of a Session ending.
+ *
+ *
+ *
+ * @param cleanStart
+ * Set to True to enable cleanSession
+ */
+ public void setCleanStart(boolean cleanStart) {
+ this.cleanStart = cleanStart;
+ }
+
+ /**
+ * Returns the "keep alive" interval.
+ *
+ * @see #setKeepAliveInterval(int)
+ * @return the keep alive interval.
+ */
+ public int getKeepAliveInterval() {
+ return keepAliveInterval;
+ }
+
+ /**
+ * Sets the "keep alive" interval. This value, measured in seconds, defines the
+ * maximum time interval between messages sent or received. It enables the
+ * client to detect if the server is no longer available, without having to wait
+ * for the TCP/IP timeout. The client will ensure that at least one message
+ * travels across the network within each keep alive period. In the absence of a
+ * data-related message during the time period, the client sends a very small
+ * "ping" message, which the server will acknowledge. A value of 0 disables
+ * keepalive processing in the client.
+ *
+ * The default value is 60 seconds
+ *
+ *
+ * @param keepAliveInterval
+ * the interval, measured in seconds, must be >= 0.
+ * @throws IllegalArgumentException
+ * if the keepAliveInterval was invalid
+ */
+ public void setKeepAliveInterval(int keepAliveInterval) {
+ if (keepAliveInterval < 0) {
+ throw new IllegalArgumentException();
+ }
+ this.keepAliveInterval = keepAliveInterval;
+ }
+
+ /**
+ * Returns the connection timeout value.
+ *
+ * @see #setConnectionTimeout(int)
+ * @return the connection timeout value.
+ */
+ public int getConnectionTimeout() {
+ return connectionTimeout;
+ }
+
+ /**
+ * Sets the connection timeout value. This value, measured in seconds, defines
+ * the maximum time interval the client will wait for the network connection to
+ * the MQTT server to be established. The default timeout is 30 seconds. A value
+ * of 0 disables timeout processing meaning the client will wait until the
+ * network connection is made successfully or fails.
+ *
+ * @param connectionTimeout
+ * the timeout value, measured in seconds. It must be >0;
+ */
+ public void setConnectionTimeout(int connectionTimeout) {
+ if (connectionTimeout < 0) {
+ throw new IllegalArgumentException();
+ }
+ this.connectionTimeout = connectionTimeout;
+ }
+
+ /**
+ * Get the maximum time (in millis) to wait between reconnects
+ *
+ * @return Get the maximum time (in millis) to wait between reconnects
+ */
+ public int getMaxReconnectDelay() {
+ return maxReconnectDelay;
+ }
+
+ /**
+ * Set the maximum time to wait between reconnects
+ *
+ * @param maxReconnectDelay
+ * the duration (in millis)
+ */
+ public void setMaxReconnectDelay(int maxReconnectDelay) {
+ this.maxReconnectDelay = maxReconnectDelay;
+ }
+
+ /**
+ * Return a list of serverURIs the client may connect to
+ *
+ * @return the serverURIs or null if not set
+ */
+ public String[] getServerURIs() {
+ return serverURIs;
+ }
+
+ /**
+ * Set a list of one or more serverURIs the client may connect to.
+ *
+ * Each serverURI specifies the address of a server that the client
+ * may connect to. Two types of connection are supported tcp:// for
+ * a TCP connection and ssl:// for a TCP connection secured by
+ * SSL/TLS. For example:
+ *
+ *
tcp://localhost:1883
+ *
ssl://localhost:8883
+ *
+ * If the port is not specified, it will default to 1883 for
+ * tcp://" URIs, and 8883 for ssl:// URIs.
+ *
+ * If serverURIs is set then it overrides the serverURI parameter passed in on
+ * the constructor of the MQTT client.
+ *
+ * When an attempt to connect is initiated the client will start with the first
+ * serverURI in the list and work through the list until a connection is
+ * established with a server. If a connection cannot be made to any of the
+ * servers then the connect attempt fails.
+ *
+ * Specifying a list of servers that a client may connect to has several uses:
+ *
+ *
High Availability and reliable message delivery
+ *
+ * Some MQTT servers support a high availability feature where two or more
+ * "equal" MQTT servers share state. An MQTT client can connect to any of the
+ * "equal" servers and be assured that messages are reliably delivered and
+ * durable subscriptions are maintained no matter which server the client
+ * connects to.
+ *
+ *
+ * The cleanStart flag must be set to false if durable subscriptions and/or
+ * reliable message delivery is required.
+ *
+ *
+ *
Hunt List
+ *
+ * A set of servers may be specified that are not "equal" (as in the high
+ * availability option). As no state is shared across the servers reliable
+ * message delivery and durable subscriptions are not valid. The cleanStart flag
+ * must be set to true if the hunt list mode is used
+ *
+ *
+ *
+ *
+ * @param serverURIs
+ * to be used by the client
+ */
+ public void setServerURIs(String[] serverURIs) {
+ for (String serverURI : serverURIs) {
+ NetworkModuleService.validateURI(serverURI);
+ }
+ this.serverURIs = serverURIs.clone();
+ }
+
+ /**
+ * Returns whether the client will automatically attempt to reconnect to the
+ * server if the connection is lost
+ *
+ * @return the automatic reconnection flag.
+ */
+ public boolean isAutomaticReconnect() {
+ return automaticReconnect;
+ }
+
+ /**
+ * Sets whether the client will automatically attempt to reconnect to the server
+ * if the connection is lost.
+ *
+ *
If set to false, the client will not attempt to automatically reconnect
+ * to the server in the event that the connection is lost.
+ *
If set to true, in the event that the connection is lost, the client will
+ * attempt to reconnect to the server. It will initially wait 1 second before it
+ * attempts to reconnect, for every failed reconnect attempt, the delay will
+ * double until it is at 2 minutes at which point the delay will stay at 2
+ * minutes.
+ *
+ *
+ * You can change the Minimum and Maximum delays by using
+ * {@link #setAutomaticReconnectDelay(int, int)}
+ *
+ * This Defaults to true
+ *
+ * @param automaticReconnect
+ * If set to True, Automatic Reconnect will be enabled
+ */
+ public void setAutomaticReconnect(boolean automaticReconnect) {
+ this.automaticReconnect = automaticReconnect;
+ }
+
+ /**
+ * Sets the Minimum and Maximum delays used when attempting to automatically
+ * reconnect.
+ *
+ * @param minDelay
+ * the minimum delay to wait before attempting to reconnect in
+ * seconds, defaults to 1 second.
+ * @param maxDelay
+ * the maximum delay to wait before attempting to reconnect in
+ * seconds, defaults to 120 seconds.
+ */
+ public void setAutomaticReconnectDelay(int minDelay, int maxDelay) {
+ this.automaticReconnectMinDelay = minDelay;
+ this.automaticReconnectMaxDelay = maxDelay;
+ }
+
+ /**
+ * Returns the minimum number of seconds to wait before attempting to
+ * automatically reconnect.
+ *
+ * @return the automatic reconnect minimum delay in seconds.
+ */
+ public int getAutomaticReconnectMinDelay() {
+ return automaticReconnectMinDelay;
+ }
+
+ /**
+ * Returns the maximum number of seconds to wait before attempting to
+ * automatically reconnect.
+ *
+ * @return the automatic reconnect maximum delay in seconds.
+ */
+ public int getAutomaticReconnectMaxDelay() {
+ return automaticReconnectMaxDelay;
+ }
+
+ /**
+ * Returns the Session Expiry Interval. If null, this means the
+ * session will not expire.
+ *
+ * @return the Session Expiry Interval in seconds.
+ */
+ public Long getSessionExpiryInterval() {
+ return sessionExpiryInterval;
+ }
+
+ /**
+ * Sets the Session Expiry Interval. This value, measured in seconds, defines
+ * the maximum time that the broker will maintain the session for once the
+ * client disconnects. Clients should only connect with a long Session Expiry
+ * interval if they intend to connect to the server at some later point in time.
+ *
+ *
+ *
By default this value is null and so will not be sent, in this case, the
+ * session will not expire.
+ *
If a 0 is sent, the session will end immediately once the Network
+ * Connection is closed.
+ *
+ *
+ * When the client has determined that it has no longer any use for the session,
+ * it should disconnect with a Session Expiry Interval set to 0.
+ *
+ * @param sessionExpiryInterval
+ * The Session Expiry Interval in seconds.
+ */
+ public void setSessionExpiryInterval(Long sessionExpiryInterval) {
+ this.sessionExpiryInterval = sessionExpiryInterval;
+ }
+
+ /**
+ * Returns the Receive Maximum value. If null, it will default to
+ * 65,535.
+ *
+ * @return the Receive Maximum
+ */
+ public Integer getReceiveMaximum() {
+ return receiveMaximum;
+ }
+
+ /**
+ * Sets the Receive Maximum. This value represents the limit of QoS 1 and QoS 2
+ * publications that the client is willing to process concurrently. There is no
+ * mechanism to limit the number of QoS 0 publications that the Server might try
+ * to send.
+ *
+ *
If set to null then this value defaults to 65,535.
+ *
If set, the minimum value for this property is 1.
+ *
The maximum value for this property is 65,535.
+ *
+ *
+ * @param receiveMaximum
+ * the Receive Maximum.
+ */
+ public void setReceiveMaximum(Integer receiveMaximum) {
+ if (receiveMaximum != null && (receiveMaximum == 0 || receiveMaximum > 65535)) {
+ throw new IllegalArgumentException();
+ }
+ this.receiveMaximum = receiveMaximum;
+ }
+
+ /**
+ * Returns the Maximum Packet Size. If null, no limit is imposed.
+ *
+ * @return the Maximum Packet Size in bytes.
+ */
+ public Long getMaximumPacketSize() {
+ return maximumPacketSize;
+ }
+
+ /**
+ * Sets the Maximum Packet Size. This value represents the Maximum Packet Size
+ * the client is willing to accept.
+ *
+ *
+ *
If set to null then no limit is imposed beyond the
+ * limitations of the protocol.
+ *
If set, the minimum value for this property is 1.
+ *
The maximum value for this property is 2,684,354,656.
+ *
+ *
+ * @param maximumPacketSize
+ * The Maximum packet size.
+ */
+ public void setMaximumPacketSize(Long maximumPacketSize) {
+ this.maximumPacketSize = maximumPacketSize;
+ }
+
+ /**
+ * Returns the Topic Alias Maximum. If null, the default value is
+ * 0.
+ *
+ * @return the Topic Alias Maximum.
+ */
+ public Integer getTopicAliasMaximum() {
+ return topicAliasMaximum;
+ }
+
+ /**
+ * Sets the Topic Alias Maximum. This value if present represents the highest
+ * value that the Client will accept as a Topic Alias sent by the Server.
+ *
+ *
+ *
If set to null, then it will default to to 0.
+ *
If set to 0, the Client will not accept any Topic Aliases
+ *
The Maximum value for this property is 65535.
+ *
+ *
+ * @param topicAliasMaximum
+ * the Topic Alias Maximum
+ */
+ public void setTopicAliasMaximum(Integer topicAliasMaximum) {
+ if (topicAliasMaximum != null && topicAliasMaximum > 65535) {
+ throw new IllegalArgumentException();
+ }
+ this.topicAliasMaximum = topicAliasMaximum;
+ }
+
+ /**
+ * Returns the Request Response Info flag. If null, the default
+ * value is false.
+ *
+ * @return The Request Response Info Flag.
+ */
+ public Boolean getRequestResponseInfo() {
+ return requestResponseInfo;
+ }
+
+ /**
+ * Sets the Request Response Info Flag.
+ *
+ *
If set to null, then it will default to false.
+ *
If set to false, the server will not return any response information in
+ * the CONNACK.
+ *
If set to true, the server MAY return response information in the
+ * CONNACK.
+ *
+ *
+ * @param requestResponseInfo
+ * The Request Response Info Flag.
+ */
+ public void setRequestResponseInfo(boolean requestResponseInfo) {
+ this.requestResponseInfo = requestResponseInfo;
+ }
+
+ /**
+ * Returns the Request Problem Info flag. If null, the default
+ * value is true.
+ *
+ * @return the Request Problem Info flag.
+ */
+ public Boolean getRequestProblemInfo() {
+ return requestProblemInfo;
+ }
+
+ /**
+ * Sets the Request Problem Info flag.
+ *
+ *
If set to null, then it will default to true.
+ *
If set to false, the server MAY return a Reason String or User Properties
+ * on a CONNACK or DISCONNECT, but must not send a Reason String or User
+ * Properties on any packet other than PUBLISH, CONNACK or DISCONNECT.
+ *
If set to true, the server MAY return a Reason String or User Properties
+ * on any packet where it is allowed.
+ *
+ *
+ * @param requestProblemInfo
+ * The Flag to request problem information.
+ */
+ public void setRequestProblemInfo(boolean requestProblemInfo) {
+ this.requestProblemInfo = requestProblemInfo;
+ }
+
+ /**
+ * Returns the User Properties.
+ *
+ * @return the User Properties.
+ */
+ public List getUserProperties() {
+ return userProperties;
+ }
+
+ /**
+ * Sets the User Properties. A User Property is a UTF-8 String Pair, the same
+ * name is allowed to appear more than once.
+ *
+ * @param userProperties
+ * User Properties
+ */
+ public void setUserProperties(List userProperties) {
+ this.userProperties = userProperties;
+ }
+
+ /**
+ * Returns the Authentication Method. If null, extended
+ * authentication is not performed.
+ *
+ * @return the Authentication Method.
+ */
+ public String getAuthMethod() {
+ return authMethod;
+ }
+
+ /**
+ * Sets the Authentication Method. If set, this value contains the name of the
+ * authentication method to be used for extended authentication.
+ *
+ * If null, extended authentication is not performed.
+ *
+ * @param authMethod
+ * The Authentication Method.
+ */
+ public void setAuthMethod(String authMethod) {
+ this.authMethod = authMethod;
+ }
+
+ /**
+ * Returns the Authentication Data.
+ *
+ * @return the Authentication Data.
+ */
+ public byte[] getAuthData() {
+ return authData;
+ }
+
+ /**
+ * Sets the Authentication Data. If set, this byte array contains the extended
+ * authentication data, defined by the Authenticated Method. It is a protocol
+ * error to include Authentication Data if there is no Authentication Method.
+ *
+ * @param authData
+ * The Authentication Data
+ */
+ public void setAuthData(byte[] authData) {
+ this.authData = authData;
+ }
+
+ /**
+ * Returns the socket factory that will be used when connecting, or
+ * null if one has not been set.
+ *
+ * @return The Socket Factory
+ */
+ public SocketFactory getSocketFactory() {
+ return socketFactory;
+ }
+
+ /**
+ * Sets the SocketFactory to use. This allows an application to
+ * apply its own policies around the creation of network sockets. If using an
+ * SSL connection, an SSLSocketFactory can be used to supply
+ * application-specific security settings.
+ *
+ * @param socketFactory
+ * the factory to use.
+ */
+ public void setSocketFactory(SocketFactory socketFactory) {
+ this.socketFactory = socketFactory;
+ }
+
+ /**
+ * Returns the SSL properties for the connection.
+ *
+ * @return the properties for the SSL connection
+ */
+ public Properties getSSLProperties() {
+ return sslClientProps;
+ }
+
+ /**
+ * Sets the SSL properties for the connection.
+ *
+ * Note that these properties are only valid if an implementation of the Java
+ * Secure Socket Extensions (JSSE) is available. These properties are
+ * not used if a SocketFactory has been set using
+ * {@link #setSocketFactory(SocketFactory)}. The following properties can be
+ * used:
+ *
+ *
+ *
com.ibm.ssl.protocol
+ *
One of: SSL, SSLv3, TLS, TLSv1, SSL_TLS.
+ *
com.ibm.ssl.contextProvider
+ *
Underlying JSSE provider. For example "IBMJSSE2" or "SunJSSE"
+ *
+ *
com.ibm.ssl.keyStore
+ *
The name of the file that contains the KeyStore object that you want the
+ * KeyManager to use. For example /mydir/etc/key.p12
+ *
+ *
com.ibm.ssl.keyStorePassword
+ *
The password for the KeyStore object that you want the KeyManager to use.
+ * The password can either be in plain-text, or may be obfuscated using the
+ * static method:
+ * com.ibm.micro.security.Password.obfuscate(char[] password). This
+ * obfuscates the password using a simple and insecure XOR and Base64 encoding
+ * mechanism. Note that this is only a simple scrambler to obfuscate clear-text
+ * passwords.
+ *
+ *
com.ibm.ssl.keyStoreType
+ *
Type of key store, for example "PKCS12", "JKS", or "JCEKS".
+ *
+ *
com.ibm.ssl.keyStoreProvider
+ *
Key store provider, for example "IBMJCE" or "IBMJCEFIPS".
+ *
+ *
com.ibm.ssl.trustStore
+ *
The name of the file that contains the KeyStore object that you want the
+ * TrustManager to use.
+ *
+ *
com.ibm.ssl.trustStorePassword
+ *
The password for the TrustStore object that you want the TrustManager to
+ * use. The password can either be in plain-text, or may be obfuscated using the
+ * static method:
+ * com.ibm.micro.security.Password.obfuscate(char[] password). This
+ * obfuscates the password using a simple and insecure XOR and Base64 encoding
+ * mechanism. Note that this is only a simple scrambler to obfuscate clear-text
+ * passwords.
+ *
+ *
com.ibm.ssl.trustStoreType
+ *
The type of KeyStore object that you want the default TrustManager to
+ * use. Same possible values as "keyStoreType".
+ *
+ *
com.ibm.ssl.trustStoreProvider
+ *
Trust store provider, for example "IBMJCE" or "IBMJCEFIPS".
+ *
+ *
com.ibm.ssl.enabledCipherSuites
+ *
A list of which ciphers are enabled. Values are dependent on the
+ * provider, for example:
+ * SSL_RSA_WITH_AES_128_CBC_SHA;SSL_RSA_WITH_3DES_EDE_CBC_SHA.
+ *
+ *
com.ibm.ssl.keyManager
+ *
Sets the algorithm that will be used to instantiate a KeyManagerFactory
+ * object instead of using the default algorithm available in the platform.
+ * Example values: "IbmX509" or "IBMJ9X509".
+ *
+ *
com.ibm.ssl.trustManager
+ *
Sets the algorithm that will be used to instantiate a TrustManagerFactory
+ * object instead of using the default algorithm available in the platform.
+ * Example values: "PKIX" or "IBMJ9X509".
+ *
+ *
+ * @param props
+ * The SSL {@link Properties}
+ */
+ public void setSSLProperties(Properties props) {
+ this.sslClientProps = props;
+ }
+
+ /**
+ * Returns the HostnameVerifier for the SSL connection.
+ *
+ * @return the HostnameVerifier for the SSL connection
+ */
+ public HostnameVerifier getSSLHostnameVerifier() {
+ return sslHostnameVerifier;
+ }
+
+ /**
+ * Sets the HostnameVerifier for the SSL connection. Note that it will be used
+ * after handshake on a connection and you should do actions by yourserlf when
+ * hostname is verified error.
+ *
+ * There is no default HostnameVerifier
+ *
+ *
+ * @param hostnameVerifier
+ * the {@link HostnameVerifier}
+ */
+ public void setSSLHostnameVerifier(HostnameVerifier hostnameVerifier) {
+ this.sslHostnameVerifier = hostnameVerifier;
+ }
+
+ /**
+ * Returns whether to automatically assign subscription identifiers when
+ * subscribing to a topic.
+ *
+ * @return if automatic assignment of subscription identifiers is enabled.
+ */
+ public boolean useSubscriptionIdentifiers() {
+ return useSubscriptionIdentifiers;
+ }
+
+ /**
+ * Sets whether to automatically assign subscription identifiers when
+ * subscribing to a topic. This will mean that if a subscription has a callback
+ * associated with it then it is guaranteed to be called when an incoming
+ * message has the correct subscription ID embedded. If disabled, then the
+ * client will do best effort topic matching with all callbacks, however this
+ * might result in an incorrect callback being called if there are multiple
+ * subscriptions to topics using a combination of wildcards.
+ *
+ * @param useSubscriptionIdentifiers
+ * Whether to enable automatic assignment of subscription
+ * identifiers.
+ */
+ public void setUseSubscriptionIdentifiers(boolean useSubscriptionIdentifiers) {
+ this.useSubscriptionIdentifiers = useSubscriptionIdentifiers;
+ }
+
+ public boolean isHttpsHostnameVerificationEnabled() {
+ return httpsHostnameVerificationEnabled;
+ }
+
+ public void setHttpsHostnameVerificationEnabled(boolean httpsHostnameVerificationEnabled) {
+ this.httpsHostnameVerificationEnabled = httpsHostnameVerificationEnabled;
+ }
+
+ /**
+ * @return The Debug Properties
+ */
+ public Properties getDebug() {
+ final String strNull = "null";
+ Properties p = new Properties();
+ p.put("MqttVersion", getMqttVersion());
+ p.put("CleanStart", Boolean.valueOf(isCleanStart()));
+ p.put("ConTimeout", getConnectionTimeout());
+ p.put("KeepAliveInterval", getKeepAliveInterval());
+ p.put("UserName", (getUserName() == null) ? strNull : getUserName());
+ p.put("WillDestination", (getWillDestination() == null) ? strNull : getWillDestination());
+ if (getSocketFactory() == null) {
+ p.put("SocketFactory", strNull);
+ } else {
+ p.put("SocketFactory", getSocketFactory());
+ }
+ if (getSSLProperties() == null) {
+ p.put("SSLProperties", strNull);
+ } else {
+ p.put("SSLProperties", getSSLProperties());
+ }
+ return p;
+ }
+
+ /**
+ * Sets the Custom WebSocket Headers for the WebSocket Connection.
+ *
+ * @param headers
+ * The custom websocket headers {@link Properties}
+ */
+ public void setCustomWebSocketHeaders(Map headers) {
+ this.customWebSocketHeaders = Collections.unmodifiableMap(headers);
+ }
+
+ public Map getCustomWebSocketHeaders() {
+ return customWebSocketHeaders;
+ }
+
+ public String toString() {
+ return Debug.dumpProperties(getDebug(), "Connection options");
+ }
+
+ public boolean isSendReasonMessages() {
+ return sendReasonMessages;
+ }
+
+ public void setSendReasonMessages(boolean sendReasonMessages) {
+ this.sendReasonMessages = sendReasonMessages;
+ }
+
+ public int getExecutorServiceTimeout() {
+ return executorServiceTimeout;
+ }
+
+ /**
+ * Set the time in seconds that the executor service should wait when
+ * terminating before forcefully terminating. It is not recommended to change
+ * this value unless you are absolutely sure that you need to.
+ *
+ * @param executorServiceTimeout the time in seconds to wait when shutting down.Ï
+ */
+ public void setExecutorServiceTimeout(int executorServiceTimeout) {
+ this.executorServiceTimeout = executorServiceTimeout;
+ }
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/MqttConnectionOptionsBuilder.java b/src/main/java/org/eclipse/paho/mqttv5/client/MqttConnectionOptionsBuilder.java
new file mode 100644
index 0000000..1de91fb
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/MqttConnectionOptionsBuilder.java
@@ -0,0 +1,109 @@
+package org.eclipse.paho.mqttv5.client;
+
+import java.util.ArrayList;
+
+import org.eclipse.paho.mqttv5.common.MqttMessage;
+import org.eclipse.paho.mqttv5.common.packet.UserProperty;
+
+public class MqttConnectionOptionsBuilder {
+ private MqttConnectionOptions mqttConnectionOptions;
+
+ public MqttConnectionOptionsBuilder() {
+ mqttConnectionOptions = new MqttConnectionOptions();
+ }
+
+ public MqttConnectionOptionsBuilder serverURI(String serverURI) {
+ mqttConnectionOptions.setServerURIs(new String[] { serverURI });
+ return this;
+ }
+
+ public MqttConnectionOptionsBuilder serverURIs(String[] serverURIs) {
+ mqttConnectionOptions.setServerURIs(serverURIs);
+ return this;
+ }
+
+ public MqttConnectionOptionsBuilder automaticReconnect(boolean enabled) {
+ mqttConnectionOptions.setAutomaticReconnect(enabled);
+ return this;
+ }
+
+ public MqttConnectionOptionsBuilder automaticReconnectDelay(int minimum, int maximum) {
+ mqttConnectionOptions.setAutomaticReconnectDelay(minimum, maximum);
+ return this;
+ }
+
+ public MqttConnectionOptionsBuilder keepAliveInterval(int keepAlive) {
+ mqttConnectionOptions.setKeepAliveInterval(keepAlive);
+ return this;
+ }
+
+ public MqttConnectionOptionsBuilder connectionTimeout(int connectionTimeout) {
+ mqttConnectionOptions.setConnectionTimeout(connectionTimeout);
+ return this;
+ }
+
+ public MqttConnectionOptionsBuilder cleanStart(boolean cleanStart) {
+ mqttConnectionOptions.setCleanStart(cleanStart);
+ return this;
+ }
+
+ public MqttConnectionOptionsBuilder username(String username) {
+ mqttConnectionOptions.setUserName(username);
+ return this;
+ }
+
+ public MqttConnectionOptionsBuilder password(byte[] password) {
+ mqttConnectionOptions.setPassword(password);
+ return this;
+ }
+
+ public MqttConnectionOptionsBuilder will(String topic, MqttMessage message) {
+ mqttConnectionOptions.setWill(topic, message);
+ return this;
+ }
+
+ public MqttConnectionOptionsBuilder sessionExpiryInterval(Long sessionExpiryInterval) {
+ mqttConnectionOptions.setSessionExpiryInterval(sessionExpiryInterval);
+ return this;
+ }
+
+ public MqttConnectionOptionsBuilder maximumPacketSize(Long maximumPacketSize) {
+ mqttConnectionOptions.setMaximumPacketSize(maximumPacketSize);
+ return this;
+ }
+
+ public MqttConnectionOptionsBuilder topicAliasMaximum(Integer topicAliasMaximum) {
+ mqttConnectionOptions.setTopicAliasMaximum(topicAliasMaximum);
+ return this;
+ }
+
+ public MqttConnectionOptionsBuilder requestReponseInfo(Boolean requestResponseInfo) {
+ mqttConnectionOptions.setRequestResponseInfo(requestResponseInfo);
+ return this;
+ }
+
+ public MqttConnectionOptionsBuilder requestProblemInfo(Boolean requestProblemInfo) {
+ mqttConnectionOptions.setRequestProblemInfo(requestProblemInfo);
+ return this;
+ }
+
+ public MqttConnectionOptionsBuilder userProperties(ArrayList properties) {
+ mqttConnectionOptions.setUserProperties(properties);
+ return this;
+ }
+
+ public MqttConnectionOptionsBuilder authMethod(String authMethod) {
+ mqttConnectionOptions.setAuthMethod(authMethod);
+ return this;
+ }
+
+ public MqttConnectionOptionsBuilder authData(byte[] authData) {
+ mqttConnectionOptions.setAuthData(authData);
+ return this;
+ }
+
+ public MqttConnectionOptions build() {
+ return mqttConnectionOptions;
+ }
+
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/MqttDeliveryToken.java b/src/main/java/org/eclipse/paho/mqttv5/client/MqttDeliveryToken.java
new file mode 100644
index 0000000..dc8aae7
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/MqttDeliveryToken.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.mqttv5.client;
+
+import org.eclipse.paho.mqttv5.common.MqttException;
+import org.eclipse.paho.mqttv5.common.MqttMessage;
+
+/**
+ * Provides a mechanism to track the delivery progress of a message.
+ *
+ *
+ * Used to track the the delivery progress of a message when a publish is
+ * executed in a non-blocking manner (run in the background)
+ *
+ * @see MqttToken
+ */
+public class MqttDeliveryToken extends MqttToken implements IMqttDeliveryToken {
+
+
+ public MqttDeliveryToken() {
+ super();
+ }
+
+ public MqttDeliveryToken(String logContext) {
+ super(logContext);
+ }
+
+ /**
+ * Returns the message associated with this token.
+ *
Until the message has been delivered, the message being delivered will
+ * be returned. Once the message has been delivered null will be
+ * returned.
+ * @return the message associated with this token or null if already delivered.
+ * @throws MqttException if there was a problem completing retrieving the message
+ */
+ public MqttMessage getMessage() throws MqttException {
+ return internalTok.getMessage();
+ }
+
+ protected void setMessage(MqttMessage msg) {
+ internalTok.setMessage(msg);
+ }
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/MqttDisconnectResponse.java b/src/main/java/org/eclipse/paho/mqttv5/client/MqttDisconnectResponse.java
new file mode 100644
index 0000000..7a09450
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/MqttDisconnectResponse.java
@@ -0,0 +1,97 @@
+package org.eclipse.paho.mqttv5.client;
+
+import java.util.ArrayList;
+
+import org.eclipse.paho.mqttv5.common.MqttException;
+import org.eclipse.paho.mqttv5.common.packet.MqttDisconnect;
+import org.eclipse.paho.mqttv5.common.packet.UserProperty;
+
+/**
+ * This Object holds relevant properties for the client to use once the TCP
+ * connection has either been lost, or if the server has gracefully
+ * disconnected.
+ *
+ */
+public class MqttDisconnectResponse {
+ private int returnCode;
+ private String reasonString;
+ private ArrayList userProperties;
+ private String serverReference;
+ private MqttException exception;
+
+ /**
+ * Initialises a new MqttDisconnectResponse with an exception. This will only be
+ * thrown in the event of a non-clean disconnect, i.e. connection lost.
+ *
+ * @param exception
+ * The exception thrown containing details about the lost connection.
+ */
+ public MqttDisconnectResponse(MqttException exception) {
+ this.exception = exception;
+ }
+
+ /**
+ * Initialise a new MqttDisconnectResponse with parameters provided in an
+ * {@link MqttDisconnect} packet. This will be used when a clean disconnect has
+ * Occurred.
+ *
+ * @param returnCode
+ * The Disconnect Return Code
+ * @param reasonString
+ * The Disconnect Reason String
+ * @param userProperties
+ * The Disconnect User Properties
+ * @param serverReference
+ * The Server Reference
+ */
+ public MqttDisconnectResponse(int returnCode, String reasonString, ArrayList userProperties,
+ String serverReference) {
+ this.returnCode = returnCode;
+ this.reasonString = reasonString;
+ this.userProperties = userProperties;
+ this.serverReference = serverReference;
+ }
+
+ /**
+ * @return The Return code
+ */
+ public int getReturnCode() {
+ return returnCode;
+ }
+
+ /**
+ * @return The Reason String
+ */
+ public String getReasonString() {
+ return reasonString;
+ }
+
+ /**
+ * @return The User Properties
+ */
+ public ArrayList getUserProperties() {
+ return userProperties;
+ }
+
+ /**
+ * @return The Server Reference, a new URI for a different server to connect to.
+ */
+ public String getServerReference() {
+ return serverReference;
+ }
+
+ /**
+ * @return The Exception thrown, most likely caused by a lost connection.
+ */
+ public MqttException getException() {
+ return exception;
+ }
+
+ @Override
+ public String toString() {
+ return "MqttDisconnectResponse [returnCode=" + returnCode + ", reasonString=" + reasonString
+ + ", userProperties=" + userProperties + ", serverReference=" + serverReference + ", exception="
+ + exception + "]";
+ }
+
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/MqttPingSender.java b/src/main/java/org/eclipse/paho/mqttv5/client/MqttPingSender.java
new file mode 100644
index 0000000..c6763d8
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/MqttPingSender.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (c) 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ */
+
+package org.eclipse.paho.mqttv5.client;
+
+import org.eclipse.paho.mqttv5.client.internal.ClientComms;
+
+/**
+ * Represents an object used to send ping packet to MQTT broker every keep alive
+ * interval.
+ */
+public interface MqttPingSender {
+
+ /**
+ * Initial method. Pass interal state of current client in.
+ *
+ * @param comms
+ * The core of the client, which holds the state information for
+ * pending and in-flight messages.
+ */
+ void init(ClientComms comms);
+
+ /**
+ * Start ping sender. It will be called after connection is success.
+ */
+ void start();
+
+ /**
+ * Stop ping sender. It is called if there is any errors or connection
+ * shutdowns.
+ */
+ void stop();
+
+ /**
+ * Schedule next ping in certain delay.
+ *
+ * @param delayInMilliseconds
+ * delay in milliseconds.
+ */
+ void schedule(long delayInMilliseconds);
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/MqttToken.java b/src/main/java/org/eclipse/paho/mqttv5/client/MqttToken.java
new file mode 100644
index 0000000..b33aa69
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/MqttToken.java
@@ -0,0 +1,148 @@
+/*******************************************************************************
+ * Copyright (c) 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributions:
+ * Ian Craggs - MQTT 3.1.1 support
+ */
+
+package org.eclipse.paho.mqttv5.client;
+
+import org.eclipse.paho.mqttv5.client.internal.Token;
+import org.eclipse.paho.mqttv5.common.MqttException;
+import org.eclipse.paho.mqttv5.common.MqttMessage;
+import org.eclipse.paho.mqttv5.common.packet.MqttProperties;
+import org.eclipse.paho.mqttv5.common.packet.MqttWireMessage;
+
+/**
+ * Provides a mechanism for tracking the completion of an asynchronous action.
+ *
+ * A token that implements the ImqttToken interface is returned from all
+ * non-blocking method with the exception of publish.
+ *
+ *
+ * @see IMqttToken
+ */
+
+public class MqttToken implements IMqttToken {
+ private MqttAsyncClient client = null;
+ /**
+ * A reference to the the class that provides most of the implementation of the
+ * MqttToken. MQTT application programs must not use the internal class.
+ */
+ public Token internalTok = null;
+
+ public MqttToken() {
+ }
+
+ public MqttToken(MqttAsyncClient client) {
+ this.client = client;
+ }
+
+ public MqttToken(String logContext) {
+ internalTok = new Token(logContext);
+ }
+
+ public MqttException getException() {
+ return internalTok.getException();
+ }
+
+ public boolean isComplete() {
+ return internalTok.isComplete();
+ }
+
+ public void setActionCallback(MqttActionListener listener) {
+ internalTok.setActionCallback(listener);
+
+ }
+
+ public MqttActionListener getActionCallback() {
+ return internalTok.getActionCallback();
+ }
+
+ public void waitForCompletion() throws MqttException {
+ internalTok.waitForCompletion(-1);
+ }
+
+ public void waitForCompletion(long timeout) throws MqttException {
+ internalTok.waitForCompletion(timeout);
+ }
+
+ public MqttClientInterface getClient() {
+ return internalTok.getClient();
+ }
+
+ public String[] getTopics() {
+ return internalTok.getTopics();
+ }
+
+ public Object getUserContext() {
+ return internalTok.getUserContext();
+ }
+
+ public void setUserContext(Object userContext) {
+ internalTok.setUserContext(userContext);
+ }
+
+ public int getMessageId() {
+ return internalTok.getMessageID();
+ }
+
+ public int[] getGrantedQos() {
+ return internalTok.getGrantedQos();
+ }
+
+ public boolean getSessionPresent() {
+ return internalTok.getSessionPresent();
+ }
+
+ public MqttWireMessage getResponse() {
+ return internalTok.getResponse();
+ }
+
+ public MqttProperties getResponseProperties() {
+ return (internalTok.getWireMessage() == null) ? null : internalTok.getWireMessage().getProperties();
+ }
+
+ public MqttWireMessage getRequestMessage() {
+ return internalTok.getRequestMessage();
+ }
+
+ public void setRequestMessage(MqttWireMessage request) {
+ internalTok.setRequestMessage(request);
+ }
+
+ public MqttProperties getRequestProperties() {
+ return (internalTok.getRequestMessage() == null) ? null : internalTok.getRequestMessage().getProperties();
+ }
+
+ @Override
+ public int[] getReasonCodes() {
+ return internalTok.getReasonCodes();
+ }
+
+ /**
+ * Returns the message associated with this token.
+ *
Until the message has been delivered, the message being delivered will
+ * be returned. Once the message has been delivered null will be
+ * returned.
+ * @return the message associated with this token or null if already delivered.
+ * @throws MqttException if there was a problem completing retrieving the message
+ */
+ public MqttMessage getMessage() throws MqttException {
+ return internalTok.getMessage();
+ }
+
+ protected void setMessage(MqttMessage msg) {
+ internalTok.setMessage(msg);
+ }
+
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/MqttTopic.java b/src/main/java/org/eclipse/paho/mqttv5/client/MqttTopic.java
new file mode 100644
index 0000000..5cccf86
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/MqttTopic.java
@@ -0,0 +1,126 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.mqttv5.client;
+
+import org.eclipse.paho.mqttv5.client.internal.ClientComms;
+import org.eclipse.paho.mqttv5.common.MqttException;
+import org.eclipse.paho.mqttv5.common.MqttMessage;
+import org.eclipse.paho.mqttv5.common.MqttPersistenceException;
+import org.eclipse.paho.mqttv5.common.packet.MqttProperties;
+import org.eclipse.paho.mqttv5.common.packet.MqttPublish;
+
+/**
+ * Represents a topic destination, used for publish/subscribe messaging.
+ */
+public class MqttTopic {
+
+
+ private ClientComms comms;
+ private String name;
+
+ /**
+ * @param name
+ * The Name of the topic
+ * @param comms
+ * The {@link ClientComms}
+ */
+ public MqttTopic(String name, ClientComms comms) {
+ this.comms = comms;
+ this.name = name;
+ }
+
+ /**
+ * Publishes a message on the topic. This is a convenience method, which will
+ * create a new {@link MqttMessage} object with a byte array payload and the
+ * specified QoS, and then publish it. All other values in the message will be
+ * set to the defaults.
+ *
+ * @param payload
+ * the byte array to use as the payload
+ * @param qos
+ * the Quality of Service. Valid values are 0, 1 or 2.
+ * @param retained
+ * whether or not this message should be retained by the server.
+ * @return {@link MqttToken}
+ * @throws MqttException
+ * If an error occurs publishing the message
+ * @throws MqttPersistenceException
+ * If an error occurs persisting the message
+ * @throws IllegalArgumentException
+ * if value of QoS is not 0, 1 or 2.
+ * @see #publish(MqttMessage)
+ * @see MqttMessage#setQos(int)
+ * @see MqttMessage#setRetained(boolean)
+ */
+ public MqttToken publish(byte[] payload, int qos, boolean retained)
+ throws MqttException, MqttPersistenceException {
+ MqttMessage message = new MqttMessage(payload);
+ message.setQos(qos);
+ message.setRetained(retained);
+ return this.publish(message);
+ }
+
+ /**
+ * Publishes the specified message to this topic, but does not wait for delivery
+ * of the message to complete. The returned {@link MqttToken token} can
+ * be used to track the delivery status of the message. Once this method has
+ * returned cleanly, the message has been accepted for publication by the
+ * client. Message delivery will be completed in the background when a
+ * connection is available.
+ *
+ * @param message
+ * the message to publish
+ * @return an MqttToken for tracking the delivery of the message
+ * @throws MqttException
+ * if an error occurs publishing the message
+ * @throws MqttPersistenceException
+ * if an error occurs persisting the message
+ */
+ public MqttToken publish(MqttMessage message) throws MqttException, MqttPersistenceException {
+ MqttToken token = new MqttToken(comms.getClient().getClientId());
+ token.internalTok.setDeliveryToken(true);
+ token.setMessage(message);
+ comms.sendNoWait(createPublish(message, new MqttProperties()), token);
+ token.internalTok.waitUntilSent();
+ return token;
+ }
+
+ /**
+ * Returns the name of the queue or topic.
+ *
+ * @return the name of this destination.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Create a PUBLISH packet from the specified message.
+ */
+ private MqttPublish createPublish(MqttMessage message, MqttProperties properties) {
+ return new MqttPublish(this.getName(), message, properties);
+ }
+
+ /**
+ * Returns a string representation of this topic.
+ *
+ * @return a string representation of this topic.
+ */
+ public String toString() {
+ return getName();
+ }
+
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/TimerPingSender.java b/src/main/java/org/eclipse/paho/mqttv5/client/TimerPingSender.java
new file mode 100644
index 0000000..85c4470
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/TimerPingSender.java
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * Copyright (c) 2014, 2018 IBM Corp.
+ * Copyright (c) 2017 BMW Car IT GmbH.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ */
+
+package org.eclipse.paho.mqttv5.client;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.paho.mqttv5.client.internal.ClientComms;
+import org.eclipse.paho.mqttv5.client.logging.Logger;
+import org.eclipse.paho.mqttv5.client.logging.LoggerFactory;
+
+/**
+ * Default ping sender implementation
+ *
+ *
This class implements the {@link MqttPingSender} pinger interface
+ * allowing applications to send ping packet to server every keep alive interval.
+ *
+ *
+ * @see MqttPingSender
+ */
+public class TimerPingSender implements MqttPingSender{
+ private static final String CLASS_NAME = TimerPingSender.class.getName();
+ private Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT, CLASS_NAME);
+
+ private ClientComms comms;
+ private Timer timer;
+ private ScheduledExecutorService executorService = null;
+ private ScheduledFuture> scheduledFuture;
+ private String clientid;
+
+ public TimerPingSender(ScheduledExecutorService executorService) {
+ this.executorService = executorService;
+ }
+
+ public void init(ClientComms comms) {
+ if (comms == null) {
+ throw new IllegalArgumentException("ClientComms cannot be null.");
+ }
+ this.comms = comms;
+ clientid = comms.getClient().getClientId();
+ log.setResourceName(clientid);
+ }
+
+ public void start() {
+ final String methodName = "start";
+
+ //@Trace 659=start timer for client:{0}
+ log.fine(CLASS_NAME, methodName, "659", new Object[]{ clientid });
+ if (executorService == null) {
+ timer = new Timer("MQTT Ping: " + clientid);
+ //Check ping after first keep alive interval.
+ timer.schedule(new PingTask(), comms.getKeepAlive());
+ } else {
+ //Check ping after first keep alive interval.
+ schedule(comms.getKeepAlive());
+ }
+ }
+
+ public void stop() {
+ final String methodName = "stop";
+ //@Trace 661=stop
+ log.fine(CLASS_NAME, methodName, "661", null);
+ if (executorService == null) {
+ if (timer != null){
+ timer.cancel();
+ }
+ } else {
+ if (scheduledFuture != null) {
+ scheduledFuture.cancel(true);
+ }
+ }
+ }
+
+ public void schedule(long delayInMilliseconds) {
+ if (executorService == null) {
+ timer.schedule(new PingTask(), delayInMilliseconds);
+ } else {
+ scheduledFuture = executorService.schedule(new PingRunnable(), delayInMilliseconds, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ private class PingTask extends TimerTask {
+ private static final String methodName = "PingTask.run";
+
+ public void run() {
+ Thread.currentThread().setName("MQTT Ping: " + clientid);
+ //@Trace 660=Check schedule at {0}
+ log.fine(CLASS_NAME, methodName, "660", new Object[]{ Long.valueOf(System.nanoTime()) });
+ comms.checkForActivity();
+ }
+ }
+
+ private class PingRunnable implements Runnable {
+ private static final String methodName = "PingTask.run";
+
+ public void run() {
+ String originalThreadName = Thread.currentThread().getName();
+ Thread.currentThread().setName("MQTT Ping: " + clientid);
+ //@Trace 660=Check schedule at {0}
+ log.fine(CLASS_NAME, methodName, "660", new Object[]{ Long.valueOf(System.nanoTime()) });
+ comms.checkForActivity();
+ Thread.currentThread().setName(originalThreadName);
+ }
+ }
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/internal/ClientComms.java b/src/main/java/org/eclipse/paho/mqttv5/client/internal/ClientComms.java
new file mode 100644
index 0000000..7a84632
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/internal/ClientComms.java
@@ -0,0 +1,978 @@
+package org.eclipse.paho.mqttv5.client.internal;
+
+/*******************************************************************************
+ * Copyright (c) 2009, 2018 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ * Ian Craggs - per subscription message handlers (bug 466579)
+ * Ian Craggs - ack control (bug 472172)
+ * James Sutton - checkForActivity Token (bug 473928)
+ * James Sutton - Automatic Reconnect & Offline Buffering.
+ */
+import java.util.Enumeration;
+import java.util.Properties;
+import java.util.Vector;
+import java.util.concurrent.ExecutorService;
+
+import org.eclipse.paho.mqttv5.client.BufferedMessage;
+import org.eclipse.paho.mqttv5.client.IMqttMessageListener;
+import org.eclipse.paho.mqttv5.client.MqttActionListener;
+import org.eclipse.paho.mqttv5.client.MqttCallback;
+import org.eclipse.paho.mqttv5.client.MqttClientException;
+import org.eclipse.paho.mqttv5.client.MqttClientInterface;
+import org.eclipse.paho.mqttv5.client.MqttClientPersistence;
+import org.eclipse.paho.mqttv5.client.MqttConnectionOptions;
+import org.eclipse.paho.mqttv5.client.MqttPingSender;
+import org.eclipse.paho.mqttv5.client.MqttToken;
+import org.eclipse.paho.mqttv5.client.MqttTopic;
+import org.eclipse.paho.mqttv5.client.TimerPingSender;
+import org.eclipse.paho.mqttv5.client.logging.Logger;
+import org.eclipse.paho.mqttv5.client.logging.LoggerFactory;
+import org.eclipse.paho.mqttv5.common.MqttException;
+import org.eclipse.paho.mqttv5.common.MqttMessage;
+import org.eclipse.paho.mqttv5.common.MqttPersistenceException;
+import org.eclipse.paho.mqttv5.common.packet.MqttConnAck;
+import org.eclipse.paho.mqttv5.common.packet.MqttConnect;
+import org.eclipse.paho.mqttv5.common.packet.MqttDisconnect;
+import org.eclipse.paho.mqttv5.common.packet.MqttProperties;
+import org.eclipse.paho.mqttv5.common.packet.MqttPublish;
+import org.eclipse.paho.mqttv5.common.packet.MqttWireMessage;
+
+/**
+ * Handles client communications with the server. Sends and receives MQTT V5
+ * messages.
+ */
+public class ClientComms {
+ public static String VERSION = "${project.version}";
+ public static String BUILD_LEVEL = "L${build.level}";
+ private static final String CLASS_NAME = ClientComms.class.getName();
+ private Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT, CLASS_NAME);
+
+ private static final byte CONNECTED = 0;
+ private static final byte CONNECTING = 1;
+ private static final byte DISCONNECTING = 2;
+ private static final byte DISCONNECTED = 3;
+ private static final byte CLOSED = 4;
+
+ private MqttClientInterface client;
+ private int networkModuleIndex;
+ private NetworkModule[] networkModules;
+ private CommsReceiver receiver;
+ private CommsSender sender;
+ private CommsCallback callback;
+ private ClientState clientState;
+ private MqttConnectionOptions conOptions;
+ private MqttClientPersistence persistence;
+ private MqttPingSender pingSender;
+ private CommsTokenStore tokenStore;
+ private boolean stoppingComms = false;
+
+ private byte conState = DISCONNECTED;
+ private final Object conLock = new Object(); // Used to synchronize connection state
+ private boolean closePending = false;
+ private boolean resting = false;
+ private DisconnectedMessageBuffer disconnectedMessageBuffer;
+ private ExecutorService executorService;
+ private MqttConnectionState mqttConnection;
+
+ /**
+ * Creates a new ClientComms object, using the specified module to handle the
+ * network calls.
+ *
+ * @param client
+ * The {@link MqttClientInterface}
+ * @param persistence
+ * the {@link MqttClientPersistence} layer.
+ * @param pingSender
+ * the {@link TimerPingSender}
+ * @param executorService
+ * the {@link ExecutorService}
+ * @param mqttSession
+ * the {@link MqttSessionState}
+ * @param mqttConnection
+ * the {@link MqttConnectionState}
+ * @throws MqttException
+ * if an exception occurs whilst communicating with the server
+ */
+ public ClientComms(MqttClientInterface client, MqttClientPersistence persistence, MqttPingSender pingSender,
+ ExecutorService executorService, MqttSessionState mqttSession, MqttConnectionState mqttConnection) throws MqttException {
+ this.conState = DISCONNECTED;
+ this.client = client;
+ this.persistence = persistence;
+ this.pingSender = pingSender;
+ this.pingSender.init(this);
+ this.executorService = executorService;
+ this.mqttConnection = mqttConnection;
+
+ this.tokenStore = new CommsTokenStore(getClient().getClientId());
+ this.callback = new CommsCallback(this);
+ this.clientState = new ClientState(persistence, tokenStore, this.callback, this, pingSender, this.mqttConnection);
+
+ callback.setClientState(clientState);
+ log.setResourceName(getClient().getClientId());
+ }
+
+ CommsReceiver getReceiver() {
+ return receiver;
+ }
+
+ /**
+ * Sends a message to the server. Does not check if connected this validation
+ * must be done by invoking routines.
+ *
+ * @param message
+ * @param token
+ * @throws MqttException
+ */
+ void internalSend(MqttWireMessage message, MqttToken token) throws MqttException {
+ final String methodName = "internalSend";
+ // @TRACE 200=internalSend key={0} message={1} token={2}
+ log.fine(CLASS_NAME, methodName, "200", new Object[] { message.getKey(), message, token });
+
+ if (token.getClient() == null) {
+ // Associate the client with the token - also marks it as in use.
+ token.internalTok.setClient(getClient());
+ } else {
+ // Token is already in use - cannot reuse
+ // @TRACE 213=fail: token in use: key={0} message={1} token={2}
+ log.fine(CLASS_NAME, methodName, "213", new Object[] { message.getKey(), message, token });
+
+ throw new MqttException(MqttClientException.REASON_CODE_TOKEN_INUSE);
+ }
+
+ try {
+ // Persist if needed and send the message
+ this.clientState.send(message, token);
+ } catch (MqttException e) {
+ token.internalTok.setClient(null); // undo client setting on error
+ if (message instanceof MqttPublish) {
+ this.clientState.undo((MqttPublish) message);
+ }
+ throw e;
+ }
+ }
+
+ /**
+ * Sends a message to the broker if in connected state, but only waits for the
+ * message to be stored, before returning.
+ *
+ * @param message
+ * The {@link MqttWireMessage} to send
+ * @param token
+ * The {@link MqttToken} to send.
+ * @throws MqttException
+ * if an error occurs sending the message
+ */
+ public void sendNoWait(MqttWireMessage message, MqttToken token) throws MqttException {
+ final String methodName = "sendNoWait";
+
+ if (isConnected() || (!isConnected() && message instanceof MqttConnect)
+ || (isDisconnecting() && message instanceof MqttDisconnect)) {
+
+ if (disconnectedMessageBuffer != null && disconnectedMessageBuffer.getMessageCount() != 0) {
+ // @TRACE 507=Client Connected, Offline Buffer available, but not empty. Adding
+ // message to buffer. message={0}
+ log.fine(CLASS_NAME, methodName, "507", new Object[] { message.getKey() });
+ // If the message is a publish, strip the topic alias:
+ if(message instanceof MqttPublish && message.getProperties().getTopicAlias()!= null) {
+ MqttProperties messageProps = message.getProperties();
+ messageProps.setTopicAlias(null);
+ message.setProperties(messageProps);
+ }
+ if (disconnectedMessageBuffer.isPersistBuffer()) {
+ this.clientState.persistBufferedMessage(message);
+ }
+ disconnectedMessageBuffer.putMessage(message, token);
+
+ } else {
+
+ if (message instanceof MqttPublish) {
+ // Override the QoS if the server has set a maximum
+ if (this.mqttConnection.getMaximumQoS() != null
+ && ((MqttPublish) message).getMessage().getQos() > this.mqttConnection.getMaximumQoS()) {
+ MqttMessage mqttMessage = ((MqttPublish) message).getMessage();
+ mqttMessage.setQos(this.mqttConnection.getMaximumQoS());
+ ((MqttPublish) message).setMessage(mqttMessage);
+ }
+
+ // Override the Retain flag if the server has disabled it
+ if (this.mqttConnection.isRetainAvailable() != null
+ && ((MqttPublish) message).getMessage().isRetained()
+ && (this.mqttConnection.isRetainAvailable() == false)) {
+ MqttMessage mqttMessage = ((MqttPublish) message).getMessage();
+ mqttMessage.setRetained(false);
+ ((MqttPublish) message).setMessage(mqttMessage);
+ }
+
+ }
+ this.internalSend(message, token);
+ }
+ } else if (disconnectedMessageBuffer != null && isResting()) {
+ // @TRACE 508=Client Resting, Offline Buffer available. Adding message to
+ // buffer. message={0}
+ log.fine(CLASS_NAME, methodName, "508", new Object[] { message.getKey() });
+ if (disconnectedMessageBuffer.isPersistBuffer()) {
+ this.clientState.persistBufferedMessage(message);
+ }
+ disconnectedMessageBuffer.putMessage(message, token);
+ } else {
+ // @TRACE 208=failed: not connected
+ log.fine(CLASS_NAME, methodName, "208");
+ throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_CLIENT_NOT_CONNECTED);
+ }
+ }
+
+ /**
+ * Close and tidy up.
+ *
+ * Call each main class and let it tidy up e.g. releasing the token store which
+ * normally survives a disconnect.
+ *
+ * @param force
+ * force disconnection
+ * @throws MqttException
+ * if not disconnected
+ */
+ public void close(boolean force) throws MqttException {
+ final String methodName = "close";
+ synchronized (conLock) {
+ if (!isClosed()) {
+ // Must be disconnected before close can take place or if we are being forced
+ if (!isDisconnected() || force) {
+ // @TRACE 224=failed: not disconnected
+ log.fine(CLASS_NAME, methodName, "224");
+
+ if (isConnecting()) {
+ throw new MqttException(MqttClientException.REASON_CODE_CONNECT_IN_PROGRESS);
+ } else if (isConnected()) {
+ throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_CLIENT_CONNECTED);
+ } else if (isDisconnecting()) {
+ closePending = true;
+ return;
+ }
+ }
+
+ conState = CLOSED;
+ // Don't shut down an externally supplied executor service
+ //shutdownExecutorService();
+ // ShutdownConnection has already cleaned most things
+ clientState.close();
+ clientState = null;
+ callback = null;
+ persistence = null;
+ sender = null;
+ pingSender = null;
+ receiver = null;
+ networkModules = null;
+ conOptions = null;
+ tokenStore = null;
+ }
+ }
+ }
+
+ /**
+ * Sends a connect message and waits for an ACK or NACK. Connecting is a special
+ * case which will also start up the network connection, receive thread, and
+ * keep alive thread.
+ *
+ * @param options
+ * The {@link MqttConnectionOptions} for the connection
+ * @param token
+ * The {@link MqttToken} to track the connection
+ * @throws MqttException
+ * if an error occurs when connecting
+ */
+ public void connect(MqttConnectionOptions options, MqttToken token) throws MqttException {
+ final String methodName = "connect";
+ synchronized (conLock) {
+ if (isDisconnected() && !closePending) {
+ // @TRACE 214=state=CONNECTING
+ log.fine(CLASS_NAME, methodName, "214");
+
+ conState = CONNECTING;
+
+ conOptions = options;
+
+ MqttConnect connect = new MqttConnect(client.getClientId(), conOptions.getMqttVersion(),
+ conOptions.isCleanStart(), conOptions.getKeepAliveInterval(),
+ conOptions.getConnectionProperties(), conOptions.getWillMessageProperties());
+
+ if (conOptions.getWillDestination() != null) {
+ connect.setWillDestination(conOptions.getWillDestination());
+ }
+
+ if (conOptions.getWillMessage() != null) {
+ connect.setWillMessage(conOptions.getWillMessage());
+ }
+
+ if (conOptions.getUserName() != null) {
+ connect.setUserName(conOptions.getUserName());
+ }
+ if (conOptions.getPassword() != null) {
+ connect.setPassword(conOptions.getPassword());
+ }
+
+ /*
+ * conOptions.getUserName(), conOptions.getPassword(),
+ * conOptions.getWillMessage(), conOptions.getWillDestination()
+ */
+ this.mqttConnection.setKeepAliveSeconds(conOptions.getKeepAliveInterval());
+ this.clientState.setCleanStart(conOptions.isCleanStart());
+
+ tokenStore.open();
+ ConnectBG conbg = new ConnectBG(this, token, connect, executorService);
+ conbg.start();
+ } else {
+ // @TRACE 207=connect failed: not disconnected {0}
+ log.fine(CLASS_NAME, methodName, "207", new Object[] { Byte.valueOf(conState) });
+ if (isClosed() || closePending) {
+ throw new MqttException(MqttClientException.REASON_CODE_CLIENT_CLOSED);
+ } else if (isConnecting()) {
+ throw new MqttException(MqttClientException.REASON_CODE_CONNECT_IN_PROGRESS);
+ } else if (isDisconnecting()) {
+ throw new MqttException(MqttClientException.REASON_CODE_CLIENT_DISCONNECTING);
+ } else {
+ throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_CLIENT_CONNECTED);
+ }
+ }
+ }
+ }
+
+ public void connectComplete(MqttConnAck cack, MqttException mex) throws MqttException {
+ final String methodName = "connectComplete";
+ int rc = cack.getReturnCode();
+ synchronized (conLock) {
+ if (rc == 0) {
+ // We've successfully connected
+ // @TRACE 215=state=CONNECTED
+ log.fine(CLASS_NAME, methodName, "215");
+
+ conState = CONNECTED;
+ return;
+ }
+ }
+
+ // @TRACE 204=connect failed: rc={0}
+ log.fine(CLASS_NAME, methodName, "204", new Object[] { Integer.valueOf(rc) });
+ throw mex;
+ }
+
+ /**
+ * Shuts down the connection to the server. This may have been invoked as a
+ * result of a user calling disconnect or an abnormal disconnection. The method
+ * may be invoked multiple times in parallel as each thread when it receives an
+ * error uses this method to ensure that shutdown completes successfully.
+ *
+ * @param token
+ * the {@link MqttToken} To track closing the connection
+ * @param reason
+ * the {@link MqttException} thrown requiring the connection to be
+ * shut down.
+ * @param message
+ * the {@link MqttDisconnect} that triggered the connection to be
+ * shut down.
+ */
+ public void shutdownConnection(MqttToken token, MqttException reason, MqttDisconnect message) {
+ final String methodName = "shutdownConnection";
+
+ boolean wasConnected;
+ MqttToken endToken = null; // Token to notify after disconnect completes
+
+ // This method could concurrently be invoked from many places only allow it
+ // to run once.
+ synchronized (conLock) {
+ if (stoppingComms || closePending || isClosed()) {
+ return;
+ }
+ stoppingComms = true;
+
+ // @TRACE 216=state=DISCONNECTING
+ log.fine(CLASS_NAME, methodName, "216");
+
+ wasConnected = (isConnected() || isDisconnecting());
+ conState = DISCONNECTING;
+ }
+
+ // Update the token with the reason for shutdown if it
+ // is not already complete.
+ if (token != null && !token.isComplete()) {
+ token.internalTok.setException(reason);
+ }
+
+ // Stop the thread that is used to call the user back
+ // when actions complete
+ if (callback != null) {
+ callback.stop();
+ }
+
+ // Stop the thread that handles inbound work from the network
+ if (receiver != null) {
+ receiver.stop();
+ }
+
+ // Stop the network module, send and receive now not possible
+ try {
+ if (networkModules != null) {
+ NetworkModule networkModule = networkModules[networkModuleIndex];
+ if (networkModule != null) {
+ networkModule.stop();
+ }
+ }
+ } catch (Exception ioe) {
+ // Ignore as we are shutting down
+ }
+
+ // Stop any new tokens being saved by app and throwing an exception if they do
+ tokenStore.quiesce(new MqttException(MqttClientException.REASON_CODE_CLIENT_DISCONNECTING));
+
+ // Notify any outstanding tokens with the exception of
+ // con or discon which may be returned and will be notified at
+ // the end
+ endToken = handleOldTokens(token, reason);
+
+ try {
+ // Clean session handling and tidy up
+ clientState.disconnected(reason);
+ if (clientState.getCleanStart())
+ callback.removeMessageListeners();
+ } catch (Exception ex) {
+ // Ignore as we are shutting down
+ }
+
+ if (sender != null) {
+ sender.stop();
+ }
+
+ if (pingSender != null) {
+ pingSender.stop();
+ }
+
+ try {
+ if (disconnectedMessageBuffer == null && persistence != null) {
+ persistence.close();
+ }
+
+ } catch (Exception ex) {
+ // Ignore as we are shutting down
+ }
+ // All disconnect logic has been completed allowing the
+ // client to be marked as disconnected.
+ synchronized (conLock) {
+ // @TRACE 217=state=DISCONNECTED
+ log.fine(CLASS_NAME, methodName, "217");
+
+ conState = DISCONNECTED;
+ stoppingComms = false;
+ }
+
+ // Internal disconnect processing has completed. If there
+ // is a disconnect token or a connect in error notify
+ // it now. This is done at the end to allow a new connect
+ // to be processed and now throw a currently disconnecting error.
+ // any outstanding tokens and unblock any waiters
+ if (endToken != null && callback != null) {
+ callback.asyncOperationComplete(endToken);
+ }
+ if (wasConnected && callback != null) {
+ // Let the user know client has disconnected either normally or abnormally
+ callback.connectionLost(reason, message);
+ }
+
+ // While disconnecting, close may have been requested - try it now
+ synchronized (conLock) {
+ if (closePending) {
+ try {
+ close(true);
+ } catch (Exception e) { // ignore any errors as closing
+ }
+ }
+ }
+ }
+
+ // Tidy up. There may be tokens outstanding as the client was
+ // not disconnected/quiseced cleanly! Work out what tokens still
+ // need to be notified and waiters unblocked. Store the
+ // disconnect or connect token to notify after disconnect is
+ // complete.
+ private MqttToken handleOldTokens(MqttToken token, MqttException reason) {
+ final String methodName = "handleOldTokens";
+ // @TRACE 222=>
+ log.fine(CLASS_NAME, methodName, "222");
+
+ MqttToken tokToNotifyLater = null;
+ try {
+ // First the token that was related to the disconnect / shutdown may
+ // not be in the token table - temporarily add it if not
+ if (token != null) {
+ if (tokenStore.getToken(token.internalTok.getKey()) == null) {
+ tokenStore.saveToken(token, token.internalTok.getKey());
+ }
+ }
+
+ Vector toksToNot = clientState.resolveOldTokens(reason);
+ Enumeration toksToNotE = toksToNot.elements();
+ while (toksToNotE.hasMoreElements()) {
+ MqttToken tok = (MqttToken) toksToNotE.nextElement();
+
+ if (tok.internalTok.getKey().equals(MqttDisconnect.KEY)
+ || tok.internalTok.getKey().equals(MqttConnect.KEY)) {
+ // Its con or discon so remember and notify @ end of disc routine
+ tokToNotifyLater = tok;
+ } else {
+ // notify waiters and callbacks of outstanding tokens
+ // that a problem has occurred and disconnect is in
+ // progress
+ callback.asyncOperationComplete(tok);
+ }
+ }
+ } catch (Exception ex) {
+ // Ignore as we are shutting down
+ }
+ return tokToNotifyLater;
+ }
+
+ public void disconnect(MqttDisconnect disconnect, long quiesceTimeout, MqttToken token) throws MqttException {
+ final String methodName = "disconnect";
+ synchronized (conLock) {
+ if (isClosed()) {
+ // @TRACE 223=failed: in closed state
+ log.fine(CLASS_NAME, methodName, "223");
+ throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_CLIENT_CLOSED);
+ } else if (isDisconnected()) {
+ // @TRACE 211=failed: already disconnected
+ log.fine(CLASS_NAME, methodName, "211");
+ throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_CLIENT_ALREADY_DISCONNECTED);
+ } else if (isDisconnecting()) {
+ // @TRACE 219=failed: already disconnecting
+ log.fine(CLASS_NAME, methodName, "219");
+ throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_CLIENT_DISCONNECTING);
+ } else if (Thread.currentThread() == callback.getThread()) {
+ // @TRACE 210=failed: called on callback thread
+ log.fine(CLASS_NAME, methodName, "210");
+ // Not allowed to call disconnect() from the callback, as it will deadlock.
+ throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_CLIENT_DISCONNECT_PROHIBITED);
+ }
+
+ // @TRACE 218=state=DISCONNECTING
+ log.fine(CLASS_NAME, methodName, "218");
+ conState = DISCONNECTING;
+ DisconnectBG discbg = new DisconnectBG(disconnect, quiesceTimeout, token, executorService);
+ discbg.start();
+ }
+ }
+
+ public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout, int reasonCode,
+ MqttProperties disconnectProperties) throws MqttException {
+ disconnectForcibly(quiesceTimeout, disconnectTimeout, true, reasonCode, disconnectProperties);
+ }
+
+ /**
+ * Disconnect the connection and reset all the states.
+ *
+ * @param quiesceTimeout
+ * How long to wait whilst quiesing before messages are deleted.
+ * @param disconnectTimeout
+ * How long to wait whilst disconnecting
+ * @param sendDisconnectPacket
+ * If true, will send a disconnect packet
+ * @param reasonCode
+ * the disconnection reason code.
+ * @param disconnectProperties
+ * the {@link MqttProperties} to send in the Disconnect packet.
+ * @throws MqttException
+ * if an error occurs whilst disconnecting
+ */
+ public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout, boolean sendDisconnectPacket,
+ int reasonCode, MqttProperties disconnectProperties) throws MqttException {
+ conState = DISCONNECTING;
+ // Allow current inbound and outbound work to complete
+ if (clientState != null) {
+ clientState.quiesce(quiesceTimeout);
+ }
+ MqttToken token = new MqttToken(client.getClientId());
+ try {
+ // Send disconnect packet
+ if (sendDisconnectPacket) {
+ internalSend(new MqttDisconnect(reasonCode, disconnectProperties), token);
+
+ // Wait util the disconnect packet sent with timeout
+ token.waitForCompletion(disconnectTimeout);
+ }
+ } catch (Exception ex) {
+ // ignore, probably means we failed to send the disconnect packet.
+ } finally {
+ token.internalTok.markComplete(null, null);
+ shutdownConnection(token, null, null);
+ }
+ }
+
+ public boolean isConnected() {
+ synchronized (conLock) {
+ return conState == CONNECTED;
+ }
+ }
+
+ public boolean isConnecting() {
+ synchronized (conLock) {
+ return conState == CONNECTING;
+ }
+ }
+
+ public boolean isDisconnected() {
+ synchronized (conLock) {
+ return conState == DISCONNECTED;
+ }
+ }
+
+ public boolean isDisconnecting() {
+ synchronized (conLock) {
+ return conState == DISCONNECTING;
+ }
+ }
+
+ public boolean isClosed() {
+ synchronized (conLock) {
+ return conState == CLOSED;
+ }
+ }
+
+ public boolean isResting() {
+ synchronized (conLock) {
+ return resting;
+ }
+ }
+
+ public void setCallback(MqttCallback mqttCallback) {
+ this.callback.setCallback(mqttCallback);
+ }
+
+ public void setReconnectCallback(MqttCallback callback) {
+ this.callback.setReconnectCallback(callback);
+ }
+
+ public void setManualAcks(boolean manualAcks) {
+ this.callback.setManualAcks(manualAcks);
+ }
+
+ public void messageArrivedComplete(int messageId, int qos) throws MqttException {
+ this.callback.messageArrivedComplete(messageId, qos);
+ }
+
+ public void setMessageListener(Integer subscriptionId, String topicFilter, IMqttMessageListener messageListener) {
+ this.callback.setMessageListener(subscriptionId, topicFilter, messageListener);
+ }
+
+ public void removeMessageListener(String topicFilter) {
+ this.callback.removeMessageListener(topicFilter);
+ }
+
+ protected MqttTopic getTopic(String topic) {
+ return new MqttTopic(topic, this);
+ }
+
+ public void setNetworkModuleIndex(int index) {
+ this.networkModuleIndex = index;
+ }
+
+ public int getNetworkModuleIndex() {
+ return networkModuleIndex;
+ }
+
+ public NetworkModule[] getNetworkModules() {
+ return networkModules;
+ }
+
+ public void setNetworkModules(NetworkModule[] networkModules) {
+ this.networkModules = networkModules;
+ }
+
+ public MqttToken[] getPendingTokens() {
+ return tokenStore.getOutstandingDelTokens();
+ }
+
+ protected void deliveryComplete(MqttPublish msg) throws MqttPersistenceException {
+ this.clientState.deliveryComplete(msg);
+ }
+
+ protected void deliveryComplete(int messageId) throws MqttPersistenceException {
+ this.clientState.deliveryComplete(messageId);
+ }
+
+ public MqttClientInterface getClient() {
+ return client;
+ }
+
+ public long getKeepAlive() {
+ return this.mqttConnection.getKeepAlive();
+ }
+
+ public MqttState getClientState() {
+ return clientState;
+ }
+
+ public MqttConnectionOptions getConOptions() {
+ return conOptions;
+ }
+
+ public Properties getDebug() {
+ Properties props = new Properties();
+ props.put("conState", Integer.valueOf(conState));
+ props.put("serverURI", getClient().getServerURI());
+ props.put("callback", callback);
+ props.put("stoppingComms", Boolean.valueOf(stoppingComms));
+ return props;
+ }
+
+ // Kick off the connect processing in the background so that it does not block.
+ // For instance
+ // the socket could take time to create.
+ private class ConnectBG implements Runnable {
+ ClientComms clientComms = null;
+ MqttToken conToken;
+ MqttConnect conPacket;
+ private String threadName;
+
+ ConnectBG(ClientComms cc, MqttToken cToken, MqttConnect cPacket, ExecutorService executorService) {
+ clientComms = cc;
+ conToken = cToken;
+ conPacket = cPacket;
+ threadName = "MQTT Con: " + getClient().getClientId();
+ }
+
+ void start() {
+ if (executorService == null) {
+ new Thread(this).start();
+ } else {
+ executorService.execute(this);
+ }
+ }
+
+ public void run() {
+ Thread.currentThread().setName(threadName);
+ final String methodName = "connectBG:run";
+ MqttException mqttEx = null;
+ // @TRACE 220=>
+ log.fine(CLASS_NAME, methodName, "220");
+
+ try {
+ // Reset an exception on existing delivery tokens.
+ // This will have been set if disconnect occurred before delivery was
+ // fully processed.
+ MqttToken[] toks = tokenStore.getOutstandingDelTokens();
+ for (MqttToken tok : toks) {
+ tok.internalTok.setException(null);
+ }
+
+ // Save the connect token in tokenStore as failure can occur before send
+ tokenStore.saveToken(conToken, conPacket);
+
+ // Connect to the server at the network level e.g. TCP socket and then
+ // start the background processing threads before sending the connect
+ // packet.
+ NetworkModule networkModule = networkModules[networkModuleIndex];
+ networkModule.start();
+ receiver = new CommsReceiver(clientComms, clientState, tokenStore, networkModule.getInputStream());
+ receiver.start("MQTT Rec: " + getClient().getClientId(), executorService);
+ sender = new CommsSender(clientComms, clientState, tokenStore, networkModule.getOutputStream());
+ sender.start("MQTT Snd: " + getClient().getClientId(), executorService);
+ callback.start("MQTT Call: " + getClient().getClientId(), executorService);
+ internalSend(conPacket, conToken);
+ } catch (MqttException ex) {
+ // @TRACE 212=connect failed: unexpected exception
+ log.fine(CLASS_NAME, methodName, "212", null, ex);
+ mqttEx = ex;
+ } catch (Exception ex) {
+ // @TRACE 209=connect failed: unexpected exception
+ log.fine(CLASS_NAME, methodName, "209", null, ex);
+ mqttEx = ExceptionHelper.createMqttException(ex);
+ }
+
+ if (mqttEx != null) {
+ shutdownConnection(conToken, mqttEx, null);
+ }
+ }
+ }
+
+ // Kick off the disconnect processing in the background so that it does not
+ // block. For instance
+ // the quiesce
+ private class DisconnectBG implements Runnable {
+ MqttDisconnect disconnect;
+ long quiesceTimeout;
+ MqttToken token;
+ private String threadName;
+
+ DisconnectBG(MqttDisconnect disconnect, long quiesceTimeout, MqttToken token, ExecutorService executorService) {
+ this.disconnect = disconnect;
+ this.quiesceTimeout = quiesceTimeout;
+ this.token = token;
+ }
+
+ void start() {
+ threadName = "MQTT Disc: "+getClient().getClientId();
+ if (executorService == null) {
+ new Thread(this).start();
+ } else {
+ executorService.execute(this);
+ }
+ }
+
+ public void run() {
+ Thread.currentThread().setName(threadName);
+ final String methodName = "disconnectBG:run";
+ // @TRACE 221=>
+ log.fine(CLASS_NAME, methodName, "221");
+
+ // Allow current inbound and outbound work to complete
+ clientState.quiesce(quiesceTimeout);
+ try {
+ internalSend(disconnect, token);
+ // do not wait if the sender process is not running
+ if (sender != null && sender.isRunning()) {
+ token.internalTok.waitUntilSent();
+ }
+ }
+ catch (MqttException ex) {
+ }
+ finally {
+ token.internalTok.markComplete(null, null);
+ if (sender == null || !sender.isRunning()) {
+ // if the sender process is not running
+ token.internalTok.notifyComplete();
+ }
+ shutdownConnection(token, null, null);
+ }
+ }
+ }
+
+ /*
+ * Check and send a ping if needed and check for ping timeout. Need to send a
+ * ping if nothing has been sent or received in the last keepalive interval.
+ */
+ public MqttToken checkForActivity() {
+ return this.checkForActivity(null);
+ }
+
+ /*
+ * Check and send a ping if needed and check for ping timeout. Need to send a
+ * ping if nothing has been sent or received in the last keepalive interval.
+ * Passes an IMqttActionListener to ClientState.checkForActivity so that the
+ * callbacks are attached as soon as the token is created (Bug 473928)
+ */
+ public MqttToken checkForActivity(MqttActionListener pingCallback) {
+ MqttToken token = null;
+ try {
+ token = clientState.checkForActivity(pingCallback);
+ } catch (MqttException e) {
+ handleRunException(e);
+ } catch (Exception e) {
+ handleRunException(e);
+ }
+ return token;
+ }
+
+ private void handleRunException(Exception ex) {
+ final String methodName = "handleRunException";
+ // @TRACE 804=exception
+ log.fine(CLASS_NAME, methodName, "804", null, ex);
+ MqttException mex;
+ if (!(ex instanceof MqttException)) {
+ mex = new MqttException(MqttClientException.REASON_CODE_CONNECTION_LOST, ex);
+ } else {
+ mex = (MqttException) ex;
+ }
+
+ shutdownConnection(null, mex, null);
+ }
+
+ /**
+ * When Automatic reconnect is enabled, we want ClientComs to enter the
+ * 'resting' state if disconnected. This will allow us to publish messages
+ *
+ * @param resting
+ * if true, resting is enabled
+ */
+ public void setRestingState(boolean resting) {
+ this.resting = resting;
+ }
+
+ public void setDisconnectedMessageBuffer(DisconnectedMessageBuffer disconnectedMessageBuffer) {
+ this.disconnectedMessageBuffer = disconnectedMessageBuffer;
+ }
+
+ public int getBufferedMessageCount() {
+ return this.disconnectedMessageBuffer.getMessageCount();
+ }
+
+ public MqttMessage getBufferedMessage(int bufferIndex) {
+ MqttPublish send = (MqttPublish) this.disconnectedMessageBuffer.getMessage(bufferIndex).getMessage();
+ return send.getMessage();
+ }
+
+ public void deleteBufferedMessage(int bufferIndex) {
+ this.disconnectedMessageBuffer.deleteMessage(bufferIndex);
+ }
+
+ /**
+ * When the client automatically reconnects, we want to send all messages from
+ * the buffer first before allowing the user to send any messages
+ */
+ public void notifyReconnect() {
+ final String methodName = "notifyReconnect";
+ if (disconnectedMessageBuffer != null) {
+ // @TRACE 509=Client Reconnected, Offline Buffer Available. Sending Buffered
+ // Messages.
+ log.fine(CLASS_NAME, methodName, "509");
+
+ disconnectedMessageBuffer.setPublishCallback(new ReconnectDisconnectedBufferCallback(methodName));
+ if (executorService == null) {
+ new Thread(disconnectedMessageBuffer).start();
+ } else {
+ executorService.execute(disconnectedMessageBuffer);
+ }
+ }
+ }
+
+ class ReconnectDisconnectedBufferCallback implements IDisconnectedBufferCallback {
+
+ final String methodName;
+
+ ReconnectDisconnectedBufferCallback(String methodName) {
+ this.methodName = methodName;
+ }
+
+ public void publishBufferedMessage(BufferedMessage bufferedMessage) throws MqttException {
+ if (isConnected()) {
+ // @TRACE 510=Publising Buffered message message={0}
+ log.fine(CLASS_NAME, methodName, "510", new Object[] { bufferedMessage.getMessage().getKey() });
+ internalSend(bufferedMessage.getMessage(), bufferedMessage.getToken());
+ // Delete from persistence if in there
+ clientState.unPersistBufferedMessage(bufferedMessage.getMessage());
+ } else {
+ // @TRACE 208=failed: not connected
+ log.fine(CLASS_NAME, methodName, "208");
+ throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_CLIENT_NOT_CONNECTED);
+ }
+ }
+ }
+
+ public int getActualInFlight() {
+ return this.clientState.getActualInFlight();
+ }
+
+ public boolean doesSubscriptionIdentifierExist(int subscriptionIdentifier) {
+ return this.callback.doesSubscriptionIdentifierExist(subscriptionIdentifier);
+
+ }
+
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/internal/ClientState.java b/src/main/java/org/eclipse/paho/mqttv5/client/internal/ClientState.java
new file mode 100644
index 0000000..216d62c
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/internal/ClientState.java
@@ -0,0 +1,1625 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2018 IBM Corp and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ * Ian Craggs - fix duplicate message id (Bug 466853)
+ * Ian Craggs - ack control (bug 472172)
+ * James Sutton - Ping Callback (bug 473928)
+ * Ian Craggs - fix for NPE bug 470718
+ * James Sutton - Automatic Reconnect & Offline Buffering
+ * Jens Reimann - Fix issue #370
+ * James Sutton - Mqttv5 - Outgoing Topic Aliases
+ */
+package org.eclipse.paho.mqttv5.client.internal;
+
+import java.io.EOFException;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.Vector;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.paho.mqttv5.client.MqttActionListener;
+import org.eclipse.paho.mqttv5.client.MqttClientException;
+import org.eclipse.paho.mqttv5.client.MqttClientPersistence;
+import org.eclipse.paho.mqttv5.client.MqttPingSender;
+import org.eclipse.paho.mqttv5.client.MqttToken;
+import org.eclipse.paho.mqttv5.client.logging.Logger;
+import org.eclipse.paho.mqttv5.client.logging.LoggerFactory;
+import org.eclipse.paho.mqttv5.common.MqttException;
+import org.eclipse.paho.mqttv5.common.MqttMessage;
+import org.eclipse.paho.mqttv5.common.MqttPersistable;
+import org.eclipse.paho.mqttv5.common.MqttPersistenceException;
+import org.eclipse.paho.mqttv5.common.packet.MqttAck;
+import org.eclipse.paho.mqttv5.common.packet.MqttAuth;
+import org.eclipse.paho.mqttv5.common.packet.MqttConnAck;
+import org.eclipse.paho.mqttv5.common.packet.MqttConnect;
+import org.eclipse.paho.mqttv5.common.packet.MqttPingReq;
+import org.eclipse.paho.mqttv5.common.packet.MqttPingResp;
+import org.eclipse.paho.mqttv5.common.packet.MqttProperties;
+import org.eclipse.paho.mqttv5.common.packet.MqttPubAck;
+import org.eclipse.paho.mqttv5.common.packet.MqttPubComp;
+import org.eclipse.paho.mqttv5.common.packet.MqttPubRec;
+import org.eclipse.paho.mqttv5.common.packet.MqttPubRel;
+import org.eclipse.paho.mqttv5.common.packet.MqttPublish;
+import org.eclipse.paho.mqttv5.common.packet.MqttReturnCode;
+import org.eclipse.paho.mqttv5.common.packet.MqttWireMessage;
+
+/**
+ * The core of the client, which holds the state information for pending and
+ * in-flight messages.
+ *
+ * Messages that have been accepted for delivery are moved between several
+ * objects while being delivered.
+ *
+ * 1) When the client is not running messages are stored in a persistent store
+ * that implements the MqttClientPersistent Interface. The default is
+ * MqttDefaultFilePersistencew which stores messages safely across failures and
+ * system restarts. If no persistence is specified there is a fall back to
+ * MemoryPersistence which will maintain the messages while the Mqtt client is
+ * instantiated.
+ *
+ * 2) When the client or specifically ClientState is instantiated the messages
+ * are read from the persistent store into: - outboundqos2 hashtable if a QoS 2
+ * PUBLISH or PUBREL - outboundqos1 hashtable if a QoS 1 PUBLISH (see
+ * restoreState)
+ *
+ * 3) On Connect, copy messages from the outbound hashtables to the
+ * pendingMessages or pendingFlows vector in messageid order. - Initial message
+ * publish goes onto the pendingmessages buffer. - PUBREL goes onto the
+ * pendingflows buffer (see restoreInflightMessages)
+ *
+ * 4) Sender thread reads messages from the pendingflows and pendingmessages
+ * buffer one at a time. The message is removed from the pendingbuffer but
+ * remains on the outbound* hashtable. The hashtable is the place where the full
+ * set of outstanding messages are stored in memory. (Persistence is only used
+ * at start up)
+ *
+ * 5) Receiver thread - receives wire messages: - if QoS 1 then remove from
+ * persistence and outboundqos1 - if QoS 2 PUBREC send PUBREL. Updating the
+ * outboundqos2 entry with the PUBREL and update persistence. - if QoS 2 PUBCOMP
+ * remove from persistence and outboundqos2
+ *
+ * Notes: because of the multithreaded nature of the client it is vital that any
+ * changes to this class take concurrency into account. For instance as soon as
+ * a flow / message is put on the wire it is possible for the receiving thread
+ * to receive the ack and to be processing the response before the sending side
+ * has finished processing. For instance a connect may be sent, the conack
+ * received before the connect notify send has been processed!
+ *
+ */
+public class ClientState implements MqttState {
+ private static final String CLASS_NAME = ClientState.class.getName();
+ private Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT, CLASS_NAME);
+ private static final String PERSISTENCE_SENT_PREFIX = "s-";
+ private static final String PERSISTENCE_SENT_BUFFERED_PREFIX = "sb-";
+ private static final String PERSISTENCE_CONFIRMED_PREFIX = "sc-";
+ private static final String PERSISTENCE_RECEIVED_PREFIX = "r-";
+
+ private static final int MIN_MSG_ID = 1; // Lowest possible MQTT message ID to use
+ private static final int MAX_MSG_ID = 65535; // Highest possible MQTT message ID to use
+ private int nextMsgId = MIN_MSG_ID - 1; // The next available message ID to use
+ private ConcurrentHashMap inUseMsgIds; // Used to store a set of in-use message IDs
+
+ volatile private Vector pendingMessages;
+ volatile private Vector pendingFlows;
+
+ private CommsTokenStore tokenStore;
+ private ClientComms clientComms = null;
+ private CommsCallback callback = null;
+ //private long keepAlive;
+ private boolean cleanStart;
+ private MqttClientPersistence persistence;
+
+ private int actualInFlight = 0;
+ private int inFlightPubRels = 0;
+
+ private final Object queueLock = new Object();
+ private final Object quiesceLock = new Object();
+ private boolean quiescing = false;
+
+ private long lastOutboundActivity = 0;
+ private long lastInboundActivity = 0;
+ private long lastPing = 0;
+ private MqttWireMessage pingCommand;
+ private final Object pingOutstandingLock = new Object();
+ private int pingOutstanding = 0;
+
+ private boolean connected = false;
+
+ private ConcurrentHashMap outboundQoS2 = null;
+ private ConcurrentHashMap outboundQoS1 = null;
+ private ConcurrentHashMap outboundQoS0 = null;
+ private ConcurrentHashMap inboundQoS2 = null;
+
+ private MqttPingSender pingSender = null;
+
+ // Topic Alias Maps
+ private Hashtable outgoingTopicAliases;
+ private Hashtable incomingTopicAliases;
+
+ private MqttConnectionState mqttConnection;
+
+ protected ClientState(MqttClientPersistence persistence, CommsTokenStore tokenStore, CommsCallback callback,
+ ClientComms clientComms, MqttPingSender pingSender, MqttConnectionState mqttConnection)
+ throws MqttException {
+
+ log.setResourceName(clientComms.getClient().getClientId());
+ log.finer(CLASS_NAME, "", "");
+
+ inUseMsgIds = new ConcurrentHashMap<>();
+ pendingFlows = new Vector();
+ pendingMessages = new Vector(mqttConnection.getReceiveMaximum());
+ outboundQoS2 = new ConcurrentHashMap<>();
+ outboundQoS1 = new ConcurrentHashMap<>();
+ outboundQoS0 = new ConcurrentHashMap<>();
+ inboundQoS2 = new ConcurrentHashMap<>();
+ pingCommand = new MqttPingReq();
+ inFlightPubRels = 0;
+ actualInFlight = 0;
+ this.outgoingTopicAliases = new Hashtable();
+ this.incomingTopicAliases = new Hashtable();
+
+ this.persistence = persistence;
+ this.callback = callback;
+ this.tokenStore = tokenStore;
+ this.clientComms = clientComms;
+ this.pingSender = pingSender;
+ this.mqttConnection = mqttConnection;
+
+ restoreState();
+ }
+
+ protected void setCleanStart(boolean cleanStart) {
+ this.cleanStart = cleanStart;
+ }
+
+ protected boolean getCleanStart() {
+ return this.cleanStart;
+ }
+
+ private String getSendPersistenceKey(MqttWireMessage message) {
+ return PERSISTENCE_SENT_PREFIX + message.getMessageId();
+ }
+
+ private String getSendConfirmPersistenceKey(MqttWireMessage message) {
+ return PERSISTENCE_CONFIRMED_PREFIX + message.getMessageId();
+ }
+
+ private String getReceivedPersistenceKey(MqttWireMessage message) {
+ return PERSISTENCE_RECEIVED_PREFIX + message.getMessageId();
+ }
+
+ private String getReceivedPersistenceKey(int messageId) {
+ return PERSISTENCE_RECEIVED_PREFIX + messageId;
+ }
+
+ private String getSendBufferedPersistenceKey(MqttWireMessage message) {
+ return PERSISTENCE_SENT_BUFFERED_PREFIX + message.getMessageId();
+ }
+
+ protected void clearState() throws MqttException {
+ final String methodName = "clearState";
+ // @TRACE 603=clearState
+ log.fine(CLASS_NAME, methodName, ">");
+
+ persistence.clear();
+ inUseMsgIds.clear();
+ pendingMessages.clear();
+ pendingFlows.clear();
+ outboundQoS2.clear();
+ outboundQoS1.clear();
+ outboundQoS0.clear();
+ inboundQoS2.clear();
+ tokenStore.clear();
+ outgoingTopicAliases.clear();
+ incomingTopicAliases.clear();
+ }
+
+ protected void clearConnectionState() throws MqttException {
+ final String methodName = "clearConnectionState";
+ // @TRACE=665=Clearing Connection State (Topic Aliases)
+ log.fine(CLASS_NAME, methodName, "665");
+ outgoingTopicAliases.clear();
+ incomingTopicAliases.clear();
+
+ }
+
+ private MqttWireMessage restoreMessage(String key, MqttPersistable persistable) throws MqttException {
+ final String methodName = "restoreMessage";
+ MqttWireMessage message = null;
+
+ try {
+ message = MqttWireMessage.createWireMessage(persistable);
+ } catch (MqttException ex) {
+ // @TRACE 602=key={0} exception
+ log.fine(CLASS_NAME, methodName, "602", new Object[] { key }, ex);
+ if (ex.getCause() instanceof EOFException) {
+ // Premature end-of-file means that the message is corrupted
+ if (key != null) {
+ persistence.remove(key);
+ }
+ } else {
+ throw ex;
+ }
+ }
+ // @TRACE 601=key={0} message={1}
+ log.fine(CLASS_NAME, methodName, "601", new Object[] { key, message });
+ return message;
+ }
+
+ /**
+ * Inserts a new message to the list, ensuring that list is ordered from lowest
+ * to highest in terms of the message id's.
+ *
+ * @param list
+ * the list to insert the message into
+ * @param newMsg
+ * the message to insert into the list
+ */
+ private void insertInOrder(Vector list, MqttWireMessage newMsg) {
+ int newMsgId = newMsg.getMessageId();
+ for (int i = 0; i < list.size(); i++) {
+ MqttWireMessage otherMsg = (MqttWireMessage) list.elementAt(i);
+ int otherMsgId = otherMsg.getMessageId();
+ if (otherMsgId > newMsgId) {
+ list.insertElementAt(newMsg, i);
+ return;
+ }
+ }
+ list.addElement(newMsg);
+ }
+
+ /**
+ * Produces a new list with the messages properly ordered according to their
+ * message id's.
+ *
+ * @param list
+ * the list containing the messages to produce a new reordered list
+ * for - this will not be modified or replaced, i.e., be read-only to
+ * this method
+ * @return a new reordered list
+ */
+ private Vector reOrder(Vector list) {
+
+ // here up the new list
+ Vector newList = new Vector();
+
+ if (list.size() == 0) {
+ return newList; // nothing to reorder
+ }
+
+ int previousMsgId = 0;
+ int largestGap = 0;
+ int largestGapMsgIdPosInList = 0;
+ for (int i = 0; i < list.size(); i++) {
+ int currentMsgId = ((MqttWireMessage) list.elementAt(i)).getMessageId();
+ if (currentMsgId - previousMsgId > largestGap) {
+ largestGap = currentMsgId - previousMsgId;
+ largestGapMsgIdPosInList = i;
+ }
+ previousMsgId = currentMsgId;
+ }
+ int lowestMsgId = ((MqttWireMessage) list.elementAt(0)).getMessageId();
+ int highestMsgId = previousMsgId; // last in the sorted list
+
+ // we need to check that the gap after highest msg id to the lowest msg id is
+ // not beaten
+ if (MAX_MSG_ID - highestMsgId + lowestMsgId > largestGap) {
+ largestGapMsgIdPosInList = 0;
+ }
+
+ // starting message has been located, let's start from this point on
+ for (int i = largestGapMsgIdPosInList; i < list.size(); i++) {
+ newList.addElement(list.elementAt(i));
+ }
+
+ // and any wrapping back to the beginning
+ for (int i = 0; i < largestGapMsgIdPosInList; i++) {
+ newList.addElement(list.elementAt(i));
+ }
+
+ return newList;
+ }
+
+ /**
+ * Restores the state information from persistence.
+ *
+ * @throws MqttException
+ * if an exception occurs whilst restoring state
+ */
+ protected void restoreState() throws MqttException {
+ final String methodName = "restoreState";
+ Enumeration messageKeys = persistence.keys();
+ MqttPersistable persistable;
+ String key;
+ int highestMsgId = nextMsgId;
+ Vector orphanedPubRels = new Vector();
+ // @TRACE 600=>
+ log.fine(CLASS_NAME, methodName, "600");
+
+ while (messageKeys.hasMoreElements()) {
+ key = (String) messageKeys.nextElement();
+ persistable = persistence.get(key);
+ MqttWireMessage message = restoreMessage(key, persistable);
+ if (message != null) {
+ if (key.startsWith(PERSISTENCE_RECEIVED_PREFIX)) {
+ // @TRACE 604=inbound QoS 2 publish key={0} message={1}
+ log.fine(CLASS_NAME, methodName, "604", new Object[] { key, message });
+
+ // The inbound messages that we have persisted will be QoS 2
+ inboundQoS2.put(Integer.valueOf(message.getMessageId()), message);
+ } else if (key.startsWith(PERSISTENCE_SENT_PREFIX)) {
+ MqttPublish sendMessage = (MqttPublish) message;
+ highestMsgId = Math.max(sendMessage.getMessageId(), highestMsgId);
+ if (persistence.containsKey(getSendConfirmPersistenceKey(sendMessage))) {
+ MqttPersistable persistedConfirm = persistence.get(getSendConfirmPersistenceKey(sendMessage));
+ // QoS 2, and CONFIRM has already been sent...
+ // NO DUP flag is allowed for 3.1.1 spec while it's not clear for 3.1 spec
+ // So we just remove DUP
+ MqttPubRel confirmMessage = (MqttPubRel) restoreMessage(key, persistedConfirm);
+ if (confirmMessage != null) {
+ // confirmMessage.setDuplicate(true); // REMOVED
+ // @TRACE 605=outbound QoS 2 pubrel key={0} message={1}
+ log.fine(CLASS_NAME, methodName, "605", new Object[] { key, message });
+
+ outboundQoS2.put(Integer.valueOf(confirmMessage.getMessageId()), confirmMessage);
+ } else {
+ // @TRACE 606=outbound QoS 2 completed key={0} message={1}
+ log.fine(CLASS_NAME, methodName, "606", new Object[] { key, message });
+ }
+ } else {
+ // QoS 1 or 2, with no CONFIRM sent...
+ // Put the SEND to the list of pending messages, ensuring message ID ordering...
+ sendMessage.setDuplicate(true);
+ if (sendMessage.getMessage().getQos() == 2) {
+ // @TRACE 607=outbound QoS 2 publish key={0} message={1}
+ log.fine(CLASS_NAME, methodName, "607", new Object[] { key, message });
+
+ outboundQoS2.put(Integer.valueOf(sendMessage.getMessageId()), sendMessage);
+ } else {
+ // @TRACE 608=outbound QoS 1 publish key={0} message={1}
+ log.fine(CLASS_NAME, methodName, "608", new Object[] { key, message });
+
+ outboundQoS1.put(Integer.valueOf(sendMessage.getMessageId()), sendMessage);
+ }
+ }
+ MqttToken tok = tokenStore.restoreToken(sendMessage);
+ tok.internalTok.setClient(clientComms.getClient());
+ inUseMsgIds.put(Integer.valueOf(sendMessage.getMessageId()),
+ Integer.valueOf(sendMessage.getMessageId()));
+ } else if (key.startsWith(PERSISTENCE_SENT_BUFFERED_PREFIX)) {
+
+ // Buffered outgoing messages that have not yet been sent at all
+ MqttPublish sendMessage = (MqttPublish) message;
+ highestMsgId = Math.max(sendMessage.getMessageId(), highestMsgId);
+ if (sendMessage.getMessage().getQos() == 2) {
+ // @TRACE 607=outbound QoS 2 publish key={0} message={1}
+ log.fine(CLASS_NAME, methodName, "607", new Object[] { key, message });
+ outboundQoS2.put(Integer.valueOf(sendMessage.getMessageId()), sendMessage);
+ } else if (sendMessage.getMessage().getQos() == 1) {
+ // @TRACE 608=outbound QoS 1 publish key={0} message={1}
+ log.fine(CLASS_NAME, methodName, "608", new Object[] { key, message });
+
+ outboundQoS1.put(Integer.valueOf(sendMessage.getMessageId()), sendMessage);
+
+ } else {
+ // @TRACE 511=outbound QoS 0 publish key={0} message={1}
+ log.fine(CLASS_NAME, methodName, "511", new Object[] { key, message });
+ outboundQoS0.put(Integer.valueOf(sendMessage.getMessageId()), sendMessage);
+ // Because there is no Puback, we have to trust that this is enough to send the
+ // message
+ persistence.remove(key);
+
+ }
+
+ MqttToken tok = tokenStore.restoreToken(sendMessage);
+ tok.internalTok.setClient(clientComms.getClient());
+ inUseMsgIds.put(Integer.valueOf(sendMessage.getMessageId()),
+ Integer.valueOf(sendMessage.getMessageId()));
+
+ } else if (key.startsWith(PERSISTENCE_CONFIRMED_PREFIX)) {
+ MqttPubRel pubRelMessage = (MqttPubRel) message;
+ if (!persistence.containsKey(getSendPersistenceKey(pubRelMessage))) {
+ orphanedPubRels.addElement(key);
+ }
+ }
+ }
+ }
+
+ messageKeys = orphanedPubRels.elements();
+ while (messageKeys.hasMoreElements()) {
+ key = (String) messageKeys.nextElement();
+ // @TRACE 609=removing orphaned pubrel key={0}
+ log.fine(CLASS_NAME, methodName, "609", new Object[] { key });
+
+ persistence.remove(key);
+ }
+
+ nextMsgId = highestMsgId;
+ }
+
+ private void restoreInflightMessages() {
+ final String methodName = "restoreInflightMessages";
+ pendingMessages = new Vector(this.mqttConnection.getReceiveMaximum());
+ pendingFlows = new Vector();
+
+ Enumeration keys = outboundQoS2.keys();
+ while (keys.hasMoreElements()) {
+ Integer key = keys.nextElement();
+ MqttWireMessage msg = (MqttWireMessage) outboundQoS2.get(key);
+ if (msg instanceof MqttPublish) {
+ // @TRACE 610=QoS 2 publish key={0}
+ log.fine(CLASS_NAME, methodName, "610", new Object[] { key });
+ // set DUP flag only for PUBLISH, but NOT for PUBREL (spec 3.1.1)
+ msg.setDuplicate(true);
+ insertInOrder(pendingMessages, (MqttPublish) msg);
+ } else if (msg instanceof MqttPubRel) {
+ // @TRACE 611=QoS 2 pubrel key={0}
+ log.fine(CLASS_NAME, methodName, "611", new Object[] { key });
+
+ insertInOrder(pendingFlows, (MqttPubRel) msg);
+ }
+ }
+ keys = outboundQoS1.keys();
+ while (keys.hasMoreElements()) {
+ Object key = keys.nextElement();
+ MqttPublish msg = (MqttPublish) outboundQoS1.get(key);
+ msg.setDuplicate(true);
+ // @TRACE 612=QoS 1 publish key={0}
+ log.fine(CLASS_NAME, methodName, "612", new Object[] { key });
+
+ insertInOrder(pendingMessages, msg);
+ }
+ keys = outboundQoS0.keys();
+ while (keys.hasMoreElements()) {
+ Object key = keys.nextElement();
+ MqttPublish msg = (MqttPublish) outboundQoS0.get(key);
+ // @TRACE 512=QoS 0 publish key={0}
+ log.fine(CLASS_NAME, methodName, "512", new Object[] { key });
+ insertInOrder(pendingMessages, msg);
+
+ }
+
+ this.pendingFlows = reOrder(pendingFlows);
+ this.pendingMessages = reOrder(pendingMessages);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.internal.MqttState#send(org.eclipse.paho.
+ * client.mqttv3.internal.wire.MqttWireMessage,
+ * org.eclipse.paho.mqttv5.client.MqttToken)
+ */
+ @Override
+ public void send(MqttWireMessage message, MqttToken token) throws MqttException {
+ final String methodName = "send";
+ // Set Message ID if required
+ if (message.isMessageIdRequired() && (message.getMessageId() == 0)) {
+ message.setMessageId(getNextMessageId());
+ }
+ // Set Topic Alias if required
+ if (message instanceof MqttPublish && ((MqttPublish) message).getTopicName() != null
+ && this.mqttConnection != null && this.mqttConnection.getOutgoingTopicAliasMaximum() > 0) {
+ String topic = ((MqttPublish) message).getTopicName();
+ if (outgoingTopicAliases.containsKey(topic)) {
+ // Existing Topic Alias, Assign it and remove the topic string
+ ((MqttPublish) message).getProperties().setTopicAlias(outgoingTopicAliases.get(topic));
+ ((MqttPublish) message).setTopicName(null);
+ } else {
+ int nextOutgoingTopicAlias = this.mqttConnection.getNextOutgoingTopicAlias();
+ if (nextOutgoingTopicAlias <= this.mqttConnection.getOutgoingTopicAliasMaximum()) {
+ // Create a new Topic Alias and increment the counter
+ ((MqttPublish) message).getProperties().setTopicAlias(nextOutgoingTopicAlias);
+ outgoingTopicAliases.put(((MqttPublish) message).getTopicName(), nextOutgoingTopicAlias);
+ }
+ }
+ }
+
+ if (token != null) {
+ try {
+ token.internalTok.setMessageID(message.getMessageId());
+ } catch (Exception e) {
+ }
+ }
+
+ if (message instanceof MqttPublish) {
+ synchronized (queueLock) {
+ if (actualInFlight >= this.mqttConnection.getReceiveMaximum()) {
+ // @TRACE 613= sending {0} msgs at max inflight window
+ log.fine(CLASS_NAME, methodName, "613", new Object[] { Integer.valueOf(actualInFlight) });
+
+ throw new MqttException(MqttClientException.REASON_CODE_MAX_INFLIGHT);
+ }
+
+ MqttMessage innerMessage = ((MqttPublish) message).getMessage();
+ // @TRACE 628=pending publish key={0} qos={1} message={2}
+ log.fine(CLASS_NAME, methodName, "628", new Object[] { Integer.valueOf(message.getMessageId()),
+ Integer.valueOf(innerMessage.getQos()), message });
+
+ switch (innerMessage.getQos()) {
+ case 2:
+ outboundQoS2.put(Integer.valueOf(message.getMessageId()), message);
+ persistence.put(getSendPersistenceKey(message), (MqttPublish) message);
+ break;
+ case 1:
+ outboundQoS1.put(Integer.valueOf(message.getMessageId()), message);
+ persistence.put(getSendPersistenceKey(message), (MqttPublish) message);
+ break;
+ }
+ tokenStore.saveToken(token, message);
+ pendingMessages.addElement(message);
+ queueLock.notifyAll();
+ }
+ } else {
+ // @TRACE 615=pending send key={0} message {1}
+ log.fine(CLASS_NAME, methodName, "615", new Object[] { Integer.valueOf(message.getMessageId()), message });
+
+ if (message instanceof MqttConnect) {
+ synchronized (queueLock) {
+ // Add the connect action at the head of the pending queue ensuring it jumps
+ // ahead of any of other pending actions.
+ tokenStore.saveToken(token, message);
+ pendingFlows.insertElementAt(message, 0);
+ queueLock.notifyAll();
+ }
+ } else {
+ if (message instanceof MqttPingReq) {
+ this.pingCommand = message;
+ } else if (message instanceof MqttPubRel) {
+ outboundQoS2.put(Integer.valueOf(message.getMessageId()), message);
+ persistence.put(getSendConfirmPersistenceKey(message), (MqttPubRel) message);
+ } else if (message instanceof MqttPubComp) {
+ persistence.remove(getReceivedPersistenceKey(message));
+ }
+
+ synchronized (queueLock) {
+ if (!(message instanceof MqttAck)) {
+ tokenStore.saveToken(token, message);
+ }
+ pendingFlows.addElement(message);
+ queueLock.notifyAll();
+ }
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.internal.MqttState#persistBufferedMessage(org.
+ * eclipse.paho.client.mqttv3.internal.wire.MqttWireMessage)
+ */
+ @Override
+ public void persistBufferedMessage(MqttWireMessage message) {
+ final String methodName = "persistBufferedMessage";
+ String key = getSendBufferedPersistenceKey(message);
+
+ // Because the client will have disconnected, we will want to re-open
+ // persistence
+ try {
+ message.setMessageId(getNextMessageId());
+ key = getSendBufferedPersistenceKey(message);
+ try {
+ persistence.put(key, (MqttPublish) message);
+ } catch (MqttPersistenceException mpe) {
+ // @TRACE 515=Could not Persist, attempting to Re-Open Persistence Store
+ log.fine(CLASS_NAME, methodName, "515");
+ persistence.open(this.clientComms.getClient().getClientId());
+ persistence.put(key, (MqttPublish) message);
+ }
+ // @TRACE 513=Persisted Buffered Message key={0}
+ log.fine(CLASS_NAME, methodName, "513", new Object[] { key });
+ } catch (MqttException ex) {
+ // @TRACE 514=Failed to persist buffered message key={0}
+ log.warning(CLASS_NAME, methodName, "513", new Object[] { key });
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.internal.MqttState#unPersistBufferedMessage(
+ * org.eclipse.paho.mqttv5.client.internal.wire.MqttWireMessage)
+ */
+ @Override
+ public void unPersistBufferedMessage(MqttWireMessage message) {
+ final String methodName = "unPersistBufferedMessage";
+ try {
+ // @TRACE 517=Un-Persisting Buffered message key={0}
+ log.fine(CLASS_NAME, methodName, "517", new Object[] { message.getKey() });
+ persistence.remove(getSendBufferedPersistenceKey(message));
+ } catch (MqttPersistenceException mpe) {
+ // @TRACE 518=Failed to Un-Persist Buffered message key={0}
+ log.fine(CLASS_NAME, methodName, "518", new Object[] { message.getKey() });
+ }
+
+ }
+
+ /**
+ * This removes the MqttSend message from the outbound queue and persistence.
+ *
+ * @param message
+ * the {@link MqttPublish} message to be removed
+ * @throws MqttPersistenceException
+ * if an exception occurs whilst removing the message
+ */
+ protected void undo(MqttPublish message) throws MqttPersistenceException {
+ final String methodName = "undo";
+ synchronized (queueLock) {
+ // @TRACE 618=key={0} QoS={1}
+ log.fine(CLASS_NAME, methodName, "618", new Object[] { Integer.valueOf(message.getMessageId()),
+ Integer.valueOf(message.getMessage().getQos()) });
+
+ if (message.getMessage().getQos() == 1) {
+ outboundQoS1.remove(Integer.valueOf(message.getMessageId()));
+ } else {
+ outboundQoS2.remove(Integer.valueOf(message.getMessageId()));
+ }
+ pendingMessages.removeElement(message);
+ persistence.remove(getSendPersistenceKey(message));
+ tokenStore.removeToken(message);
+ if (message.getMessage().getQos() > 0) {
+ // Free this message Id so it can be used again
+ releaseMessageId(message.getMessageId());
+ // Set the messageId to 0 so if it's ever retried, it will get a new messageId
+ message.setMessageId(0);
+ }
+
+ checkQuiesceLock();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.internal.MqttState#checkForActivity(org.
+ * eclipse.paho.client.mqttv3.IMqttActionListener)
+ */
+ @Override
+ public MqttToken checkForActivity(MqttActionListener pingCallback) throws MqttException {
+ final String methodName = "checkForActivity";
+ // @TRACE 616=checkForActivity entered
+ log.fine(CLASS_NAME, methodName, "616", new Object[] {});
+
+ synchronized (quiesceLock) {
+ // ref bug: https://bugs.eclipse.org/bugs/show_bug.cgi?id=440698
+ // No ping while quiescing
+ if (quiescing) {
+ return null;
+ }
+ }
+
+ MqttToken token = null;
+
+ long nextPingTime, keepAlive = TimeUnit.MILLISECONDS.toNanos(this.mqttConnection.getKeepAlive());
+
+ if (connected && this.mqttConnection.getKeepAlive() > 0) {
+ long time = System.nanoTime();
+ // Reduce schedule frequency since System.currentTimeMillis is no accurate, add
+ // a buffer (This might not be needed since we moved to nanoTime)
+ // It is 1/10 in minimum keepalive unit.
+ int delta = 100000;
+
+ // ref bug: https://bugs.eclipse.org/bugs/show_bug.cgi?id=446663
+ synchronized (pingOutstandingLock) {
+
+ // Is the broker connection lost because the broker did not reply to my ping?
+ if (pingOutstanding > 0 && (time - lastInboundActivity >= keepAlive + delta)) {
+ // lastInboundActivity will be updated once receiving is done.
+ // Add a delta, since the timer and System.currentTimeMillis() is not accurate.
+ // (This might not be needed since we moved to nanoTime)
+ // A ping is outstanding but no packet has been received in KA so connection is
+ // deemed broken
+ // @TRACE 619=Timed out as no activity, keepAlive={0} lastOutboundActivity={1}
+ // lastInboundActivity={2} time={3} lastPing={4}
+ log.severe(CLASS_NAME, methodName, "619",
+ new Object[] { Long.valueOf(keepAlive), Long.valueOf(lastOutboundActivity),
+ Long.valueOf(lastInboundActivity), Long.valueOf(time), Long.valueOf(lastPing) });
+
+ // A ping has already been sent. At this point, assume that the
+ // broker has hung and the TCP layer hasn't noticed.
+ throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_CLIENT_TIMEOUT);
+ }
+
+ // Is the broker connection lost because I could not get any successful write
+ // for 2 keepAlive intervals?
+ if (pingOutstanding == 0 && (time - lastOutboundActivity >= 2 * keepAlive)) {
+
+ // I am probably blocked on a write operations as I should have been able to
+ // write at least a ping message
+ log.severe(CLASS_NAME, methodName, "642",
+ new Object[] { Long.valueOf(keepAlive), Long.valueOf(lastOutboundActivity),
+ Long.valueOf(lastInboundActivity), Long.valueOf(time), Long.valueOf(lastPing) });
+
+ // A ping has not been sent but I am not progressing on the current write
+ // operation.
+ // At this point, assume that the broker has hung and the TCP layer hasn't
+ // noticed.
+ throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_WRITE_TIMEOUT);
+ }
+
+ // 1. Is a ping required by the client to verify whether the broker is down?
+ // Condition: ((pingOutstanding == 0 && (time - lastInboundActivity >= keepAlive
+ // + delta)))
+ // In this case only one ping is sent. If not confirmed, client will assume a
+ // lost connection to the broker.
+ // 2. Is a ping required by the broker to keep the client alive?
+ // Condition: (time - lastOutboundActivity >= keepAlive - delta)
+ // In this case more than one ping outstanding may be necessary.
+ // This would be the case when receiving a large message;
+ // the broker needs to keep receiving a regular ping even if the ping response
+ // are queued after the long message
+ // If lacking to do so, the broker will consider my connection lost and cut my
+ // socket.
+ if ((pingOutstanding == 0 && (time - lastInboundActivity >= keepAlive - delta))
+ || (time - lastOutboundActivity >= keepAlive - delta)) {
+
+ // @TRACE 620=ping needed. keepAlive={0} lastOutboundActivity={1}
+ // lastInboundActivity={2}
+ log.fine(CLASS_NAME, methodName, "620", new Object[] { Long.valueOf(keepAlive),
+ Long.valueOf(lastOutboundActivity), Long.valueOf(lastInboundActivity) });
+
+ // pingOutstanding++; // it will be set after the ping has been written on the
+ // wire
+ // lastPing = time; // it will be set after the ping has been written on the
+ // wire
+ token = new MqttToken(clientComms.getClient().getClientId());
+ if (pingCallback != null) {
+ token.setActionCallback(pingCallback);
+ }
+ tokenStore.saveToken(token, pingCommand);
+ pendingFlows.insertElementAt(pingCommand, 0);
+
+ nextPingTime = keepAlive;
+
+ // Wake sender thread since it may be in wait state (in ClientState.get())
+ notifyQueueLock();
+ } else {
+ log.fine(CLASS_NAME, methodName, "634", null);
+ nextPingTime = Math.max(1, keepAlive - (time - lastOutboundActivity));
+ }
+ }
+ // @TRACE 624=Schedule next ping at {0}
+ log.fine(CLASS_NAME, methodName, "624", new Object[] { Long.valueOf(nextPingTime) });
+ pingSender.schedule(TimeUnit.NANOSECONDS.toMillis(nextPingTime));
+ }
+
+ return token;
+ }
+
+ /**
+ * This returns the next piece of work, ie message, for the CommsSender to send
+ * over the network. Calls to this method block until either: - there is a
+ * message to be sent - the keepAlive interval is exceeded, which triggers a
+ * ping message to be returned - {@link ClientState#disconnected(MqttException)}
+ * is called
+ *
+ * @return the next message to send, or null if the client is disconnected
+ * @throws MqttException
+ * if an exception occurs whilst returning the next piece of work
+ */
+ protected MqttWireMessage get() throws MqttException {
+ final String methodName = "get";
+ MqttWireMessage result = null;
+
+ synchronized (queueLock) {
+ while (result == null) {
+
+ // If there is no work wait until there is work.
+ // If the inflight window is full and no flows are pending wait until space is
+ // freed.
+ // In both cases queueLock will be notified.
+ if ((pendingMessages.isEmpty() && pendingFlows.isEmpty())
+ || (pendingFlows.isEmpty() && actualInFlight >= this.mqttConnection.getReceiveMaximum())) {
+ try {
+ // @TRACE 644=wait for new work or for space in the inflight window
+ log.fine(CLASS_NAME, methodName, "644");
+
+ queueLock.wait();
+
+ // @TRACE 647=new work or ping arrived
+ log.fine(CLASS_NAME, methodName, "647");
+ } catch (InterruptedException e) {
+ }
+ }
+
+ // Handle the case where not connected. This should only be the case if:
+ // - in the process of disconnecting / shutting down
+ // - in the process of connecting
+ if (pendingFlows == null || (!connected && (pendingFlows.isEmpty()
+ || !((MqttWireMessage) pendingFlows.elementAt(0) instanceof MqttConnect)))) {
+ // @TRACE 621=no outstanding flows and not connected
+ log.fine(CLASS_NAME, methodName, "621");
+
+ return null;
+ }
+
+ // Check if there is a need to send a ping to keep the session alive.
+ // Note this check is done before processing messages. If not done first
+ // an app that only publishes QoS 0 messages will prevent keepalive processing
+ // from functioning.
+ // checkForActivity(); //Use pinger, don't check here
+
+ // Now process any queued flows or messages
+ if (!pendingFlows.isEmpty()) {
+ // Process the first "flow" in the queue
+ result = (MqttWireMessage) pendingFlows.remove(0);
+ if (result instanceof MqttPubRel) {
+ inFlightPubRels++;
+
+ // @TRACE 617=+1 inflightpubrels={0}
+ log.fine(CLASS_NAME, methodName, "617", new Object[] { Integer.valueOf(inFlightPubRels) });
+ }
+
+ checkQuiesceLock();
+ } else if (!pendingMessages.isEmpty()) {
+
+ // If the inflight window is full then messages are not
+ // processed until the inflight window has space.
+ if (actualInFlight < this.mqttConnection.getReceiveMaximum()) {
+ // The in flight window is not full so process the
+ // first message in the queue
+ result = (MqttWireMessage) pendingMessages.elementAt(0);
+ pendingMessages.removeElementAt(0);
+ actualInFlight++;
+
+ // @TRACE 623=+1 actualInFlight={0}
+ log.fine(CLASS_NAME, methodName, "623", new Object[] { Integer.valueOf(actualInFlight) });
+ } else {
+ // @TRACE 622=inflight window full
+ log.fine(CLASS_NAME, methodName, "622");
+ }
+ }
+ } // end while
+ } // synchronized
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.internal.MqttState#notifySentBytes(int)
+ */
+ @Override
+ public void notifySentBytes(int sentBytesCount) {
+ final String methodName = "notifySentBytes";
+ if (sentBytesCount > 0) {
+ this.lastOutboundActivity = System.nanoTime();
+ }
+ // @TRACE 643=sent bytes count={0}
+ log.fine(CLASS_NAME, methodName, "643", new Object[] { Integer.valueOf(sentBytesCount) });
+ }
+
+ /**
+ * Called by the CommsSender when a message has been sent
+ *
+ * @param message
+ * the {@link MqttWireMessage} to notify
+ */
+ protected void notifySent(MqttWireMessage message) {
+ final String methodName = "notifySent";
+
+ this.lastOutboundActivity = System.nanoTime();
+ // @TRACE 625=key={0}
+ log.fine(CLASS_NAME, methodName, "625", new Object[] { message.getKey() });
+
+ MqttToken token = tokenStore.getToken(message);
+ if (token == null) return;
+ token.internalTok.notifySent();
+ if (message instanceof MqttPingReq) {
+ synchronized (pingOutstandingLock) {
+ long time = System.nanoTime();
+ synchronized (pingOutstandingLock) {
+ lastPing = time;
+ pingOutstanding++;
+ }
+ // @TRACE 635=ping sent. pingOutstanding: {0}
+ log.fine(CLASS_NAME, methodName, "635", new Object[] { Integer.valueOf(pingOutstanding) });
+ }
+ } else if (message instanceof MqttPublish) {
+ if (((MqttPublish) message).getMessage().getQos() == 0) {
+ // once a QoS 0 message is sent we can clean up its records straight away as
+ // we won't be hearing about it again
+ token.internalTok.markComplete(null, null);
+ callback.asyncOperationComplete(token);
+ decrementInFlight();
+ releaseMessageId(message.getMessageId());
+ tokenStore.removeToken(message);
+ checkQuiesceLock();
+ }
+ }
+ }
+
+ private void decrementInFlight() {
+ final String methodName = "decrementInFlight";
+ synchronized (queueLock) {
+ actualInFlight--;
+ // @TRACE 646=-1 actualInFlight={0}
+ log.fine(CLASS_NAME, methodName, "646", new Object[] { Integer.valueOf(actualInFlight) });
+
+ if (!checkQuiesceLock()) {
+ queueLock.notifyAll();
+ }
+ }
+ }
+
+ protected boolean checkQuiesceLock() {
+ final String methodName = "checkQuiesceLock";
+ // if (quiescing && actualInFlight == 0 && pendingFlows.size() == 0 &&
+ // inFlightPubRels == 0 && callback.isQuiesced()) {
+ int tokC = tokenStore.count();
+ if (quiescing && tokC == 0 && pendingFlows.size() == 0 && callback.isQuiesced()) {
+ // @TRACE 626=quiescing={0} actualInFlight={1} pendingFlows={2}
+ // inFlightPubRels={3} callbackQuiesce={4} tokens={5}
+ log.fine(CLASS_NAME, methodName, "626",
+ new Object[] { Boolean.valueOf(quiescing), Integer.valueOf(actualInFlight),
+ Integer.valueOf(pendingFlows.size()), Integer.valueOf(inFlightPubRels),
+ Boolean.valueOf(callback.isQuiesced()), Integer.valueOf(tokC) });
+ synchronized (quiesceLock) {
+ quiesceLock.notifyAll();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.internal.MqttState#notifyReceivedBytes(int)
+ */
+ @Override
+ public void notifyReceivedBytes(int receivedBytesCount) {
+ final String methodName = "notifyReceivedBytes";
+ if (receivedBytesCount > 0) {
+ this.lastInboundActivity = System.nanoTime();
+ }
+ // @TRACE 630=received bytes count={0}
+ log.fine(CLASS_NAME, methodName, "630", new Object[] { Integer.valueOf(receivedBytesCount) });
+ }
+
+ /**
+ * Called by the CommsReceiver when an ack has arrived.
+ *
+ * @param ack
+ * The {@link MqttAck} that has arrived
+ * @throws MqttException
+ * if an exception occurs when sending / notifying
+ */
+ protected void notifyReceivedAck(MqttAck ack) throws MqttException {
+ final String methodName = "notifyReceivedAck";
+ this.lastInboundActivity = System.nanoTime();
+
+ // @TRACE 627=received key={0} message={1}
+ log.fine(CLASS_NAME, methodName, "627", new Object[] { Integer.valueOf(ack.getMessageId()), ack });
+
+ MqttToken token = tokenStore.getToken(ack);
+ MqttException mex = null;
+
+ if (token == null) {
+ // @TRACE 662=no message found for ack id={0}
+ log.fine(CLASS_NAME, methodName, "662", new Object[] { Integer.valueOf(ack.getMessageId()) });
+ } else if (ack instanceof MqttPubRec) {
+ if (((MqttPubRec) ack).getReasonCodes()[0] > MqttReturnCode.RETURN_CODE_UNSPECIFIED_ERROR) {
+ // @TRACE 664=[MQTT-4.3.3-4] - A Reason code greater than 0x80 (128) was
+ // received in an incoming PUBREC id={0} rc={1} message={2}, halting QoS 2 flow.
+ log.severe(CLASS_NAME, methodName, "664",
+ new Object[] { ack.getMessageId(), ack.getReasonCodes()[0], ack.toString() });
+ throw new MqttException(((MqttPubRec) ack).getReasonCodes()[0]);
+ }
+
+ // Update the token with the reason codes
+ updateResult(ack, token, mex);
+
+ // Complete the QoS 2 flow. Unlike all other
+ // flows, QoS is a 2 phase flow. The second phase sends a
+ // PUBREL - the operation is not complete until a PUBCOMP
+ // is received
+ // Currently this client has no need of the properties, so this is left empty.
+ MqttPubRel rel = new MqttPubRel(MqttReturnCode.RETURN_CODE_SUCCESS, ack.getMessageId(),
+ new MqttProperties());
+ this.send(rel, token);
+ } else if (ack instanceof MqttPubAck || ack instanceof MqttPubComp) {
+
+ // QoS 1 & 2 notify users of result before removing from
+ // persistence
+ notifyResult(ack, token, mex);
+ // Do not remove publish / delivery token at this stage
+ // do this when the persistence is removed later
+ } else if (ack instanceof MqttPingResp) {
+
+ synchronized (pingOutstandingLock) {
+ pingOutstanding = Math.max(0, pingOutstanding - 1);
+ notifyResult(ack, token, mex);
+ if (pingOutstanding == 0) {
+ tokenStore.removeToken(ack);
+ }
+ }
+ // @TRACE 636=ping response received. pingOutstanding: {0}
+ log.fine(CLASS_NAME, methodName, "636", new Object[] { Integer.valueOf(pingOutstanding) });
+ } else if (ack instanceof MqttConnAck) {
+
+ int rc = ((MqttConnAck) ack).getReturnCode();
+ if (rc == 0) {
+ synchronized (queueLock) {
+ if (cleanStart) {
+ clearState();
+ // Add the connect token back in so that users can be
+ // notified when connect completes.
+ tokenStore.saveToken(token, ack);
+ }
+ inFlightPubRels = 0;
+ actualInFlight = 0;
+ restoreInflightMessages();
+ connected();
+ }
+ } else {
+
+ mex = ExceptionHelper.createMqttException(rc);
+ throw mex;
+ }
+
+ clientComms.connectComplete((MqttConnAck) ack, mex);
+ notifyResult(ack, token, mex);
+ tokenStore.removeToken(ack);
+
+ // Notify the sender thread that there maybe work for it to do now
+ synchronized (queueLock) {
+ queueLock.notifyAll();
+ }
+ } else {
+ notifyResult(ack, token, mex);
+ releaseMessageId(ack.getMessageId());
+ tokenStore.removeToken(ack);
+ }
+
+ checkQuiesceLock();
+ }
+
+ /**
+ * Called by the CommsReceiver when an Ack has been received but cannot be
+ * matched to a token. This method will generate the appropriate response with
+ * an error code.
+ *
+ * @param ack
+ * - The Orphaned Ack
+ * @throws MqttException
+ * if an exception occurs whilst handling orphaned Acks
+ */
+ protected void handleOrphanedAcks(MqttAck ack) throws MqttException {
+ final String methodName = "handleOrphanedAcks";
+ // @TRACE 666=Orphaned Ack key={0} message={1}
+ log.fine(CLASS_NAME, methodName, "666", new Object[] { Integer.valueOf(ack.getMessageId()), ack });
+
+ if (ack instanceof MqttPubAck) {
+ // MqttPubAck - This would be the end of a QoS 1 flow, so message can be ignored
+ } else if (ack instanceof MqttPubRec) {
+ // MqttPubRec - Send an MqttPubRel with the appropriate Reason Code
+ MqttProperties pubRelProperties = new MqttProperties();
+ if (this.mqttConnection.isSendReasonMessages()) {
+ String reasonString = String.format("Message identifier [%d] was not found. Discontinuing QoS 2 flow.",
+ ack.getMessageId());
+ pubRelProperties.setReasonString(reasonString);
+ }
+ MqttPubRel rel = new MqttPubRel(MqttReturnCode.RETURN_CODE_PACKET_ID_NOT_FOUND, ack.getMessageId(),
+ new MqttProperties());
+ this.send(rel, null);
+ } else if (ack instanceof MqttPubComp) {
+ // MqttPubComp
+ }
+ }
+
+ /**
+ * Called by {@link ClientState#notifyReceivedMsg} when a PUBREL message is
+ * received.
+ *
+ * @param pubRel
+ * The in-bound PUBREL
+ * @throws MqttException
+ * When an exception occurs whilst handling the PUBREL
+ */
+ protected void handleInboundPubRel(MqttPubRel pubRel) throws MqttException {
+ final String methodName = "handleInboundPubRel";
+ if (pubRel.getReasonCodes()[0] > MqttReturnCode.RETURN_CODE_UNSPECIFIED_ERROR) {
+ // @TRACE 667=MqttPubRel was received with an error code: key={0} message={1},
+ // Reason Code={2}
+ log.severe(CLASS_NAME, methodName, "667",
+ new Object[] { pubRel.getMessageId(), pubRel.toString(), pubRel.getReasonCodes()[0] });
+ throw new MqttException(pubRel.getReasonCodes()[0]);
+ } else {
+ // Currently this client has no need of the properties, so this is left empty.
+ MqttPubComp pubComp = new MqttPubComp(MqttReturnCode.RETURN_CODE_SUCCESS, pubRel.getMessageId(),
+ new MqttProperties());
+ // @TRACE 668=Creating MqttPubComp: {0}
+ log.info(CLASS_NAME, methodName, "668", new Object[] { pubComp.toString() });
+ this.send(pubComp, null);
+ }
+ }
+
+ /**
+ * Called by the CommsReceiver when a message has been received. Handles inbound
+ * messages and other flows such as PUBREL.
+ *
+ * @param message
+ * The {@link MqttWireMessage} that has been received
+ * @throws MqttException
+ * when an exception occurs whilst notifying
+ */
+ protected void notifyReceivedMsg(MqttWireMessage message) throws MqttException {
+ final String methodName = "notifyReceivedMsg";
+ this.lastInboundActivity = System.nanoTime();
+
+ // @TRACE 651=received key={0} message={1}
+ log.fine(CLASS_NAME, methodName, "651", new Object[] { Integer.valueOf(message.getMessageId()), message });
+
+ if (!quiescing) {
+ if (message instanceof MqttPublish) {
+ MqttPublish send = (MqttPublish) message;
+
+ // Do we have an incoming topic Alias?
+ if (send.getProperties().getTopicAlias() != null) {
+ int incomingTopicAlias = send.getProperties().getTopicAlias();
+
+ // Are incoming Topic Aliases enabled / is it a valid Alias?
+ if (incomingTopicAlias > this.mqttConnection.getIncomingTopicAliasMax()
+ || incomingTopicAlias == 0) {
+ // @TRACE 653=Invalid Topic Alias: topicAliasMax={0}, publishTopicAlias={1}
+ log.severe(CLASS_NAME, methodName, "653",
+ new Object[] { Integer.valueOf(this.mqttConnection.getIncomingTopicAliasMax()),
+ Integer.valueOf(incomingTopicAlias) });
+ if (callback != null) {
+ callback.mqttErrorOccurred(new MqttException(MqttException.REASON_CODE_INVALID_TOPIC_ALAS));
+ }
+ throw new MqttException(MqttClientException.REASON_CODE_INVALID_TOPIC_ALAS);
+
+ }
+
+ // Is this alias being sent with a topic string?
+ if (send.getTopicName() != null) {
+ // @TRACE 652=Setting Incoming New Topic Alias alias={0}, topicName={1}
+ log.fine(CLASS_NAME, methodName, "652", new Object[] {
+ Integer.valueOf(send.getProperties().getTopicAlias()), send.getTopicName() });
+ incomingTopicAliases.put(send.getProperties().getTopicAlias(), send.getTopicName());
+ } else {
+ // No Topic String, so must be in incomingTopicAliases.
+ if (incomingTopicAliases.contains(incomingTopicAlias)) {
+ send.setTopicName(incomingTopicAliases.get(incomingTopicAlias));
+ } else {
+ // @TRACE 654=Unknown Topic Alias: Incoming Alias={1}
+ log.severe(CLASS_NAME, methodName, "654",
+ new Object[] { Integer.valueOf(send.getProperties().getTopicAlias()) });
+ throw new MqttException(MqttClientException.REASON_CODE_UNKNOWN_TOPIC_ALIAS);
+ }
+ }
+ }
+
+ switch (send.getMessage().getQos()) {
+ case 0:
+ case 1:
+ if (callback != null) {
+ callback.messageArrived(send);
+ }
+ break;
+ case 2:
+ persistence.put(getReceivedPersistenceKey(message), (MqttPublish) message);
+ inboundQoS2.put(Integer.valueOf(send.getMessageId()), send);
+ if (callback != null) {
+ callback.messageArrived(send);
+ }
+ // Currently this client has no need of the properties, so this is left empty.
+ this.send(new MqttPubRec(MqttReturnCode.RETURN_CODE_SUCCESS, send.getMessageId(),
+ new MqttProperties()), null);
+ break;
+
+ default:
+ // should NOT reach here
+ }
+ } else if (message instanceof MqttPubRel) {
+ handleInboundPubRel((MqttPubRel) message);
+ } else if (message instanceof MqttAuth) {
+ MqttAuth authMsg = (MqttAuth) message;
+ callback.authMessageReceived(authMsg);
+ }
+ }
+ }
+
+ /**
+ * Called when waiters and callbacks have processed the message. For messages
+ * where delivery is complete the message can be removed from persistence and
+ * counters adjusted accordingly. Also tidy up by removing token from store...
+ *
+ * @param token
+ * The {@link MqttToken} that will be used to notify
+ * @throws MqttException
+ * if an exception occurs during notification
+ */
+ protected void notifyComplete(MqttToken token) throws MqttException {
+
+ final String methodName = "notifyComplete";
+
+ MqttWireMessage message = token.internalTok.getWireMessage();
+
+ if (message != null && message instanceof MqttAck) {
+
+ // @TRACE 629=received key={0} token={1} message={2}
+ log.fine(CLASS_NAME, methodName, "629",
+ new Object[] { Integer.valueOf(message.getMessageId()), token, message });
+
+ MqttAck ack = (MqttAck) message;
+
+ if (ack instanceof MqttPubAck) {
+
+ // QoS 1 - user notified now remove from persistence...
+ persistence.remove(getSendPersistenceKey(message));
+ persistence.remove(getSendBufferedPersistenceKey(message));
+ outboundQoS1.remove(Integer.valueOf(ack.getMessageId()));
+ decrementInFlight();
+ releaseMessageId(message.getMessageId());
+ tokenStore.removeToken(message);
+ // @TRACE 650=removed Qos 1 publish. key={0}
+ log.fine(CLASS_NAME, methodName, "650", new Object[] { Integer.valueOf(ack.getMessageId()) });
+ } else if (ack instanceof MqttPubComp) {
+ // QoS 2 - user notified now remove from persistence...
+ persistence.remove(getSendPersistenceKey(message));
+ persistence.remove(getSendConfirmPersistenceKey(message));
+ persistence.remove(getSendBufferedPersistenceKey(message));
+ outboundQoS2.remove(Integer.valueOf(ack.getMessageId()));
+
+ inFlightPubRels--;
+ decrementInFlight();
+ releaseMessageId(message.getMessageId());
+ tokenStore.removeToken(message);
+
+ // @TRACE 645=removed QoS 2 publish/pubrel. key={0}, -1 inFlightPubRels={1}
+ log.fine(CLASS_NAME, methodName, "645",
+ new Object[] { Integer.valueOf(ack.getMessageId()), Integer.valueOf(inFlightPubRels) });
+ }
+
+ checkQuiesceLock();
+ }
+ }
+
+ /**
+ * Updates a token with the latest reason codes, currently only used for PubRec
+ * messages.
+ *
+ * @param ack
+ * - The message that we are using for the update
+ * @param token
+ * - The Token we are updating
+ * @param ex
+ * - if there was a problem store the exception in the token.
+ */
+ protected void updateResult(MqttWireMessage ack, MqttToken token, MqttException ex) {
+ token.internalTok.update(ack, ex);
+ }
+
+ protected void notifyResult(MqttWireMessage ack, MqttToken token, MqttException ex) {
+ final String methodName = "notifyResult";
+ // unblock any threads waiting on the token
+ token.internalTok.markComplete(ack, ex);
+ token.internalTok.notifyComplete();
+
+ // Let the user know an async operation has completed and then remove the token
+ if (ack != null && ack instanceof MqttAck && !(ack instanceof MqttPubRec)) {
+ // @TRACE 648=key{0}, msg={1}, excep={2}
+ log.fine(CLASS_NAME, methodName, "648", new Object[] { token.internalTok.getKey(), ack, ex });
+ callback.asyncOperationComplete(token);
+ }
+ // There are cases where there is no ack as the operation failed before
+ // an ack was received
+ if (ack == null) {
+ // @TRACE 649=key={0},excep={1}
+ log.fine(CLASS_NAME, methodName, "649", new Object[] { token.internalTok.getKey(), ex });
+ callback.asyncOperationComplete(token);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.internal.MqttState#connected()
+ */
+ @Override
+ public void connected() {
+ final String methodName = "connected";
+ // @TRACE 631=connected
+ log.fine(CLASS_NAME, methodName, "631");
+ this.connected = true;
+
+ pingSender.start(); // Start ping thread when client connected to server.
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.internal.MqttState#resolveOldTokens(org.
+ * eclipse.paho.client.mqttv3.MqttException)
+ */
+ @Override
+ public Vector resolveOldTokens(MqttException reason) {
+ final String methodName = "resolveOldTokens";
+ // @TRACE 632=reason {0}
+ log.fine(CLASS_NAME, methodName, "632", new Object[] { reason });
+
+ // If any outstanding let the user know the reason why it is still
+ // outstanding by putting the reason shutdown is occurring into the
+ // token.
+ MqttException shutReason = reason;
+ if (reason == null) {
+ shutReason = new MqttException(MqttClientException.REASON_CODE_CLIENT_DISCONNECTING);
+ }
+
+ // Set the token up so it is ready to be notified after disconnect
+ // processing has completed. Do not
+ // remove the token from the store if it is a delivery token, it is
+ // valid after a reconnect.
+ Vector outT = tokenStore.getOutstandingTokens();
+ Enumeration outTE = outT.elements();
+ while (outTE.hasMoreElements()) {
+ MqttToken tok = (MqttToken) outTE.nextElement();
+ synchronized (tok) {
+ if (!tok.isComplete() && !tok.internalTok.isCompletePending() && tok.getException() == null) {
+ tok.internalTok.setException(shutReason);
+ }
+ }
+ if (!(tok.internalTok.isDeliveryToken())) {
+ // If not a delivery token it is not valid on
+ // restart so remove
+ tokenStore.removeToken(tok.internalTok.getKey());
+ }
+ }
+ return outT;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.paho.mqttv5.client.internal.MqttState#disconnected(org.eclipse.
+ * paho.client.mqttv3.MqttException)
+ */
+ @Override
+ public void disconnected(MqttException reason) {
+ final String methodName = "disconnected";
+ // @TRACE 633=disconnected
+ log.fine(CLASS_NAME, methodName, "633", new Object[] { reason });
+
+ this.connected = false;
+
+ try {
+ if (cleanStart) {
+ clearState();
+ }
+
+ clearConnectionState();
+
+ pendingMessages.clear();
+ pendingFlows.clear();
+ synchronized (pingOutstandingLock) {
+ // Reset pingOutstanding to allow reconnects to assume no previous ping.
+ pingOutstanding = 0;
+ }
+ } catch (MqttException e) {
+ // Ignore as we have disconnected at this point
+ }
+ }
+
+ /**
+ * Releases a message ID back into the pool of available message IDs. If the
+ * supplied message ID is not in use, then nothing will happen.
+ *
+ * @param msgId
+ * A message ID that can be freed up for re-use.
+ */
+ private synchronized void releaseMessageId(int msgId) {
+ inUseMsgIds.remove(Integer.valueOf(msgId));
+ }
+
+ /**
+ * Get the next MQTT message ID that is not already in use, and marks it as now
+ * being in use.
+ *
+ * @return the next MQTT message ID to use
+ */
+ private synchronized int getNextMessageId() throws MqttException {
+ int startingMessageId = nextMsgId;
+ // Allow two complete passes of the message ID range. This gives
+ // any asynchronous releases a chance to occur
+ int loopCount = 0;
+ do {
+ nextMsgId++;
+ if (nextMsgId > MAX_MSG_ID) {
+ nextMsgId = MIN_MSG_ID;
+ }
+ if (nextMsgId == startingMessageId) {
+ loopCount++;
+ if (loopCount == 2) {
+ throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_NO_MESSAGE_IDS_AVAILABLE);
+ }
+ }
+ } while (inUseMsgIds.containsKey(Integer.valueOf(nextMsgId)));
+ Integer id = Integer.valueOf(nextMsgId);
+ inUseMsgIds.put(id, id);
+ return nextMsgId;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.internal.MqttState#quiesce(long)
+ */
+ @Override
+ public void quiesce(long timeout) {
+ final String methodName = "quiesce";
+ // If the timeout is greater than zero t
+ if (timeout > 0) {
+ // @TRACE 637=timeout={0}
+ log.fine(CLASS_NAME, methodName, "637", new Object[] { Long.valueOf(timeout) });
+ synchronized (queueLock) {
+ this.quiescing = true;
+ }
+ // We don't want to handle any new inbound messages
+ callback.quiesce();
+ notifyQueueLock();
+
+ synchronized (quiesceLock) {
+ try {
+ // If token count is not zero there is outbound work to process and
+ // if pending flows is not zero there is outstanding work to complete and
+ // if call back is not quiseced there it needs to complete.
+ int tokc = tokenStore.count();
+ if (tokc > 0 || pendingFlows.size() > 0 || !callback.isQuiesced()) {
+ // @TRACE 639=wait for outstanding: actualInFlight={0} pendingFlows={1}
+ // inFlightPubRels={2} tokens={3}
+ log.fine(CLASS_NAME, methodName, "639",
+ new Object[] { Integer.valueOf(actualInFlight), Integer.valueOf(pendingFlows.size()),
+ Integer.valueOf(inFlightPubRels), Integer.valueOf(tokc) });
+
+ // wait for outstanding in flight messages to complete and
+ // any pending flows to complete
+ quiesceLock.wait(timeout);
+ }
+ } catch (InterruptedException ex) {
+ // Don't care, as we're shutting down anyway
+ }
+ }
+
+ // Quiesce time up or inflight messages delivered. Ensure pending delivery
+ // vectors are cleared ready for disconnect to be sent as the final flow.
+ synchronized (queueLock) {
+ pendingMessages.clear();
+ pendingFlows.clear();
+ quiescing = false;
+ actualInFlight = 0;
+ }
+ // @TRACE 640=finished
+ log.fine(CLASS_NAME, methodName, "640");
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.internal.MqttState#notifyQueueLock()
+ */
+ @Override
+ public void notifyQueueLock() {
+ final String methodName = "notifyQueueLock";
+ synchronized (queueLock) {
+ // @TRACE 638=notifying queueLock holders
+ log.fine(CLASS_NAME, methodName, "638");
+ queueLock.notifyAll();
+ }
+ }
+
+ protected void deliveryComplete(MqttPublish message) throws MqttPersistenceException {
+ final String methodName = "deliveryComplete";
+
+ // @TRACE 641=remove publish from persistence. key={0}
+ log.fine(CLASS_NAME, methodName, "641", new Object[] { Integer.valueOf(message.getMessageId()) });
+
+ persistence.remove(getReceivedPersistenceKey(message));
+ inboundQoS2.remove(Integer.valueOf(message.getMessageId()));
+ }
+
+ protected void deliveryComplete(int messageId) throws MqttPersistenceException {
+ final String methodName = "deliveryComplete";
+
+ // @TRACE 641=remove publish from persistence. key={0}
+ log.fine(CLASS_NAME, methodName, "641", new Object[] { Integer.valueOf(messageId) });
+
+ persistence.remove(getReceivedPersistenceKey(messageId));
+ inboundQoS2.remove(Integer.valueOf(messageId));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.internal.MqttState#getActualInFlight()
+ */
+ @Override
+ public int getActualInFlight() {
+ return actualInFlight;
+ }
+
+ public Long getOutgoingMaximumPacketSize() {
+ return this.mqttConnection.getIncomingMaximumPacketSize();
+ }
+
+ public Long getIncomingMaximumPacketSize() {
+ return this.mqttConnection.getOutgoingMaximumPacketSize();
+ }
+
+ /**
+ * Tidy up - ensure that tokens are released as they are maintained over a
+ * disconnect / connect cycle.
+ */
+ protected void close() {
+ inUseMsgIds.clear();
+ if (pendingMessages != null) {
+ pendingMessages.clear();
+ }
+ pendingFlows.clear();
+ outboundQoS2.clear();
+ outboundQoS1.clear();
+ outboundQoS0.clear();
+ inboundQoS2.clear();
+ tokenStore.clear();
+ inUseMsgIds = null;
+ pendingMessages = null;
+ pendingFlows = null;
+ outboundQoS2 = null;
+ outboundQoS1 = null;
+ outboundQoS0 = null;
+ inboundQoS2 = null;
+ tokenStore = null;
+ callback = null;
+ clientComms = null;
+ persistence = null;
+ pingCommand = null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.mqttv5.client.internal.MqttState#getDebug()
+ */
+ @Override
+ public Properties getDebug() {
+ Properties props = new Properties();
+ props.put("In use msgids", inUseMsgIds);
+ props.put("pendingMessages", pendingMessages);
+ props.put("pendingFlows", pendingFlows);
+ props.put("serverReceiveMaximum", Integer.valueOf(this.mqttConnection.getReceiveMaximum()));
+ props.put("nextMsgID", Integer.valueOf(nextMsgId));
+ props.put("actualInFlight", Integer.valueOf(actualInFlight));
+ props.put("inFlightPubRels", Integer.valueOf(inFlightPubRels));
+ props.put("quiescing", Boolean.valueOf(quiescing));
+ props.put("pingoutstanding", Integer.valueOf(pingOutstanding));
+ props.put("lastOutboundActivity", Long.valueOf(lastOutboundActivity));
+ props.put("lastInboundActivity", Long.valueOf(lastInboundActivity));
+ props.put("outboundQoS2", outboundQoS2);
+ props.put("outboundQoS1", outboundQoS1);
+ props.put("outboundQoS0", outboundQoS0);
+ props.put("inboundQoS2", inboundQoS2);
+ props.put("tokens", tokenStore);
+ return props;
+ }
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/internal/CommsCallback.java b/src/main/java/org/eclipse/paho/mqttv5/client/internal/CommsCallback.java
new file mode 100644
index 0000000..7e7cdd7
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/internal/CommsCallback.java
@@ -0,0 +1,670 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2019 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ * Ian Craggs - per subscription message handlers (bug 466579)
+ * Ian Craggs - ack control (bug 472172)
+ * James Sutton - Automatic Reconnect & Offline Buffering
+ */
+package org.eclipse.paho.mqttv5.client.internal;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.paho.mqttv5.client.IMqttMessageListener;
+import org.eclipse.paho.mqttv5.client.MqttActionListener;
+import org.eclipse.paho.mqttv5.client.MqttCallback;
+import org.eclipse.paho.mqttv5.client.MqttDisconnectResponse;
+import org.eclipse.paho.mqttv5.client.MqttToken;
+import org.eclipse.paho.mqttv5.client.logging.Logger;
+import org.eclipse.paho.mqttv5.client.logging.LoggerFactory;
+import org.eclipse.paho.mqttv5.common.MqttException;
+import org.eclipse.paho.mqttv5.common.MqttMessage;
+import org.eclipse.paho.mqttv5.common.packet.MqttAuth;
+import org.eclipse.paho.mqttv5.common.packet.MqttDisconnect;
+import org.eclipse.paho.mqttv5.common.packet.MqttProperties;
+import org.eclipse.paho.mqttv5.common.packet.MqttPubAck;
+import org.eclipse.paho.mqttv5.common.packet.MqttPubComp;
+import org.eclipse.paho.mqttv5.common.packet.MqttPublish;
+import org.eclipse.paho.mqttv5.common.packet.MqttReturnCode;
+import org.eclipse.paho.mqttv5.common.packet.UserProperty;
+import org.eclipse.paho.mqttv5.common.util.MqttTopicValidator;
+
+/**
+ * Bridge between Receiver and the external API. This class gets called by
+ * Receiver, and then converts the comms-centric MQTT message objects into ones
+ * understood by the external API.
+ */
+public class CommsCallback implements Runnable {
+ private static final String CLASS_NAME = CommsCallback.class.getName();
+ private Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT, CLASS_NAME);
+
+ private static final int INBOUND_QUEUE_SIZE = 10;
+ private MqttCallback mqttCallback;
+ private MqttCallback reconnectInternalCallback;
+ private HashMap callbackMap; // Map of message handler callbacks to internal IDs
+ private HashMap callbackTopicMap; // Map of Topic Strings to internal callback Ids
+ private HashMap subscriptionIdMap; // Map of Subscription Ids to callback Ids
+ private AtomicInteger messageHandlerId = new AtomicInteger(0);
+ private ClientComms clientComms;
+ private ArrayList messageQueue;
+ private ArrayList completeQueue;
+
+ private enum State {STOPPED, RUNNING, QUIESCING}
+
+ private State current_state = State.STOPPED;
+ private State target_state = State.STOPPED;
+ private final Object lifecycle = new Object();
+ private Thread callbackThread;
+ private String threadName;
+ private Future> callbackFuture;
+
+ private final Object workAvailable = new Object();
+ private final Object spaceAvailable = new Object();
+ private ClientState clientState;
+ private boolean manualAcks = false;
+
+
+ CommsCallback(ClientComms clientComms) {
+ this.clientComms = clientComms;
+ this.messageQueue = new ArrayList<>(INBOUND_QUEUE_SIZE);
+ this.completeQueue = new ArrayList<>(INBOUND_QUEUE_SIZE);
+ this.callbackMap = new HashMap<>();
+ this.callbackTopicMap = new HashMap<>();
+ this.subscriptionIdMap = new HashMap<>();
+ log.setResourceName(clientComms.getClient().getClientId());
+ }
+
+ public void setClientState(ClientState clientState) {
+ this.clientState = clientState;
+ }
+
+ /**
+ * Starts up the Callback thread.
+ *
+ * @param threadName
+ * The name of the thread
+ * @param executorService
+ * the {@link ExecutorService}
+ */
+ public void start(String threadName, ExecutorService executorService) {
+ this.threadName = threadName;
+ synchronized (lifecycle) {
+ if (current_state == State.STOPPED) {
+ // Preparatory work before starting the background thread.
+ // For safety ensure any old events are cleared.
+ synchronized (workAvailable) {
+ messageQueue.clear();
+ completeQueue.clear();
+ }
+ target_state = State.RUNNING;
+ if (executorService == null) {
+ new Thread(this).start();
+ } else {
+ callbackFuture = executorService.submit(this);
+ }
+ }
+ }
+ while (!isRunning()) {
+ try { Thread.sleep(100); } catch (Exception e) { }
+ }
+ }
+
+ /**
+ * Stops the callback thread. This call will block until stop has completed.
+ */
+ public void stop() {
+ final String methodName = "stop";
+ synchronized (lifecycle) {
+ if (callbackFuture != null) {
+ callbackFuture.cancel(true);
+ }
+ }
+ if (isRunning()) {
+ // @TRACE 700=stopping
+ log.fine(CLASS_NAME, methodName, "700");
+ synchronized (lifecycle) {
+ target_state = State.STOPPED;
+ }
+ if (!Thread.currentThread().equals(callbackThread)) {
+ synchronized (workAvailable) {
+ // @TRACE 701=notify workAvailable and wait for run
+ // to finish
+ log.fine(CLASS_NAME, methodName, "701");
+ workAvailable.notifyAll();
+ }
+ // Wait for the thread to finish.
+ while (isRunning()) {
+ try { Thread.sleep(100); } catch (Exception e) { }
+ clientState.notifyQueueLock();
+ }
+ }
+ callbackThread = null;
+ // @TRACE 703=stopped
+ log.fine(CLASS_NAME, methodName, "703");
+ }
+ }
+
+ public void setCallback(MqttCallback mqttCallback) {
+ this.mqttCallback = mqttCallback;
+ }
+
+ public void setReconnectCallback(MqttCallback callback) {
+ this.reconnectInternalCallback = callback;
+ }
+
+ public void setManualAcks(boolean manualAcks) {
+ this.manualAcks = manualAcks;
+ }
+
+ public void run() {
+ final String methodName = "run";
+ callbackThread = Thread.currentThread();
+ callbackThread.setName(threadName);
+
+ synchronized (lifecycle) {
+ current_state = State.RUNNING;
+ }
+
+ while (isRunning()) {
+ try {
+ // If no work is currently available, then wait until there is some...
+ try {
+ synchronized (workAvailable) {
+ if (isRunning() && messageQueue.isEmpty()
+ && completeQueue.isEmpty()) {
+ // @TRACE 704=wait for workAvailable
+ log.fine(CLASS_NAME, methodName, "704");
+ workAvailable.wait();
+ }
+ }
+ } catch (InterruptedException e) {
+ }
+
+ if (isRunning()) {
+ // Check for deliveryComplete callbacks...
+ MqttToken token = null;
+ synchronized (workAvailable) {
+ if (!completeQueue.isEmpty()) {
+ // First call the delivery arrived callback if needed
+ token = completeQueue.get(0);
+ completeQueue.remove(0);
+ }
+ }
+ if (null != token) {
+ handleActionComplete(token);
+ }
+
+ // Check for messageArrived callbacks...
+ MqttPublish message = null;
+ synchronized (workAvailable) {
+ if (!messageQueue.isEmpty()) {
+ // Note, there is a window on connect where a publish
+ // could arrive before we've
+ // finished the connect logic.
+ message = messageQueue.get(0);
+ messageQueue.remove(0);
+ }
+ }
+ if (null != message) {
+ handleMessage(message);
+ }
+ }
+
+ if (isQuiescing()) {
+ clientState.checkQuiesceLock();
+ }
+
+ } catch (Throwable ex) {
+ // Users code could throw an Error or Exception e.g. in the case
+ // of class NoClassDefFoundError
+ // @TRACE 714=callback threw exception
+ log.fine(CLASS_NAME, methodName, "714", null, ex);
+
+ clientComms.shutdownConnection(null, new MqttException(ex), null);
+ } finally {
+
+ synchronized (spaceAvailable) {
+ // Notify the spaceAvailable lock, to say that there's now
+ // some space on the queue...
+
+ // @TRACE 706=notify spaceAvailable
+ log.fine(CLASS_NAME, methodName, "706");
+ spaceAvailable.notifyAll();
+ }
+ }
+ }
+ synchronized (lifecycle) {
+ current_state = State.STOPPED;
+ }
+ callbackThread = null;
+ }
+
+ private void handleActionComplete(MqttToken token) throws MqttException {
+ final String methodName = "handleActionComplete";
+ synchronized (token) {
+ // @TRACE 705=callback and notify for key={0}
+ log.fine(CLASS_NAME, methodName, "705", new Object[] { token.internalTok.getKey() });
+ if (token.isComplete()) {
+ // Finish by doing any post processing such as delete
+ // from persistent store but only do so if the action
+ // is complete
+ clientState.notifyComplete(token);
+ }
+
+ // Unblock any waiters and if pending complete now set completed
+ token.internalTok.notifyComplete();
+
+ if (!token.internalTok.isNotified()) {
+ // If a callback is registered and delivery has finished
+ // call delivery complete callback.
+ if (mqttCallback != null && token.internalTok.isDeliveryToken() == true && token.isComplete()) {
+ try {
+ mqttCallback.deliveryComplete(token);
+ } catch (Throwable ex) {
+ // Just log the fact that an exception was thrown
+ // @TRACE 726=Ignoring Exception thrown from deliveryComplete {0}
+ log.fine(CLASS_NAME, methodName, "726", new Object[] { ex });
+ }
+ }
+ // Now call async action completion callbacks
+ fireActionEvent(token);
+ }
+
+ // Set notified so we don't tell the user again about this action.
+ if (token.isComplete()) {
+ if (token.internalTok.isDeliveryToken() == true || token.getActionCallback() instanceof MqttActionListener) {
+ token.internalTok.setNotified(true);
+ }
+ }
+
+ }
+ }
+
+ /**
+ * This method is called when the connection to the server is lost. If there is
+ * no cause then it was a clean disconnect. The connectionLost callback will be
+ * invoked if registered and run on the thread that requested shutdown e.g.
+ * receiver or sender thread. If the request was a user initiated disconnect
+ * then the disconnect token will be notified.
+ *
+ * @param cause
+ * the reason behind the loss of connection.
+ * @param message
+ * The {@link MqttDisconnect} packet sent by the server
+ */
+ public void connectionLost(MqttException cause, MqttDisconnect message) {
+ final String methodName = "connectionLost";
+ // If there was a problem and a client callback has been set inform
+ // the connection lost listener of the problem.
+ try {
+ if (mqttCallback != null && message != null) {
+
+ // @TRACE 722=Server initiated disconnect, connection closed. Disconnect={0}
+ log.fine(CLASS_NAME, methodName, "722", new Object[] { message.toString() });
+ MqttDisconnectResponse disconnectResponse = new MqttDisconnectResponse(message.getReturnCode(),
+ message.getProperties().getReasonString(),
+ (ArrayList) message.getProperties().getUserProperties(),
+ message.getProperties().getServerReference());
+ mqttCallback.disconnected(disconnectResponse);
+ } else if (mqttCallback != null && cause != null) {
+ // @TRACE 708=call connectionLost
+ log.fine(CLASS_NAME, methodName, "708", new Object[] { cause });
+ MqttDisconnectResponse disconnectResponse = new MqttDisconnectResponse(cause);
+ mqttCallback.disconnected(disconnectResponse);
+ }
+ if (reconnectInternalCallback != null && cause != null) {
+ MqttDisconnectResponse disconnectResponse = new MqttDisconnectResponse(cause);
+
+ reconnectInternalCallback.disconnected(disconnectResponse);
+ }
+ } catch (Throwable t) {
+ // Just log the fact that an exception was thrown
+ // @TRACE 720=Ignoring Exception thrown from connectionLost {0}
+ log.fine(CLASS_NAME, methodName, "720", new Object[] { t });
+ }
+ }
+
+ /**
+ * An action has completed - if a completion listener has been set on the token
+ * then invoke it with the outcome of the action.
+ *
+ * @param token
+ * The {@link MqttToken} that has completed
+ */
+ public void fireActionEvent(MqttToken token) {
+ final String methodName = "fireActionEvent";
+
+ if (token != null) {
+ MqttActionListener asyncCB = token.getActionCallback();
+ if (asyncCB != null) {
+ if (token.getException() == null) {
+ // @TRACE 716=call onSuccess key={0}
+ log.fine(CLASS_NAME, methodName, "716", new Object[] { token.internalTok.getKey() });
+ asyncCB.onSuccess(token);
+ } else {
+ // @TRACE 717=call onFailure key {0}
+ log.fine(CLASS_NAME, methodName, "716", new Object[] { token.internalTok.getKey() });
+ asyncCB.onFailure(token, token.getException());
+ }
+ }
+ }
+ }
+
+ /**
+ * This method is called when a message arrives on a topic. Messages are only
+ * added to the queue for inbound messages if the client is not quiescing.
+ *
+ * @param sendMessage
+ * the MQTT SEND message.
+ */
+ public void messageArrived(MqttPublish sendMessage) {
+ final String methodName = "messageArrived";
+ if (mqttCallback != null || callbackMap.size() > 0) {
+ // If we already have enough messages queued up in memory, wait
+ // until some more queue space becomes available. This helps
+ // the client protect itself from getting flooded by messages
+ // from the server.
+ synchronized (spaceAvailable) {
+ while (isRunning() && !isQuiescing() && messageQueue.size() >= INBOUND_QUEUE_SIZE) {
+ try {
+ // @TRACE 709=wait for spaceAvailable
+ log.fine(CLASS_NAME, methodName, "709");
+ spaceAvailable.wait(200);
+ } catch (InterruptedException ex) {
+ }
+ }
+ }
+ if (!isQuiescing()) {
+ // Notify the CommsCallback thread that there's work to do...
+ synchronized (workAvailable) {
+ messageQueue.add(sendMessage);
+ // @TRACE 710=new msg avail, notify workAvailable
+ log.fine(CLASS_NAME, methodName, "710");
+ workAvailable.notifyAll();
+ }
+ }
+ }
+ }
+
+ /**
+ * This method is called when an Auth Message is received.
+ *
+ * @param authMessage
+ * The {@link MqttAuth} message.
+ */
+ public void authMessageReceived(MqttAuth authMessage) {
+ String methodName = "authMessageReceived";
+ if (mqttCallback != null) {
+ try {
+ mqttCallback.authPacketArrived(authMessage.getReturnCode(), authMessage.getProperties());
+ } catch (Throwable ex) {
+ // Just log the fact that an exception was thrown
+ // @TRACE 727=Ignoring Exception thrown from authPacketArrived {0}
+ log.fine(CLASS_NAME, methodName, "727", new Object[] { ex });
+ }
+ }
+ }
+
+ /**
+ * This method is called when a non-critical MQTT error has occurred in the
+ * client that the application should choose how to deal with.
+ *
+ * @param exception
+ * The exception that was thrown containing the cause for
+ * disconnection.
+ */
+ public void mqttErrorOccurred(MqttException exception) {
+ final String methodName = "mqttErrorOccurred";
+ log.warning(CLASS_NAME, methodName, "721", new Object[] { exception.getMessage() });
+ if (mqttCallback != null) {
+ try {
+ mqttCallback.mqttErrorOccurred(exception);
+ } catch (Exception ex) {
+ // Just log the fact that an exception was thrown
+ // @TRACE 724=Ignoring Exception thrown from mqttErrorOccurred: {0}
+ log.fine(CLASS_NAME, methodName, "724", new Object[] { ex });
+ }
+ }
+ }
+
+ /**
+ * Let the call back thread quiesce. Prevent new inbound messages being added to
+ * the process queue and let existing work quiesce. (until the thread is told to
+ * shutdown).
+ */
+ public void quiesce() {
+ final String methodName = "quiesce";
+ synchronized (lifecycle) {
+ if (current_state == State.RUNNING)
+ current_state = State.QUIESCING;
+ }
+ synchronized (spaceAvailable) {
+ // @TRACE 711=quiesce notify spaceAvailable
+ log.fine(CLASS_NAME, methodName, "711");
+ // Unblock anything waiting for space...
+ spaceAvailable.notifyAll();
+ }
+ }
+
+ boolean areQueuesEmpty() {
+ synchronized (workAvailable) {
+ return completeQueue.isEmpty() && messageQueue.isEmpty();
+ }
+ }
+
+ public boolean isQuiesced() {
+ return (isQuiescing() && areQueuesEmpty());
+ }
+
+ private void handleMessage(MqttPublish publishMessage) throws Exception {
+ final String methodName = "handleMessage";
+ // If quisecing process any pending messages.
+ String destName = publishMessage.getTopicName();
+
+ // @TRACE 713=call messageArrived key={0} topic={1}
+ log.fine(CLASS_NAME, methodName, "713", new Object[] { Integer.valueOf(publishMessage.getMessageId()), destName });
+ deliverMessage(destName, publishMessage.getMessageId(), publishMessage.getMessage());
+
+ // If we are not in manual ACK mode:
+ if (!this.manualAcks && publishMessage.getMessage().getQos() == 1) {
+ this.clientComms.internalSend(new MqttPubAck(MqttReturnCode.RETURN_CODE_SUCCESS,
+ publishMessage.getMessageId(), new MqttProperties()),
+ new MqttToken(clientComms.getClient().getClientId()));
+ }
+ }
+
+ public void messageArrivedComplete(int messageId, int qos) throws MqttException {
+ if (qos == 1) {
+ this.clientComms.internalSend(
+ new MqttPubAck(MqttReturnCode.RETURN_CODE_SUCCESS, messageId, new MqttProperties()),
+ new MqttToken(clientComms.getClient().getClientId()));
+ } else if (qos == 2) {
+ this.clientComms.deliveryComplete(messageId);
+ MqttPubComp pubComp = new MqttPubComp(MqttReturnCode.RETURN_CODE_SUCCESS, messageId, new MqttProperties());
+ // @TRACE 723=Creating MqttPubComp due to manual ACK: {0}
+ log.info(CLASS_NAME, "messageArrivedComplete", "723", new Object[] { pubComp.toString() });
+
+ this.clientComms.internalSend(pubComp, new MqttToken(clientComms.getClient().getClientId()));
+ }
+ }
+
+ public void asyncOperationComplete(MqttToken token) {
+ final String methodName = "asyncOperationComplete";
+
+ if (isRunning()) {
+ // invoke callbacks on callback thread
+ synchronized (workAvailable) {
+ completeQueue.add(token);
+ // @TRACE 715=new workAvailable. key={0}
+ log.fine(CLASS_NAME, methodName, "715", new Object[] { token.internalTok.getKey() });
+ workAvailable.notifyAll();
+ }
+ } else {
+ // invoke async callback on invokers thread
+ try {
+ handleActionComplete(token);
+ } catch (MqttException ex) {
+ // Users code could throw an Error or Exception e.g. in the case
+ // of class NoClassDefFoundError
+ // @TRACE 719=callback threw ex:
+ log.fine(CLASS_NAME, methodName, "719", null, ex);
+
+ // Shutdown likely already in progress but no harm to confirm
+ clientComms.shutdownConnection(null, new MqttException(ex), null);
+ }
+
+ }
+ }
+
+ /**
+ * Returns the thread used by this callback.
+ *
+ * @return The {@link Thread}
+ */
+ protected Thread getThread() {
+ return callbackThread;
+ }
+
+ public void setMessageListener(Integer subscriptionId, String topicFilter, IMqttMessageListener messageListener) {
+ int internalId = messageHandlerId.incrementAndGet();
+ this.callbackMap.put(internalId, messageListener);
+ this.callbackTopicMap.put(topicFilter, internalId);
+
+ if (subscriptionId != null) {
+ this.subscriptionIdMap.put(subscriptionId, internalId);
+ }
+ }
+
+ /**
+ * Removes a Message Listener by Topic. If the Topic is null or incorrect, this
+ * function will return without making any changes. It will also attempt to find
+ * any subscription IDs linked to the same message listener and will remove them
+ * too.
+ *
+ * @param topicFilter
+ * the topic filter that identifies the Message listener to remove.
+ */
+ public void removeMessageListener(String topicFilter) {
+ Integer callbackId = this.callbackTopicMap.get(topicFilter);
+ this.callbackMap.remove(callbackId);
+ this.callbackTopicMap.remove(topicFilter);
+
+ // Reverse lookup the subscription ID if it exists to remove that as well
+ for (Map.Entry entry : this.subscriptionIdMap.entrySet()) {
+ if (entry.getValue().equals(callbackId)) {
+ this.subscriptionIdMap.remove(entry.getKey());
+ }
+ }
+ }
+
+ /**
+ * Removes a Message Listener by subscription ID. If the Subscription Identifier
+ * is null or incorrect, this function will return without making any changes.
+ * It will also attempt to find any Topic Strings linked to the same message
+ * listener and will remove them too.
+ *
+ * @param subscriptionId
+ * the subscription ID that identifies the Message listener to
+ * remove.
+ */
+ public void removeMessageListener(Integer subscriptionId) {
+ Integer callbackId = this.subscriptionIdMap.get(subscriptionId);
+ this.subscriptionIdMap.remove(callbackId);
+ this.callbackMap.remove(callbackId);
+
+ // Reverse lookup the topic if it exists to remove that as well
+ for (Map.Entry entry : this.callbackTopicMap.entrySet()) {
+ if (entry.getValue().equals(callbackId)) {
+ this.callbackTopicMap.remove(entry.getKey());
+ }
+ }
+ }
+
+ public void removeMessageListeners() {
+ this.callbackMap.clear();
+ this.subscriptionIdMap.clear();
+ this.callbackTopicMap.clear();
+ }
+
+ protected boolean deliverMessage(String topicName, int messageId, MqttMessage aMessage) throws Exception {
+ boolean delivered = false;
+ String methodName = "deliverMessage";
+
+ if (aMessage.getProperties().getSubscriptionIdentifiers().isEmpty()) {
+ // No Subscription IDs, use topic filter matching
+ for (Map.Entry entry : this.callbackTopicMap.entrySet()) {
+ if (MqttTopicValidator.isMatched(entry.getKey(), topicName)) {
+ aMessage.setId(messageId);
+ this.callbackMap.get(entry.getValue()).messageArrived(topicName, aMessage);
+ delivered = true;
+ }
+ }
+
+ } else {
+ // We have Subscription IDs
+ for (Integer subId : aMessage.getProperties().getSubscriptionIdentifiers()) {
+ if (this.subscriptionIdMap.containsKey(subId)) {
+ Integer callbackId = this.subscriptionIdMap.get(subId);
+ aMessage.setId(messageId);
+ this.callbackMap.get(callbackId).messageArrived(topicName, aMessage);
+ delivered = true;
+ }
+ }
+ }
+
+ /*
+ * if the message hasn't been delivered to a per subscription handler, give it
+ * to the default handler
+ */
+ if (mqttCallback != null && !delivered) {
+ aMessage.setId(messageId);
+ try {
+ mqttCallback.messageArrived(topicName, aMessage);
+ } catch (Exception ex) {
+ // Just log the fact that an exception was thrown
+ // @TRACE 725=Ignoring Exception thrown from messageArrived: {0}
+ log.fine(CLASS_NAME, methodName, "725", new Object[] { ex });
+ }
+ delivered = true;
+ }
+
+ return delivered;
+ }
+
+ public boolean doesSubscriptionIdentifierExist(int subscriptionIdentifier) {
+ return (this.subscriptionIdMap.containsKey(subscriptionIdentifier));
+ }
+
+ public boolean isRunning() {
+ boolean result;
+ synchronized (lifecycle) {
+ result = ((current_state == State.RUNNING || current_state == State.QUIESCING)
+ && target_state == State.RUNNING);
+ }
+ return result;
+ }
+
+ public boolean isQuiescing() {
+ boolean result;
+ synchronized (lifecycle) {
+ result = (current_state == State.QUIESCING);
+ }
+ return result;
+ }
+
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/internal/CommsReceiver.java b/src/main/java/org/eclipse/paho/mqttv5/client/internal/CommsReceiver.java
new file mode 100644
index 0000000..5ab52b4
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/internal/CommsReceiver.java
@@ -0,0 +1,241 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2019 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.mqttv5.client.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+import org.eclipse.paho.mqttv5.client.MqttClientException;
+import org.eclipse.paho.mqttv5.client.MqttToken;
+import org.eclipse.paho.mqttv5.client.logging.Logger;
+import org.eclipse.paho.mqttv5.client.logging.LoggerFactory;
+import org.eclipse.paho.mqttv5.client.wire.MqttInputStream;
+import org.eclipse.paho.mqttv5.common.MqttException;
+import org.eclipse.paho.mqttv5.common.packet.MqttAck;
+import org.eclipse.paho.mqttv5.common.packet.MqttDisconnect;
+import org.eclipse.paho.mqttv5.common.packet.MqttWireMessage;
+
+/**
+ * Receives MQTT packets from the server.
+ */
+public class CommsReceiver implements Runnable {
+ private static final String CLASS_NAME = CommsReceiver.class.getName();
+ private Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT, CLASS_NAME);
+
+ private enum State {STOPPED, RUNNING, STARTING, RECEIVING}
+
+ private State current_state = State.STOPPED;
+ private State target_state = State.STOPPED;
+ private final Object lifecycle = new Object();
+ private String threadName;
+ private Future> receiverFuture;
+
+ private ClientState clientState = null;
+ private ClientComms clientComms = null;
+ private MqttInputStream in;
+ private CommsTokenStore tokenStore = null;
+ private Thread recThread = null;
+
+ public CommsReceiver(ClientComms clientComms, ClientState clientState, CommsTokenStore tokenStore, InputStream in) {
+ this.in = new MqttInputStream(clientState, in, clientComms.getClient().getClientId());
+ this.clientComms = clientComms;
+ this.clientState = clientState;
+ this.tokenStore = tokenStore;
+ log.setResourceName(clientComms.getClient().getClientId());
+ }
+
+ /**
+ * Starts up the Receiver's thread.
+ *
+ * @param threadName
+ * the thread name.
+ * @param executorService
+ * used to execute the thread
+ */
+ public void start(String threadName, ExecutorService executorService) {
+ this.threadName = threadName;
+ final String methodName = "start";
+ // @TRACE 855=starting
+ log.fine(CLASS_NAME, methodName, "855");
+ synchronized (lifecycle) {
+ if (current_state == State.STOPPED && target_state == State.STOPPED) {
+ target_state = State.RUNNING;
+ if (executorService == null) {
+ new Thread(this).start();
+ } else {
+ receiverFuture = executorService.submit(this);
+ }
+ }
+ }
+ while (!isRunning()) {
+ try { Thread.sleep(100); } catch (Exception e) { }
+ }
+ }
+
+ /**
+ * Stops the Receiver's thread. This call will block.
+ */
+ public void stop() {
+ final String methodName = "stop";
+ synchronized (lifecycle) {
+ if (receiverFuture != null) {
+ receiverFuture.cancel(true);
+ }
+ //@TRACE 850=stopping
+ log.fine(CLASS_NAME,methodName, "850");
+ if (isRunning()) {
+ target_state = State.STOPPED;
+ }
+ }
+ while (isRunning()) {
+ try { Thread.sleep(100); } catch (Exception e) { }
+ }
+ //@TRACE 851=stopped
+ log.fine(CLASS_NAME,methodName,"851");
+ }
+
+ /**
+ * Run loop to receive messages from the server.
+ */
+ public void run() {
+ recThread = Thread.currentThread();
+ recThread.setName(threadName);
+ final String methodName = "run";
+ MqttToken token = null;
+
+ synchronized (lifecycle) {
+ current_state = State.RUNNING;
+ }
+
+ try {
+ State my_target;
+ synchronized (lifecycle) {
+ my_target = target_state;
+ }
+ while (my_target == State.RUNNING && (in != null)) {
+ try {
+ //@TRACE 852=network read message
+ log.fine(CLASS_NAME,methodName,"852");
+ if (in.available() > 0) {
+ synchronized (lifecycle) {
+ current_state = State.RECEIVING;
+ }
+ }
+ MqttWireMessage message = in.readMqttWireMessage();
+ synchronized (lifecycle) {
+ current_state = State.RUNNING;
+ }
+
+ // instanceof checks if message is null
+ if (message instanceof MqttAck) {
+ token = tokenStore.getToken(message);
+ if (token != null) {
+ synchronized (token) {
+ // Ensure the notify processing is done under a lock on the token
+ // This ensures that the send processing can complete before the
+ // receive processing starts! ( request and ack and ack processing
+ // can occur before request processing is complete if not!
+ clientState.notifyReceivedAck((MqttAck) message);
+ }
+ } else {
+ // This is an ack for a message we no longer have a ticket for.
+ log.fine(CLASS_NAME, methodName, "857");
+ clientState.handleOrphanedAcks((MqttAck) message);
+ }
+ } else if (message != null && message instanceof MqttDisconnect) {
+ // This is a Disconnect Message
+ clientComms.shutdownConnection(null, new MqttException(MqttClientException.REASON_CODE_SERVER_DISCONNECTED, (MqttDisconnect) message), (MqttDisconnect) message);
+ } else {
+ if (message != null) {
+ // A new message has arrived
+ clientState.notifyReceivedMsg(message);
+ }
+ else {
+ if (!clientComms.isConnected() && !clientComms.isConnecting()) {
+ throw new IOException("Connection is lost.");
+ }
+ }
+ }
+ }
+ catch (MqttException ex) {
+ // @TRACE 856=Stopping, MQttException
+ log.fine(CLASS_NAME, methodName, "856", null, ex);
+ synchronized (lifecycle) {
+ target_state = State.STOPPED;
+ }
+ // Token maybe null but that is handled in shutdown
+ clientComms.shutdownConnection(token, ex, null);
+ }
+ catch (IOException ioe) {
+ // @TRACE 853=Stopping due to IOException
+ log.fine(CLASS_NAME, methodName, "853");
+ if (target_state != State.STOPPED) {
+ synchronized (lifecycle) {
+ target_state = State.STOPPED;
+ }
+ // An EOFException could be raised if the broker processes the
+ // DISCONNECT and ends the socket before we complete. As such,
+ // only shutdown the connection if we're not already shutting down.
+ if (!clientComms.isDisconnecting()) {
+ clientComms.shutdownConnection(token,
+ new MqttException(MqttClientException.REASON_CODE_CONNECTION_LOST, ioe), null);
+ }
+ }
+ }
+ finally {
+ synchronized (lifecycle) {
+ current_state = State.RUNNING;
+ }
+ }
+ synchronized (lifecycle) {
+ my_target = target_state;
+ }
+ } // end while
+ } finally {
+ synchronized (lifecycle) {
+ current_state = State.STOPPED;
+ }
+ } // end try
+
+ recThread = null;
+ //@TRACE 854=<
+ log.fine(CLASS_NAME,methodName,"854");
+ }
+
+ public boolean isRunning() {
+ boolean result;
+ synchronized (lifecycle) {
+ result = ((current_state == State.RUNNING || current_state == State.RECEIVING)
+ && target_state == State.RUNNING);
+ }
+ return result;
+ }
+
+ /**
+ * Returns the receiving state.
+ *
+ * @return true if the receiver is receiving data, false otherwise.
+ */
+ public boolean isReceiving() {
+ boolean result;
+ synchronized (lifecycle) {
+ result = (current_state == State.RECEIVING);
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/internal/CommsSender.java b/src/main/java/org/eclipse/paho/mqttv5/client/internal/CommsSender.java
new file mode 100644
index 0000000..6c8146c
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/internal/CommsSender.java
@@ -0,0 +1,210 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2019 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.mqttv5.client.internal;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+import org.eclipse.paho.mqttv5.client.MqttClientException;
+import org.eclipse.paho.mqttv5.client.MqttToken;
+import org.eclipse.paho.mqttv5.client.logging.Logger;
+import org.eclipse.paho.mqttv5.client.logging.LoggerFactory;
+import org.eclipse.paho.mqttv5.client.wire.MqttOutputStream;
+import org.eclipse.paho.mqttv5.common.MqttException;
+import org.eclipse.paho.mqttv5.common.packet.MqttAck;
+import org.eclipse.paho.mqttv5.common.packet.MqttDisconnect;
+import org.eclipse.paho.mqttv5.common.packet.MqttWireMessage;
+
+
+public class CommsSender implements Runnable {
+ private static final String CLASS_NAME = CommsSender.class.getName();
+ private Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT, CLASS_NAME);
+
+ //Sends MQTT packets to the server on its own thread
+ private enum State {STOPPED, RUNNING, STARTING}
+
+ private State current_state = State.STOPPED;
+ private State target_state = State.STOPPED;
+ private final Object lifecycle = new Object();
+ private Thread sendThread = null;
+ private String threadName;
+ private Future> senderFuture;
+
+ private ClientState clientState = null;
+ private MqttOutputStream out;
+ private ClientComms clientComms = null;
+ private CommsTokenStore tokenStore = null;
+
+
+ public CommsSender(ClientComms clientComms, ClientState clientState, CommsTokenStore tokenStore, OutputStream out) {
+ this.out = new MqttOutputStream(clientState, out, clientComms.getClient().getClientId());
+ this.clientComms = clientComms;
+ this.clientState = clientState;
+ this.tokenStore = tokenStore;
+ log.setResourceName(clientComms.getClient().getClientId());
+ }
+
+ /**
+ * Starts up the Sender thread.
+ * @param threadName the threadname
+ * @param executorService used to execute the thread
+ */
+ public void start(String threadName, ExecutorService executorService) {
+ this.threadName = threadName;
+ synchronized (lifecycle) {
+ if (current_state == State.STOPPED && target_state == State.STOPPED) {
+ target_state = State.RUNNING;
+ if (executorService == null) {
+ new Thread(this).start();
+ } else {
+ senderFuture = executorService.submit(this);
+ }
+ }
+ }
+ while (!isRunning()) {
+ try { Thread.sleep(100); } catch (Exception e) { }
+ }
+ }
+
+ /**
+ * Stops the Sender's thread. This call will block.
+ */
+ public void stop() {
+ final String methodName = "stop";
+
+ if (!isRunning()) {
+ return;
+ }
+
+ synchronized (lifecycle) {
+ if (senderFuture != null) {
+ senderFuture.cancel(true);
+ }
+ //@TRACE 800=stopping sender
+ log.fine(CLASS_NAME,methodName,"800");
+ if (isRunning()) {
+ target_state = State.STOPPED;
+ clientState.notifyQueueLock();
+ }
+ }
+ while (isRunning()) {
+ try { Thread.sleep(100); } catch (Exception e) { }
+ clientState.notifyQueueLock();
+ }
+ //@TRACE 801=stopped
+ log.fine(CLASS_NAME,methodName,"801");
+ }
+
+ public void run() {
+ sendThread = Thread.currentThread();
+ sendThread.setName(threadName);
+ final String methodName = "run";
+ MqttWireMessage message = null;
+
+ synchronized (lifecycle) {
+ current_state = State.RUNNING;
+ }
+
+ try {
+ State my_target;
+ synchronized (lifecycle) {
+ my_target = target_state;
+ }
+ while (my_target == State.RUNNING && (out != null)) {
+ try {
+ message = clientState.get();
+ if (message != null) {
+ //@TRACE 802=network send key={0} msg={1}
+ log.fine(CLASS_NAME,methodName,"802", new Object[] {message.getKey(),message});
+
+ if (message instanceof MqttAck) {
+ out.write(message);
+ out.flush();
+ } else {
+ MqttToken token = tokenStore.getToken(message);
+ // While quiescing the tokenstore can be cleared so need
+ // to check for null for the case where clear occurs
+ // while trying to send a message.
+ if (token != null) {
+ synchronized (token) {
+ out.write(message);
+ try {
+ out.flush();
+ } catch (IOException ex) {
+ // The flush has been seen to fail on disconnect of a SSL socket
+ // as disconnect is in progress this should not be treated as an error
+ if (!(message instanceof MqttDisconnect)) {
+ throw ex;
+ }
+ }
+ clientState.notifySent(message);
+ }
+ }
+ }
+ } else { // null message
+ //@TRACE 803=get message returned null, stopping}
+ log.fine(CLASS_NAME,methodName,"803");
+ synchronized (lifecycle) {
+ target_state = State.STOPPED;
+ }
+ }
+ } catch (MqttException me) {
+ handleRunException(message, me);
+ } catch (Exception ex) {
+ handleRunException(message, ex);
+ }
+ synchronized (lifecycle) {
+ my_target = target_state;
+ }
+ } // end while
+ } finally {
+ synchronized (lifecycle) {
+ current_state = State.STOPPED;
+ sendThread = null;
+ }
+ }
+
+ //@TRACE 805=<
+ log.fine(CLASS_NAME, methodName,"805");
+
+ }
+
+ private void handleRunException(MqttWireMessage message, Exception ex) {
+ final String methodName = "handleRunException";
+ //@TRACE 804=exception
+ log.severe(CLASS_NAME,methodName,"804",null, ex);
+ MqttException mex;
+ if ( !(ex instanceof MqttException)) {
+ mex = new MqttException(MqttClientException.REASON_CODE_CONNECTION_LOST, ex);
+ } else {
+ mex = (MqttException)ex;
+ }
+ synchronized (lifecycle) {
+ target_state = State.STOPPED;
+ }
+ clientComms.shutdownConnection(null, mex, null);
+ }
+
+ public boolean isRunning() {
+ boolean result;
+ synchronized (lifecycle) {
+ result = (current_state == State.RUNNING && target_state == State.RUNNING);
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/internal/CommsTokenStore.java b/src/main/java/org/eclipse/paho/mqttv5/client/internal/CommsTokenStore.java
new file mode 100644
index 0000000..5ccecd6
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/internal/CommsTokenStore.java
@@ -0,0 +1,254 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2018 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.mqttv5.client.internal;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import org.eclipse.paho.mqttv5.client.MqttToken;
+import org.eclipse.paho.mqttv5.client.logging.Logger;
+import org.eclipse.paho.mqttv5.client.logging.LoggerFactory;
+import org.eclipse.paho.mqttv5.common.MqttException;
+import org.eclipse.paho.mqttv5.common.packet.MqttPublish;
+import org.eclipse.paho.mqttv5.common.packet.MqttWireMessage;
+
+
+/**
+ * Provides a "token" based system for storing and tracking actions across
+ * multiple threads.
+ * When a message is sent, a token is associated with the message
+ * and saved using the {@link CommsTokenStore#saveToken(MqttToken, MqttWireMessage)} method. Anyone interested
+ * in tacking the state can call one of the wait methods on the token or using
+ * the asynchronous listener callback method on the operation.
+ * The {@link CommsReceiver} class, on another thread, reads responses back from
+ * the network. It uses the response to find the relevant token, which it can then
+ * notify.
+ *
+ * Note:
+ * Ping, connect and disconnect do not have a unique message id as
+ * only one outstanding request of each type is allowed to be outstanding
+ */
+public class CommsTokenStore {
+ private static final String CLASS_NAME = CommsTokenStore.class.getName();
+ private Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT, CLASS_NAME);
+
+ // Maps message-specific data (usually message IDs) to tokens
+ private final Hashtable tokens;
+ private String logContext;
+ private MqttException closedResponse = null;
+
+ public CommsTokenStore(String logContext) {
+ final String methodName = "";
+
+ log.setResourceName(logContext);
+ this.tokens = new Hashtable();
+ this.logContext = logContext;
+ //@TRACE 308=<>
+ log.fine(CLASS_NAME,methodName,"308");//,new Object[]{message});
+
+ }
+
+ /**
+ * Based on the message type that has just been received return the associated
+ * token from the token store or null if one does not exist.
+ * @param message whose token is to be returned
+ * @return token for the requested message
+ */
+ public MqttToken getToken(MqttWireMessage message) {
+ String key = message.getKey();
+ return (MqttToken)tokens.get(key);
+ }
+
+ public MqttToken getToken(String key) {
+ return (MqttToken)tokens.get(key);
+ }
+
+
+ public MqttToken removeToken(MqttWireMessage message) {
+ if (message != null) {
+ return removeToken(message.getKey());
+ }
+ return null;
+ }
+
+ public MqttToken removeToken(String key) {
+ final String methodName = "removeToken";
+ //@TRACE 306=key={0}
+ log.fine(CLASS_NAME,methodName,"306",new Object[]{key});
+
+ if ( null != key ){
+ return (MqttToken) tokens.remove(key);
+ }
+
+ return null;
+ }
+
+ /**
+ * Restores a token after a client restart. This method could be called
+ * for a SEND of CONFIRM, but either way, the original SEND is what's
+ * needed to re-build the token.
+ * @param message The {@link MqttPublish} message to restore
+ * @return {@link MqttToken}
+ */
+ protected MqttToken restoreToken(MqttPublish message) {
+ final String methodName = "restoreToken";
+ MqttToken token;
+ synchronized(tokens) {
+ String key = Integer.valueOf(message.getMessageId()).toString();
+ if (this.tokens.containsKey(key)) {
+ token = this.tokens.get(key);
+ //@TRACE 302=existing key={0} message={1} token={2}
+ log.fine(CLASS_NAME,methodName, "302",new Object[]{key, message,token});
+ } else {
+ token = new MqttToken(logContext);
+ token.internalTok.setDeliveryToken(true);
+ token.internalTok.setKey(key);
+ this.tokens.put(key, token);
+ //@TRACE 303=creating new token key={0} message={1} token={2}
+ log.fine(CLASS_NAME,methodName,"303",new Object[]{key, message, token});
+ }
+ }
+ return token;
+ }
+
+ // For outbound messages store the token in the token store
+ // For pubrel use the existing publish token
+ protected void saveToken(MqttToken token, MqttWireMessage message) throws MqttException {
+ final String methodName = "saveToken";
+
+ synchronized(tokens) {
+ if (closedResponse == null) {
+ String key = message.getKey();
+ //@TRACE 300=key={0} message={1}
+ log.fine(CLASS_NAME,methodName,"300",new Object[]{key, message});
+
+ saveToken(token,key);
+ } else {
+ throw closedResponse;
+ }
+ }
+ }
+
+ protected void saveToken(MqttToken token, String key) {
+ final String methodName = "saveToken";
+
+ synchronized(tokens) {
+ //@TRACE 307=key={0} token={1}
+ log.fine(CLASS_NAME,methodName,"307",new Object[]{key,token.toString()});
+ token.internalTok.setKey(key);
+ this.tokens.put(key, token);
+ }
+ }
+
+ protected void quiesce(MqttException quiesceResponse) {
+ final String methodName = "quiesce";
+
+ synchronized(tokens) {
+ //@TRACE 309=resp={0}
+ log.fine(CLASS_NAME,methodName,"309",new Object[]{quiesceResponse});
+
+ closedResponse = quiesceResponse;
+ }
+ }
+
+ public void open() {
+ final String methodName = "open";
+
+ synchronized(tokens) {
+ //@TRACE 310=>
+ log.fine(CLASS_NAME,methodName,"310");
+
+ closedResponse = null;
+ }
+ }
+
+ public MqttToken[] getOutstandingDelTokens() {
+ final String methodName = "getOutstandingDelTokens";
+
+ synchronized(tokens) {
+ //@TRACE 311=>
+ log.fine(CLASS_NAME,methodName,"311");
+
+ Vector list = new Vector();
+ Enumeration enumeration = tokens.elements();
+ MqttToken token;
+ while(enumeration.hasMoreElements()) {
+ token = (MqttToken)enumeration.nextElement();
+ if (token != null
+ && token.internalTok.isDeliveryToken() == true
+ && !token.internalTok.isNotified()) {
+
+ list.addElement(token);
+ }
+ }
+
+ MqttToken[] result = new MqttToken[list.size()];
+ return (MqttToken[]) list.toArray(result);
+ }
+ }
+
+ public Vector getOutstandingTokens() {
+ final String methodName = "getOutstandingTokens";
+
+ synchronized(tokens) {
+ //@TRACE 312=>
+ log.fine(CLASS_NAME,methodName,"312");
+
+ Vector list = new Vector();
+ Enumeration enumeration = tokens.elements();
+ MqttToken token;
+ while(enumeration.hasMoreElements()) {
+ token = (MqttToken)enumeration.nextElement();
+ if (token != null) {
+ list.addElement(token);
+ }
+ }
+ return list;
+ }
+ }
+
+ /**
+ * Empties the token store without notifying any of the tokens.
+ */
+ public void clear() {
+ final String methodName = "clear";
+ //@TRACE 305=> {0} tokens
+ log.fine(CLASS_NAME, methodName, "305", new Object[] { Integer.valueOf(tokens.size())});
+ synchronized(tokens) {
+ tokens.clear();
+ }
+ }
+
+ public int count() {
+ synchronized(tokens) {
+ return tokens.size();
+ }
+ }
+ public String toString() {
+ String lineSep = System.getProperty("line.separator","\n");
+ StringBuffer toks = new StringBuffer();
+ synchronized(tokens) {
+ Enumeration enumeration = tokens.elements();
+ MqttToken token;
+ while(enumeration.hasMoreElements()) {
+ token = (MqttToken)enumeration.nextElement();
+ toks.append("{"+token.internalTok+"}"+lineSep);
+ }
+ return toks.toString();
+ }
+ }
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/internal/ConnectActionListener.java b/src/main/java/org/eclipse/paho/mqttv5/client/internal/ConnectActionListener.java
new file mode 100644
index 0000000..bc1c3f3
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/internal/ConnectActionListener.java
@@ -0,0 +1,263 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2019 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Ian Craggs - MQTT 3.1.1 support
+ * Ian Craggs - fix bug 469527
+ * James Sutton - Automatic Reconnect & Offline Buffering
+ */
+package org.eclipse.paho.mqttv5.client.internal;
+
+import org.eclipse.paho.mqttv5.client.IMqttToken;
+import org.eclipse.paho.mqttv5.client.MqttActionListener;
+import org.eclipse.paho.mqttv5.client.MqttAsyncClient;
+import org.eclipse.paho.mqttv5.client.MqttCallback;
+import org.eclipse.paho.mqttv5.client.MqttClientPersistence;
+import org.eclipse.paho.mqttv5.client.MqttConnectionOptions;
+import org.eclipse.paho.mqttv5.client.MqttToken;
+import org.eclipse.paho.mqttv5.common.MqttException;
+import org.eclipse.paho.mqttv5.common.MqttPersistenceException;
+
+/**
+ *
+ * This class handles the connection of the AsyncClient to one of the available
+ * URLs.
+ *
+ *
+ * The URLs are supplied as either the singleton when the client is created, or
+ * as a list in the connect options.
+ *
+ *
+ * This class uses its own onSuccess and onFailure callbacks in preference to
+ * the user supplied callbacks.
+ *
+ *
+ * An attempt is made to connect to each URL in the list until either a
+ * connection attempt succeeds or all the URLs have been tried
+ *
+ *
+ * If a connection succeeds then the users token is notified and the users
+ * onSuccess callback is called.
+ *
+ *
+ * If a connection fails then another URL in the list is attempted, otherwise
+ * the users token is notified and the users onFailure callback is called
+ *
+ */
+public class ConnectActionListener implements MqttActionListener {
+
+ private MqttClientPersistence persistence;
+ private MqttAsyncClient client;
+ private ClientComms comms;
+ private MqttConnectionOptions options;
+ private MqttToken userToken;
+ private Object userContext;
+ private MqttActionListener userCallback;
+ private MqttCallback mqttCallback;
+ private MqttSessionState mqttSession;
+ private MqttConnectionState mqttConnection;
+ private boolean reconnect;
+
+ /**
+ * @param persistence
+ * The {@link MqttClientPersistence} layer
+ * @param client
+ * the {@link MqttAsyncClient}
+ * @param comms
+ * {@link ClientComms}
+ * @param options
+ * the {@link MqttConnectionOptions}
+ * @param userToken
+ * the {@link MqttToken}
+ * @param userContext
+ * the User Context Object
+ * @param userCallback
+ * the {@link MqttActionListener} as the callback for the user
+ * @param mqttSession
+ * the {@link MqttSessionState}
+ * @param mqttConnection
+ * the {@link MqttConnectionState}
+ * @param reconnect
+ * If true, this is a reconnect attempt
+ */
+ public ConnectActionListener(MqttAsyncClient client, MqttClientPersistence persistence, ClientComms comms,
+ MqttConnectionOptions options, MqttToken userToken, Object userContext, MqttActionListener userCallback,
+ boolean reconnect, MqttSessionState mqttSession, MqttConnectionState mqttConnection) {
+ this.persistence = persistence;
+ this.client = client;
+ this.comms = comms;
+ this.options = options;
+ this.userToken = userToken;
+ this.userContext = userContext;
+ this.userCallback = userCallback;
+ this.reconnect = reconnect;
+ this.mqttSession = mqttSession;
+ this.mqttConnection = mqttConnection;
+
+ }
+
+ /**
+ * If the connect succeeded then call the users onSuccess callback
+ *
+ * @param token
+ * the {@link IMqttToken} from the successful connection
+ */
+ public void onSuccess(IMqttToken token) {
+ // Set properties imposed on us by the Server
+ MqttToken myToken = (MqttToken) token;
+ if (myToken.getResponseProperties() != null) {
+ mqttConnection.setReceiveMaximum(myToken.getResponseProperties().getReceiveMaximum());
+ mqttConnection.setMaximumQoS(myToken.getResponseProperties().getMaximumQoS());
+ mqttConnection.setRetainAvailable(myToken.getResponseProperties().isRetainAvailable());
+ mqttConnection.setOutgoingMaximumPacketSize(myToken.getResponseProperties().getMaximumPacketSize());
+ mqttConnection.setIncomingMaximumPacketSize(options.getMaximumPacketSize());
+ mqttConnection.setOutgoingTopicAliasMaximum(myToken.getResponseProperties().getTopicAliasMaximum());
+ mqttConnection
+ .setWildcardSubscriptionsAvailable(myToken.getResponseProperties().isWildcardSubscriptionsAvailable());
+ mqttConnection.setSubscriptionIdentifiersAvailable(
+ myToken.getResponseProperties().isSubscriptionIdentifiersAvailable());
+ mqttConnection.setSharedSubscriptionsAvailable(myToken.getResponseProperties().isSharedSubscriptionAvailable());
+
+ // If provided, set the server keep alive value.
+ if(myToken.getResponseProperties().getServerKeepAlive() != null) {
+ mqttConnection.setKeepAliveSeconds(myToken.getResponseProperties().getServerKeepAlive());
+ }
+
+ // If we are assigning the client ID post connect, then we need to re-initialise
+ // our persistence layer.
+ if (myToken.getResponseProperties().getAssignedClientIdentifier() != null) {
+ mqttSession.setClientId(myToken.getResponseProperties().getAssignedClientIdentifier());
+ try {
+ persistence.open(myToken.getResponseProperties().getAssignedClientIdentifier());
+
+ if (options.isCleanStart()) {
+ persistence.clear();
+ }
+ } catch (MqttPersistenceException exception) {
+
+ // If we fail to open persistence at this point, our best bet is to immediately
+ // close the connection.
+ try {
+ client.disconnect();
+ } catch (MqttException ex) {
+ }
+ onFailure(token, exception);
+ return;
+ }
+ }
+ }
+
+ userToken.internalTok.markComplete(token.getResponse(), null);
+ userToken.internalTok.notifyComplete();
+ userToken.internalTok.setClient(this.client); // fix bug 469527 - maybe should be set elsewhere?
+
+ if (reconnect) {
+ comms.notifyReconnect();
+ }
+
+ if (userCallback != null) {
+ userToken.setUserContext(userContext);
+ userCallback.onSuccess(userToken);
+ }
+
+ if (mqttCallback != null) {
+ String serverURI = comms.getNetworkModules()[comms.getNetworkModuleIndex()].getServerURI();
+ try {
+ mqttCallback.connectComplete(reconnect, serverURI);
+ } catch (Throwable ex) {
+ // Just catch any exceptions thrown here and ignore.
+ }
+
+ }
+
+ }
+
+ /**
+ * The connect failed, so try the next URI on the list. If there are no more
+ * URIs, then fail the overall connect.
+ *
+ * @param token
+ * the {@link IMqttToken} from the failed connection attempt
+ * @param exception
+ * the {@link Throwable} exception from the failed connection attempt
+ */
+ public void onFailure(IMqttToken token, Throwable exception) {
+
+ int numberOfURIs = comms.getNetworkModules().length;
+ int index = comms.getNetworkModuleIndex();
+
+ if ((index + 1) < numberOfURIs) {
+
+ comms.setNetworkModuleIndex(index + 1);
+
+ try {
+ connect();
+ } catch (MqttPersistenceException e) {
+ onFailure(token, e); // try the next URI in the list
+ }
+ } else {
+
+ MqttException ex;
+ if (exception instanceof MqttException) {
+ ex = (MqttException) exception;
+ } else {
+ ex = new MqttException(exception);
+ }
+ userToken.internalTok.markComplete(null, ex);
+ userToken.internalTok.notifyComplete();
+ userToken.internalTok.setClient(this.client); // fix bug 469527 - maybe should be set elsewhere?
+
+ if (userCallback != null) {
+ userToken.setUserContext(userContext);
+ userCallback.onFailure(userToken, exception);
+ }
+ }
+ }
+
+ /**
+ * Start the connect processing
+ *
+ * @throws MqttPersistenceException
+ * if an error is thrown whilst setting up persistence
+ */
+ public void connect() throws MqttPersistenceException {
+ MqttToken token = new MqttToken(client.getClientId());
+ token.setActionCallback(this);
+ token.setUserContext(this);
+
+ if (!client.getClientId().equals("")) {
+ persistence.open(client.getClientId());
+
+ if (options.isCleanStart()) {
+ persistence.clear();
+ }
+ }
+
+ try {
+ comms.connect(options, token);
+ } catch (MqttException e) {
+ onFailure(token, e);
+ }
+ }
+
+ /**
+ * Set the MqttCallbackExtened callback to receive connectComplete callbacks
+ *
+ * @param mqttCallback
+ * the {@link MqttCallback} to be called when the connection
+ * completes
+ */
+ public void setMqttCallbackExtended(MqttCallback mqttCallback) {
+ this.mqttCallback = mqttCallback;
+ }
+
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/internal/DestinationProvider.java b/src/main/java/org/eclipse/paho/mqttv5/client/internal/DestinationProvider.java
new file mode 100644
index 0000000..7fb7510
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/internal/DestinationProvider.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.mqttv5.client.internal;
+
+import org.eclipse.paho.mqttv5.client.MqttTopic;
+
+/**
+ * This interface exists to act as a common type for
+ * MqttClient and MqttMIDPClient so they can be passed to
+ * ClientComms without either client class need to know
+ * about the other.
+ * Specifically, this allows the MIDP client to work
+ * without the non-MIDP MqttClient/MqttConnectOptions
+ * classes being present.
+ */
+public interface DestinationProvider {
+ MqttTopic getTopic(String topic);
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/internal/DisconnectedMessageBuffer.java b/src/main/java/org/eclipse/paho/mqttv5/client/internal/DisconnectedMessageBuffer.java
new file mode 100644
index 0000000..8d7a529
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/internal/DisconnectedMessageBuffer.java
@@ -0,0 +1,135 @@
+/*******************************************************************************
+ * Copyright (c) 2016, 2018 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * James Sutton - Initial Contribution for Automatic Reconnect & Offline Buffering
+ */
+package org.eclipse.paho.mqttv5.client.internal;
+
+import java.util.ArrayList;
+
+import org.eclipse.paho.mqttv5.client.BufferedMessage;
+import org.eclipse.paho.mqttv5.client.DisconnectedBufferOptions;
+import org.eclipse.paho.mqttv5.client.MqttClientException;
+import org.eclipse.paho.mqttv5.client.MqttToken;
+import org.eclipse.paho.mqttv5.client.logging.Logger;
+import org.eclipse.paho.mqttv5.client.logging.LoggerFactory;
+import org.eclipse.paho.mqttv5.common.MqttException;
+import org.eclipse.paho.mqttv5.common.packet.MqttWireMessage;
+
+public class DisconnectedMessageBuffer implements Runnable {
+
+ private static final String CLASS_NAME = DisconnectedMessageBuffer.class.getName();
+ private Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT, CLASS_NAME);
+ private DisconnectedBufferOptions bufferOpts;
+ private ArrayList buffer;
+ private final Object bufLock = new Object(); // Used to synchronise the buffer
+ private IDisconnectedBufferCallback callback;
+
+ public DisconnectedMessageBuffer(DisconnectedBufferOptions options){
+ this.bufferOpts = options;
+ buffer = new ArrayList();
+ }
+
+ /**
+ * This will add a new message to the offline buffer,
+ * if the buffer is full and deleteOldestMessages is enabled
+ * then the 0th item in the buffer will be deleted and the
+ * new message will be added. If it is not enabled then an
+ * MqttException will be thrown.
+ * @param message the {@link MqttWireMessage} that will be buffered
+ * @param token the associated {@link MqttToken}
+ * @throws MqttException if the Buffer is full
+ */
+ public void putMessage(MqttWireMessage message, MqttToken token) throws MqttException{
+ BufferedMessage bufferedMessage = new BufferedMessage(message, token);
+ synchronized (bufLock) {
+ if(buffer.size() < bufferOpts.getBufferSize()){
+ buffer.add(bufferedMessage);
+ } else if(bufferOpts.isDeleteOldestMessages() == true){
+ buffer.remove(0);
+ buffer.add(bufferedMessage);
+ }else {
+ throw new MqttException(MqttClientException.REASON_CODE_DISCONNECTED_BUFFER_FULL);
+ }
+ }
+ }
+
+ /**
+ * Retrieves a message from the buffer at the given index.
+ * @param messageIndex the index of the message to be retrieved in the buffer
+ * @return the {@link BufferedMessage}
+ */
+ public BufferedMessage getMessage(int messageIndex){
+ synchronized (bufLock) {
+ return((BufferedMessage) buffer.get(messageIndex));
+ }
+ }
+
+
+ /**
+ * Removes a message from the buffer
+ * @param messageIndex the index of the message to be deleted in the buffer
+ */
+ public void deleteMessage(int messageIndex){
+ synchronized (bufLock) {
+ buffer.remove(messageIndex);
+ }
+ }
+
+ /**
+ * Returns the number of messages currently in the buffer
+ * @return The count of messages in the buffer
+ */
+ public int getMessageCount() {
+ synchronized (bufLock) {
+ return buffer.size();
+ }
+ }
+
+ /**
+ * Flushes the buffer of messages into an open connection
+ */
+ public void run() {
+ final String methodName = "run";
+ // @TRACE 516=Restoring all buffered messages.
+ log.fine(CLASS_NAME, methodName, "516");
+ while(getMessageCount() > 0){
+ try {
+ BufferedMessage bufferedMessage = getMessage(0);
+ callback.publishBufferedMessage(bufferedMessage);
+ // Publish was successful, remove message from buffer.
+ deleteMessage(0);
+ } catch (MqttException ex) {
+ if (ex.getReasonCode() == MqttClientException.REASON_CODE_MAX_INFLIGHT) {
+ // If we get the max_inflight condition, try again after a short
+ // interval to allow more messages to be completely sent.
+ try { Thread.sleep(100); } catch (Exception e) {}
+ } else {
+ // Error occurred attempting to publish buffered message likely because the client is not connected
+ // @TRACE 519=Error occurred attempting to publish buffered message due to disconnect. Exception: {0}.
+ log.warning(CLASS_NAME, methodName, "519", new Object[]{ex.getMessage()});
+ break;
+ }
+ }
+ }
+ }
+
+ public void setPublishCallback(IDisconnectedBufferCallback callback) {
+ this.callback = callback;
+ }
+
+ public boolean isPersistBuffer(){
+ return bufferOpts.isPersistBuffer();
+ }
+
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/internal/ExceptionHelper.java b/src/main/java/org/eclipse/paho/mqttv5/client/internal/ExceptionHelper.java
new file mode 100644
index 0000000..924d19b
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/internal/ExceptionHelper.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.mqttv5.client.internal;
+
+import org.eclipse.paho.mqttv5.client.MqttClientException;
+import org.eclipse.paho.mqttv5.common.MqttException;
+import org.eclipse.paho.mqttv5.common.MqttSecurityException;
+
+/**
+ * Utility class to help create exceptions of the correct type.
+ */
+public class ExceptionHelper {
+ public static MqttException createMqttException(int reasonCode) {
+ if ((reasonCode == MqttClientException.REASON_CODE_FAILED_AUTHENTICATION) ||
+ (reasonCode == MqttClientException.REASON_CODE_NOT_AUTHORIZED)) {
+ return new MqttSecurityException(reasonCode);
+ }
+
+ return new MqttException(reasonCode);
+ }
+
+ public static MqttException createMqttException(Throwable cause) {
+ if (cause.getClass().getName().equals("java.security.GeneralSecurityException")) {
+ return new MqttSecurityException(cause);
+ }
+ return new MqttException(cause);
+ }
+
+ /**
+ * Returns whether or not the specified class is available to the current
+ * class loader. This is used to protect the code against using Java SE
+ * APIs on Java ME.
+ * @param className The name of the class
+ * @return If true, the class is available
+ */
+ public static boolean isClassAvailable(String className) {
+ boolean result = false;
+ try {
+ Class.forName(className);
+ result = true;
+ }
+ catch (ClassNotFoundException ex) {
+ }
+ return result;
+ }
+
+ // Utility classes should not have a public or default constructor.
+ private ExceptionHelper() {
+ }
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/internal/FileLock.java b/src/main/java/org/eclipse/paho/mqttv5/client/internal/FileLock.java
new file mode 100644
index 0000000..52aaca2
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/internal/FileLock.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.mqttv5.client.internal;
+/**
+ * FileLock - used to obtain a lock that can be used to prevent other MQTT clients
+ * using the same persistent store. If the lock is already held then an exception
+ * is thrown.
+ *
+ * Some Java runtimes such as JME MIDP do not support file locking or even
+ * the Java classes that support locking. The class is coded to both compile
+ * and work on all Java runtimes. In Java runtimes that do not support
+ * locking it will look as though a lock has been obtained but in reality
+ * no lock has been obtained.
+ */
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.lang.reflect.Method;
+
+public class FileLock {
+ private File lockFile;
+ private RandomAccessFile file;
+ private Object fileLock;
+
+ /**
+ * Creates an NIO FileLock on the specified file if on a suitable Java runtime.
+ * @param clientDir the a File of the directory to contain the lock file.
+ * @param lockFilename name of the the file to lock
+ * @throws Exception if the lock could not be obtained for any reason
+ */
+ public FileLock(File clientDir, String lockFilename) throws Exception {
+ // Create a file to obtain a lock on.
+ lockFile = new File(clientDir,lockFilename);
+ if (ExceptionHelper.isClassAvailable("java.nio.channels.FileLock")) {
+ try {
+ this.file = new RandomAccessFile(lockFile,"rw");
+ Method m = file.getClass().getMethod("getChannel",new Class[]{});
+ Object channel = m.invoke(file,new Object[]{});
+ m = channel.getClass().getMethod("tryLock",new Class[]{});
+ this.fileLock = m.invoke(channel, new Object[]{});
+ } catch(NoSuchMethodException nsme) {
+ this.fileLock = null;
+ } catch(IllegalArgumentException iae) {
+ this.fileLock = null;
+ } catch(IllegalAccessException iae) {
+ this.fileLock = null;
+ }
+ if (fileLock == null) {
+ // Lock not obtained
+ release();
+ throw new Exception("Problem obtaining file lock");
+ }
+ }
+ }
+
+ /**
+ * Releases the lock.
+ */
+ public void release() {
+ try {
+ if (fileLock != null) {
+ Method m = fileLock.getClass().getMethod("release",new Class[]{});
+ m.invoke(fileLock, new Object[]{});
+ fileLock = null;
+ }
+ } catch (Exception e) {
+ // Ignore exceptions
+ }
+ if (file != null) {
+ try {
+ file.close();
+ } catch (IOException e) {
+ }
+ file = null;
+ }
+
+ if (lockFile != null && lockFile.exists()) {
+ lockFile.delete();
+ }
+ lockFile = null;
+ }
+
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/internal/IDisconnectedBufferCallback.java b/src/main/java/org/eclipse/paho/mqttv5/client/internal/IDisconnectedBufferCallback.java
new file mode 100644
index 0000000..8a4717c
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/internal/IDisconnectedBufferCallback.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Copyright (c) 2016 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * James Sutton - Initial Contribution for Automatic Reconnect & Offline Buffering
+ */
+package org.eclipse.paho.mqttv5.client.internal;
+
+import org.eclipse.paho.mqttv5.client.BufferedMessage;
+import org.eclipse.paho.mqttv5.common.MqttException;
+
+public interface IDisconnectedBufferCallback {
+
+ void publishBufferedMessage(BufferedMessage bufferedMessage) throws MqttException;
+
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/internal/MessageCatalog.java b/src/main/java/org/eclipse/paho/mqttv5/client/internal/MessageCatalog.java
new file mode 100644
index 0000000..fa4de45
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/internal/MessageCatalog.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.mqttv5.client.internal;
+
+/**
+ * Catalog of human readable error messages.
+ */
+public abstract class MessageCatalog {
+ private static MessageCatalog INSTANCE = null;
+
+ public static final String getMessage(int id) {
+ if (INSTANCE == null) {
+ if (ExceptionHelper.isClassAvailable("java.util.ResourceBundle")) {
+ try {
+ // Hide this class reference behind reflection so that the class does not need to
+ // be present when compiled on midp
+ INSTANCE = (MessageCatalog)Class.forName("org.eclipse.paho.mqttv5.client.internal.ResourceBundleCatalog").newInstance();
+ } catch (Exception e) {
+ return "";
+ }
+ } else if (ExceptionHelper.isClassAvailable("org.eclipse.paho.mqttv5.client.internal.MIDPCatalog")){
+ try {
+ INSTANCE = (MessageCatalog)Class.forName("org.eclipse.paho.mqttv5.client.internal.MIDPCatalog").newInstance();
+ } catch (Exception e) {
+ return "";
+ }
+ }
+ }
+ return INSTANCE.getLocalizedMessage(id);
+ }
+
+ protected abstract String getLocalizedMessage(int id);
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/internal/MqttConnectionState.java b/src/main/java/org/eclipse/paho/mqttv5/client/internal/MqttConnectionState.java
new file mode 100644
index 0000000..e5c1495
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/internal/MqttConnectionState.java
@@ -0,0 +1,185 @@
+package org.eclipse.paho.mqttv5.client.internal;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * This class is used as a store for client information that should be preserved
+ * for a single connection.
+ * Properties returned in subsequent connect packets will override existing properties
+ * here as well.
+ *
+ * Connection variables that this class holds:
+ *
+ *
+ *
Receive Maximum
+ *
Maximum QoS
+ *
Retain Available
+ *
Maximum Packet Size
+ *
Outgoing Topic Alias Maximum
+ *
Incoming Topic Alias Maximum
+ *
Wildcard Subscriptions Available
+ *
Subscription Identifiers Available
+ *
Shared Subscriptions Available
+ *
Send Reason Messages
+ *
+ */
+public class MqttConnectionState {
+
+ // ******* Connection properties ******//
+ private Integer receiveMaximum = 65535;
+ private Integer maximumQoS = 2;
+ private Boolean retainAvailable = true;
+ private Long outgoingMaximumPacketSize = null;
+ private Long incomingMaximumPacketSize = null;
+ private Integer outgoingTopicAliasMaximum = 0;
+ private Integer incomingTopicAliasMax = 0;
+ private Boolean wildcardSubscriptionsAvailable = true;
+ private Boolean subscriptionIdentifiersAvailable = true;
+ private Boolean sharedSubscriptionsAvailable = true;
+ private boolean sendReasonMessages = false;
+ private long keepAlive = 60;
+ private String clientId = "";
+
+ // ******* Counters ******//
+ private AtomicInteger nextOutgoingTopicAlias = new AtomicInteger(1);
+
+ public MqttConnectionState(String clientId) {
+ this.clientId = clientId;
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ /**
+ * Clears the session and resets. This would be called when the connection has
+ * been lost and cleanStart = True.
+ */
+ public void clearConnectionState() {
+ nextOutgoingTopicAlias.set(1);
+ }
+
+ public Integer getReceiveMaximum() {
+ if (receiveMaximum == null) {
+ return 65535;
+ }
+ return receiveMaximum;
+ }
+
+ public void setReceiveMaximum(Integer receiveMaximum) {
+ if (receiveMaximum != null)
+ this.receiveMaximum = receiveMaximum;
+ }
+
+ public Integer getMaximumQoS() {
+ return maximumQoS;
+ }
+
+ public void setMaximumQoS(Integer maximumQoS) {
+ if (maximumQoS != null)
+ this.maximumQoS = maximumQoS;
+ }
+
+ public Boolean isRetainAvailable() {
+ return retainAvailable;
+ }
+
+ public void setRetainAvailable(Boolean retainAvailable) {
+ if (retainAvailable != null)
+ this.retainAvailable = retainAvailable;
+ }
+
+ public Long getOutgoingMaximumPacketSize() {
+ return outgoingMaximumPacketSize;
+ }
+
+ public void setOutgoingMaximumPacketSize(Long maximumPacketSize) {
+ if (maximumPacketSize != null)
+ this.outgoingMaximumPacketSize = maximumPacketSize;
+ }
+
+ public Long getIncomingMaximumPacketSize() {
+ return incomingMaximumPacketSize;
+ }
+
+
+ public void setIncomingMaximumPacketSize(Long incomingMaximumPacketSize) {
+ if (incomingMaximumPacketSize != null)
+ this.incomingMaximumPacketSize = incomingMaximumPacketSize;
+ }
+
+
+ public Integer getOutgoingTopicAliasMaximum() {
+ return outgoingTopicAliasMaximum;
+ }
+
+ public void setOutgoingTopicAliasMaximum(Integer topicAliasMaximum) {
+ if (topicAliasMaximum != null)
+ this.outgoingTopicAliasMaximum = topicAliasMaximum;
+ }
+
+ public Boolean isWildcardSubscriptionsAvailable() {
+ return wildcardSubscriptionsAvailable;
+ }
+
+ public void setWildcardSubscriptionsAvailable(Boolean wildcardSubscriptionsAvailable) {
+ if (wildcardSubscriptionsAvailable != null)
+ this.wildcardSubscriptionsAvailable = wildcardSubscriptionsAvailable;
+ }
+
+ public Boolean isSubscriptionIdentifiersAvailable() {
+ return subscriptionIdentifiersAvailable;
+ }
+
+ public void setSubscriptionIdentifiersAvailable(Boolean subscriptionIdentifiersAvailable) {
+ if (subscriptionIdentifiersAvailable != null)
+ this.subscriptionIdentifiersAvailable = subscriptionIdentifiersAvailable;
+ }
+
+ public Boolean isSharedSubscriptionsAvailable() {
+ return sharedSubscriptionsAvailable;
+ }
+
+ public void setSharedSubscriptionsAvailable(Boolean sharedSubscriptionsAvailable) {
+ if (sharedSubscriptionsAvailable != null)
+ this.sharedSubscriptionsAvailable = sharedSubscriptionsAvailable;
+ }
+
+ public Integer getNextOutgoingTopicAlias() {
+ return nextOutgoingTopicAlias.getAndIncrement();
+ }
+
+
+ public Integer getIncomingTopicAliasMax() {
+ return incomingTopicAliasMax;
+ }
+
+
+ public void setIncomingTopicAliasMax(Integer incomingTopicAliasMax) {
+ if (incomingTopicAliasMax != null)
+ this.incomingTopicAliasMax = incomingTopicAliasMax;
+ }
+
+
+ public boolean isSendReasonMessages() {
+ return sendReasonMessages;
+ }
+
+
+ public void setSendReasonMessages(boolean enableReasonMessages) {
+ this.sendReasonMessages = enableReasonMessages;
+ }
+
+
+ public long getKeepAlive() {
+ return keepAlive;
+ }
+
+
+ public void setKeepAliveSeconds(long keepAlive) {
+ this.keepAlive = keepAlive * 1000;
+ }
+
+
+
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/internal/MqttPersistentData.java b/src/main/java/org/eclipse/paho/mqttv5/client/internal/MqttPersistentData.java
new file mode 100644
index 0000000..21835b4
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/internal/MqttPersistentData.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.mqttv5.client.internal;
+
+import org.eclipse.paho.mqttv5.common.MqttPersistable;
+
+public class MqttPersistentData implements MqttPersistable {
+ // Message key
+ private String key = null;
+
+ // Message header
+ private byte[] header = null;
+ private int hOffset = 0;
+ private int hLength = 0;
+
+ // Message payload
+ private byte[] payload = null;
+ private int pOffset = 0;
+ private int pLength = 0;
+
+ /**
+ * Construct a data object to pass across the MQTT client persistence interface.
+ *
+ * When this Object is passed to the persistence implementation the key is
+ * used by the client to identify the persisted data to which further
+ * update or deletion requests are targeted.
+ * When this Object is created for returning to the client when it is
+ * recovering its state from persistence the key is not required to be set.
+ * The client can determine the key from the data.
+ * @param key The key which identifies this data
+ * @param header The message header
+ * @param hOffset The start offset of the header bytes in header.
+ * @param hLength The length of the header in the header bytes array.
+ * @param payload The message payload
+ * @param pOffset The start offset of the payload bytes in payload.
+ * @param pLength The length of the payload in the payload bytes array
+ * when persisting the message.
+ */
+ public MqttPersistentData( String key,
+ byte[] header,
+ int hOffset,
+ int hLength,
+ byte[] payload,
+ int pOffset,
+ int pLength) {
+ this.key = key;
+ this.header = header;
+ this.hOffset = hOffset;
+ this.hLength = hLength;
+ this.payload = payload;
+ this.pOffset = pOffset;
+ this.pLength = pLength;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public byte[] getHeaderBytes() {
+ return header;
+ }
+
+ public int getHeaderLength() {
+ return hLength;
+ }
+
+ public int getHeaderOffset() {
+ return hOffset;
+ }
+
+ public byte[] getPayloadBytes() {
+ return payload;
+ }
+
+ public int getPayloadLength() {
+ if ( payload == null ) {
+ return 0;
+ }
+ return pLength;
+ }
+
+ public int getPayloadOffset() {
+ return pOffset;
+ }
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/internal/MqttSessionState.java b/src/main/java/org/eclipse/paho/mqttv5/client/internal/MqttSessionState.java
new file mode 100644
index 0000000..63eb7f6
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/internal/MqttSessionState.java
@@ -0,0 +1,39 @@
+package org.eclipse.paho.mqttv5.client.internal;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * This class is used as a store for client information that should be preserved
+ * for a single MQTT Session. If the client is disconnected and reconnects with
+ * clean start = true, then this object will be reset to it's initial state.
+ *
+ * Connection variables that this class holds:
+ *
+ *
+ *
Client ID
+ *
Next Subscription Identifier - The next subscription Identifier available
+ * to use.
+ *
+ */
+public class MqttSessionState {
+
+ // ******* Session Specific Properties and counters ******//
+ private AtomicInteger nextSubscriptionIdentifier = new AtomicInteger(1);
+ private String clientId;
+
+ public void clearSessionState() {
+ nextSubscriptionIdentifier.set(1);
+ }
+
+ public Integer getNextSubscriptionIdentifier() {
+ return nextSubscriptionIdentifier.getAndIncrement();
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/internal/MqttState.java b/src/main/java/org/eclipse/paho/mqttv5/client/internal/MqttState.java
new file mode 100644
index 0000000..bd4a344
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/internal/MqttState.java
@@ -0,0 +1,114 @@
+/*******************************************************************************
+ * Copyright (c) 2016 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - Original MQTTv3 implementation
+ * James Sutton - Initial MQTTv5 implementation
+ */
+package org.eclipse.paho.mqttv5.client.internal;
+
+import java.util.Properties;
+import java.util.Vector;
+
+import org.eclipse.paho.mqttv5.client.MqttActionListener;
+import org.eclipse.paho.mqttv5.client.MqttToken;
+import org.eclipse.paho.mqttv5.common.MqttException;
+import org.eclipse.paho.mqttv5.common.packet.MqttWireMessage;
+
+public interface MqttState {
+
+ /**
+ * Submits a message for delivery. This method will block until there is
+ * room in the inFlightWindow for the message. The message is put into
+ * persistence before returning.
+ *
+ * @param message the message to send
+ * @param token the token that can be used to track delivery of the message
+ * @throws MqttException if an exception occurs whilst sending the message
+ */
+ void send(MqttWireMessage message, MqttToken token) throws MqttException;
+
+ /**
+ * Persists a buffered message to the persistence layer
+ *
+ * @param message The {@link MqttWireMessage} to persist
+ */
+ void persistBufferedMessage(MqttWireMessage message);
+
+ /**
+ * @param message The {@link MqttWireMessage} to un-persist
+ */
+ void unPersistBufferedMessage(MqttWireMessage message);
+
+ /**
+ * Check and send a ping if needed and check for ping timeout.
+ * Need to send a ping if nothing has been sent or received
+ * in the last keepalive interval. It is important to check for
+ * both sent and received packets in order to catch the case where an
+ * app is solely sending QoS 0 messages or receiving QoS 0 messages.
+ * QoS 0 message are not good enough for checking a connection is
+ * alive as they are one way messages.
+ *
+ * If a ping has been sent but no data has been received in the
+ * last keepalive interval then the connection is deamed to be broken.
+ * @param pingCallback The {@link MqttActionListener} to be called
+ * @return token of ping command, null if no ping command has been sent.
+ * @throws MqttException if an exception occurs during the Ping
+ */
+ MqttToken checkForActivity(MqttActionListener pingCallback) throws MqttException;
+
+ void notifySentBytes(int sentBytesCount);
+
+ void notifyReceivedBytes(int receivedBytesCount);
+
+ /**
+ * Called when the client has successfully connected to the broker
+ */
+ void connected();
+
+ /**
+ * Called during shutdown to work out if there are any tokens still
+ * to be notified and waiters to be unblocked. Notifying and unblocking
+ * takes place after most shutdown processing has completed. The tokenstore
+ * is tidied up so it only contains outstanding delivery tokens which are
+ * valid after reconnect (if clean session is false)
+ * @param reason The root cause of the disconnection, or null if it is a clean disconnect
+ * @return {@link Vector}
+ */
+ Vector resolveOldTokens(MqttException reason);
+
+ /**
+ * Called when the client has been disconnected from the broker.
+ * @param reason The root cause of the disconnection, or null if it is a clean disconnect
+ */
+ void disconnected(MqttException reason);
+
+ /**
+ * Quiesce the client state, preventing any new messages getting sent,
+ * and preventing the callback on any newly received messages.
+ * After the timeout expires, delete any pending messages except for
+ * outbound ACKs, and wait for those ACKs to complete.
+ * @param timeout How long to wait during Quiescing
+ */
+ void quiesce(long timeout);
+
+ void notifyQueueLock();
+
+ int getActualInFlight();
+
+ Properties getDebug();
+
+ Long getOutgoingMaximumPacketSize();
+
+ Long getIncomingMaximumPacketSize();
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/internal/NetworkModule.java b/src/main/java/org/eclipse/paho/mqttv5/client/internal/NetworkModule.java
new file mode 100644
index 0000000..b5f2e6f
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/internal/NetworkModule.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.mqttv5.client.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.eclipse.paho.mqttv5.common.MqttException;
+
+
+public interface NetworkModule {
+ void start() throws IOException, MqttException;
+
+ InputStream getInputStream() throws IOException;
+
+ OutputStream getOutputStream() throws IOException;
+
+ void stop() throws IOException;
+
+ String getServerURI();
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/internal/NetworkModuleService.java b/src/main/java/org/eclipse/paho/mqttv5/client/internal/NetworkModuleService.java
new file mode 100644
index 0000000..124b394
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/internal/NetworkModuleService.java
@@ -0,0 +1,158 @@
+/*
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ */
+package org.eclipse.paho.mqttv5.client.internal;
+
+import java.lang.reflect.Field;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ServiceLoader;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.paho.mqttv5.client.MqttConnectionOptions;
+import org.eclipse.paho.mqttv5.client.logging.Logger;
+import org.eclipse.paho.mqttv5.client.logging.LoggerFactory;
+import org.eclipse.paho.mqttv5.client.spi.NetworkModuleFactory;
+import org.eclipse.paho.mqttv5.common.MqttException;
+
+/**
+ * The NetworkModuleService uses the installed {@link NetworkModuleFactory}s to create {@link NetworkModule} instances.
+ *
+ * The selection of the appropriate NetworkModuleFactory is based on the URI scheme.
+ *
+ * @author Maik Scheibler
+ */
+public class NetworkModuleService {
+ private static Logger LOG = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT,
+ NetworkModuleService.class.getSimpleName());
+ private static final ServiceLoader FACTORY_SERVICE_LOADER = ServiceLoader.load(
+ NetworkModuleFactory.class, NetworkModuleService.class.getClassLoader());
+
+ /** Pattern to match URI authority parts: {@code authority = [userinfo"@"]host[":"port]} */
+ private static final Pattern AUTHORITY_PATTERN = Pattern.compile("((.+)@)?([^:]*)(:(\\d+))?");
+ private static final int AUTH_GROUP_USERINFO = 2;
+ private static final int AUTH_GROUP_HOST = 3;
+ private static final int AUTH_GROUP_PORT = 5;
+
+ private NetworkModuleService() {
+ // no instances
+ }
+
+ /**
+ * Validates the provided URI to be valid and that a NetworkModule is installed to serve it.
+ *
+ * @param brokerUri to be validated
+ * @throws IllegalArgumentException is case the URI is invalid or there is no {@link NetworkModule} installed for
+ * the URI scheme
+ */
+ public static void validateURI(String brokerUri) throws IllegalArgumentException {
+ try {
+ URI uri = new URI(brokerUri);
+ String scheme = uri.getScheme();
+ if (scheme == null || scheme.isEmpty()) {
+ throw new IllegalArgumentException("missing scheme in broker URI: " + brokerUri);
+ }
+ scheme = scheme.toLowerCase();
+ synchronized (FACTORY_SERVICE_LOADER) {
+ for (NetworkModuleFactory factory : FACTORY_SERVICE_LOADER) {
+ if (factory.getSupportedUriSchemes().contains(scheme)) {
+ factory.validateURI(uri);
+ return;
+ }
+ }
+ }
+ throw new IllegalArgumentException("no NetworkModule installed for scheme \"" + scheme
+ + "\" of URI \"" + brokerUri + "\"");
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Can't parse string to URI \"" + brokerUri + "\"", e);
+ }
+ }
+
+ /**
+ * Creates a {@link NetworkModule} instance for the provided address, using the given options.
+ *
+ * @param address must be a valid URI
+ * @param options used to initialize the NetworkModule
+ * @param clientId a client identifier that is unique on the server being connected to
+ * @return a new NetworkModule instance
+ * @throws MqttException if the initialization fails
+ * @throws IllegalArgumentException if the provided {@code address} is invalid
+ */
+ public static NetworkModule createInstance(String address, MqttConnectionOptions options, String clientId)
+ throws MqttException, IllegalArgumentException
+ {
+ try {
+ URI brokerUri = new URI(address);
+ applyRFC3986AuthorityPatch(brokerUri);
+ String scheme = brokerUri.getScheme().toLowerCase();
+ synchronized (FACTORY_SERVICE_LOADER) {
+ for (NetworkModuleFactory factory : FACTORY_SERVICE_LOADER) {
+ if (factory.getSupportedUriSchemes().contains(scheme)) {
+ return factory.createNetworkModule(brokerUri, options, clientId);
+ }
+ }
+ }
+ /*
+ * To throw an IllegalArgumentException exception matches the previous behavior of
+ * MqttConnectOptions.validateURI(String), but it would be nice to provide something more meaningful.
+ */
+ throw new IllegalArgumentException(brokerUri.toString());
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException(address, e);
+ }
+ }
+
+ /**
+ * Java does URI parsing according to RFC2396 and thus hostnames are limited to alphanumeric characters and '-'.
+ * But the current "Uniform Resource Identifier (URI): Generic Syntax" (RFC3986) allows for a much wider
+ * range of valid characters. This causes Java to fail parsing the authority part and thus the user-info, host and
+ * port will not be set on an URI which does not conform to RFC2396.
+ *
+ * This workaround tries to detect such a parsing failure and does tokenize the authority parts according to
+ * RFC3986, but does not enforce any character restrictions (for sake of simplicity).
+ *
+ * @param toPatch
+ * @see https://tools.ietf.org/html/rfc3986#section-3.2
+ */
+ static void applyRFC3986AuthorityPatch(URI toPatch) {
+ if (toPatch == null
+ || toPatch.getHost() != null // already successfully parsed
+ || toPatch.getAuthority() == null
+ || toPatch.getAuthority().isEmpty())
+ {
+ return;
+ }
+ Matcher matcher = AUTHORITY_PATTERN.matcher(toPatch.getAuthority());
+ if (matcher.find()) {
+ setURIField(toPatch, "userInfo", matcher.group(AUTH_GROUP_USERINFO));
+ setURIField(toPatch, "host", matcher.group(AUTH_GROUP_HOST));
+ String portString = matcher.group(AUTH_GROUP_PORT);
+ setURIField(toPatch, "port", portString != null ? Integer.parseInt(portString) : -1);
+ }
+ }
+
+ /**
+ * Reflective manipulation of a URI field to work around the URI parser, because all fields are validated even on
+ * the full qualified URI constructor.
+ *
+ * @see URI#URI(String, String, String, int, String, String, String)
+ */
+ private static void setURIField(URI toManipulate, String fieldName, Object newValue) {
+ try {
+ Field field = URI.class.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ field.set(toManipulate, newValue);
+ } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
+ LOG.warning(NetworkModuleService.class.getName(), "setURIField", "115", new Object[] {
+ toManipulate.toString() }, e);
+ }
+ }
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/internal/ResourceBundleCatalog.java b/src/main/java/org/eclipse/paho/mqttv5/client/internal/ResourceBundleCatalog.java
new file mode 100644
index 0000000..bc942d0
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/internal/ResourceBundleCatalog.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.mqttv5.client.internal;
+
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+public class ResourceBundleCatalog extends MessageCatalog {
+
+ private ResourceBundle bundle;
+
+ public ResourceBundleCatalog() {
+ //MAY throws MissingResourceException
+ bundle = ResourceBundle.getBundle("org.eclipse.paho.mqttv5.client.internal.nls.messages");
+ }
+
+ protected String getLocalizedMessage(int id) {
+ try {
+ return bundle.getString(Integer.toString(id));
+ } catch(MissingResourceException mre) {
+ return "MqttException";
+ }
+ }
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/internal/SSLNetworkModule.java b/src/main/java/org/eclipse/paho/mqttv5/client/internal/SSLNetworkModule.java
new file mode 100644
index 0000000..f588dec
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/internal/SSLNetworkModule.java
@@ -0,0 +1,161 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2019 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.mqttv5.client.internal;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SNIHostName;
+import javax.net.ssl.SNIServerName;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+
+import org.eclipse.paho.mqttv5.client.logging.Logger;
+import org.eclipse.paho.mqttv5.client.logging.LoggerFactory;
+import org.eclipse.paho.mqttv5.common.MqttException;
+
+/**
+ * A network module for connecting over SSL.
+ */
+public class SSLNetworkModule extends TCPNetworkModule {
+ private static final String CLASS_NAME = SSLNetworkModule.class.getName();
+ private Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT, CLASS_NAME);
+
+ private String[] enabledCiphers;
+ private int handshakeTimeoutSecs;
+ private HostnameVerifier hostnameVerifier;
+ private boolean httpsHostnameVerificationEnabled = true;
+
+ private String host;
+ private int port;
+
+ /**
+ * Constructs a new SSLNetworkModule using the specified host and port. The
+ * supplied SSLSocketFactory is used to supply the network socket.
+ *
+ * @param factory
+ * the {@link SSLSocketFactory} to be used in this SSLNetworkModule
+ * @param host
+ * the Hostname of the Server
+ * @param port
+ * the Port of the Server
+ * @param resourceContext
+ * Resource Context
+ */
+ public SSLNetworkModule(SSLSocketFactory factory, String host, int port, String resourceContext) {
+ super(factory, host, port, resourceContext);
+ this.host = host;
+ this.port = port;
+ log.setResourceName(resourceContext);
+ }
+
+ /**
+ * Returns the enabled cipher suites.
+ *
+ * @return a string array of enabled Cipher suites
+ */
+ public String[] getEnabledCiphers() {
+ return enabledCiphers;
+ }
+
+ /**
+ * Sets the enabled cipher suites on the underlying network socket.
+ *
+ * @param enabledCiphers
+ * a String array of cipher suites to enable
+ */
+ public void setEnabledCiphers(String[] enabledCiphers) {
+ final String methodName = "setEnabledCiphers";
+ this.enabledCiphers = enabledCiphers;
+ if ((socket != null) && (enabledCiphers != null)) {
+ if (log.isLoggable(Logger.FINE)) {
+ String ciphers = "";
+ for (int i = 0; i < enabledCiphers.length; i++) {
+ if (i > 0) {
+ ciphers += ",";
+ }
+ ciphers += enabledCiphers[i];
+ }
+ // @TRACE 260=setEnabledCiphers ciphers={0}
+ log.fine(CLASS_NAME, methodName, "260", new Object[] { ciphers });
+ }
+ ((SSLSocket) socket).setEnabledCipherSuites(enabledCiphers);
+ }
+ }
+
+ public void setSSLhandshakeTimeout(int timeout) {
+ super.setConnectTimeout(timeout);
+ this.handshakeTimeoutSecs = timeout;
+ }
+
+ public HostnameVerifier getSSLHostnameVerifier() {
+ return hostnameVerifier;
+ }
+
+ public void setSSLHostnameVerifier(HostnameVerifier hostnameVerifier) {
+ this.hostnameVerifier = hostnameVerifier;
+ }
+
+ public boolean isHttpsHostnameVerificationEnabled() {
+ return httpsHostnameVerificationEnabled;
+ }
+
+ public void setHttpsHostnameVerificationEnabled(boolean httpsHostnameVerificationEnabled) {
+ this.httpsHostnameVerificationEnabled = httpsHostnameVerificationEnabled;
+ }
+
+ public void start() throws IOException, MqttException {
+ super.start();
+ setEnabledCiphers(enabledCiphers);
+ int soTimeout = socket.getSoTimeout();
+ // RTC 765: Set a timeout to avoid the SSL handshake being blocked indefinitely
+ socket.setSoTimeout(this.handshakeTimeoutSecs * 1000);
+
+ // SNI support. Should be automatic under some circumstances - not all, apparently
+ SSLParameters sslParameters = ((SSLSocket)socket).getSSLParameters();
+ List sniHostNames = new ArrayList(1);
+ sniHostNames.add(new SNIHostName(host));
+ sslParameters.setServerNames(sniHostNames);
+
+ // If default Hostname verification is enabled, use the same method that is used with HTTPS
+ if (this.httpsHostnameVerificationEnabled) {
+ sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
+ }
+
+ ((SSLSocket) socket).setSSLParameters(sslParameters);
+
+ ((SSLSocket) socket).startHandshake();
+ if (hostnameVerifier != null) {
+ SSLSession session = ((SSLSocket) socket).getSession();
+ if(!hostnameVerifier.verify(host, session)) {
+ session.invalidate();
+ socket.close();
+ throw new SSLPeerUnverifiedException("Host: " + host + ", Peer Host: " + session.getPeerHost());
+ }
+ }
+ // reset timeout to default value
+ socket.setSoTimeout(soTimeout);
+ }
+
+ public String getServerURI() {
+ return "ssl://" + host + ":" + port;
+ }
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/internal/SSLNetworkModuleFactory.java b/src/main/java/org/eclipse/paho/mqttv5/client/internal/SSLNetworkModuleFactory.java
new file mode 100644
index 0000000..4f12260
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/internal/SSLNetworkModuleFactory.java
@@ -0,0 +1,88 @@
+/*
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ */
+package org.eclipse.paho.mqttv5.client.internal;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLSocketFactory;
+import org.eclipse.paho.mqttv5.client.MqttClientException;
+import org.eclipse.paho.mqttv5.client.MqttConnectionOptions;
+import org.eclipse.paho.mqttv5.client.security.SSLSocketFactoryFactory;
+import org.eclipse.paho.mqttv5.client.spi.NetworkModuleFactory;
+import org.eclipse.paho.mqttv5.common.MqttException;
+
+public class SSLNetworkModuleFactory implements NetworkModuleFactory {
+
+ @Override
+ public Set getSupportedUriSchemes() {
+ return Collections.unmodifiableSet(new HashSet<>(Arrays.asList("ssl")));
+ }
+
+ @Override
+ public void validateURI(URI brokerUri) throws IllegalArgumentException {
+ String path = brokerUri.getPath();
+ if (path != null && !path.isEmpty()) {
+ throw new IllegalArgumentException(brokerUri.toString());
+ }
+ }
+
+ @Override
+ public NetworkModule createNetworkModule(URI brokerUri, MqttConnectionOptions options, String clientId)
+ throws MqttException
+ {
+ String host = brokerUri.getHost();
+ int port = brokerUri.getPort(); // -1 if not defined
+ if (port == -1) {
+ port = 8883;
+ }
+ String path = brokerUri.getPath();
+ if (path != null && !path.isEmpty()) {
+ throw new IllegalArgumentException(brokerUri.toString());
+ }
+ SocketFactory factory = options.getSocketFactory();
+ SSLSocketFactoryFactory factoryFactory = null;
+ if (factory == null) {
+// try {
+ factoryFactory = new SSLSocketFactoryFactory();
+ Properties sslClientProps = options.getSSLProperties();
+ if (null != sslClientProps) {
+ factoryFactory.initialize(sslClientProps, null);
+ }
+ factory = factoryFactory.createSocketFactory(null);
+// }
+// catch (MqttDirectException ex) {
+// throw ExceptionHelper.createMqttException(ex.getCause());
+// }
+ } else if ((factory instanceof SSLSocketFactory) == false) {
+ throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_SOCKET_FACTORY_MISMATCH);
+ }
+
+ // Create the network module...
+ SSLNetworkModule netModule = new SSLNetworkModule((SSLSocketFactory) factory, host, port, clientId);
+ netModule.setSSLhandshakeTimeout(options.getConnectionTimeout());
+ netModule.setSSLHostnameVerifier(options.getSSLHostnameVerifier());
+ netModule.setHttpsHostnameVerificationEnabled(options.isHttpsHostnameVerificationEnabled());
+ // Ciphers suites need to be set, if they are available
+ if (factoryFactory != null) {
+ String[] enabledCiphers = factoryFactory.getEnabledCipherSuites(null);
+ if (enabledCiphers != null) {
+ netModule.setEnabledCiphers(enabledCiphers);
+ }
+ }
+ return netModule;
+ }
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/internal/TCPNetworkModule.java b/src/main/java/org/eclipse/paho/mqttv5/client/internal/TCPNetworkModule.java
new file mode 100644
index 0000000..48a785b
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/internal/TCPNetworkModule.java
@@ -0,0 +1,133 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2018 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.mqttv5.client.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.ConnectException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+
+import javax.net.SocketFactory;
+
+import org.eclipse.paho.mqttv5.client.MqttClientException;
+import org.eclipse.paho.mqttv5.client.logging.Logger;
+import org.eclipse.paho.mqttv5.client.logging.LoggerFactory;
+import org.eclipse.paho.mqttv5.common.MqttException;
+
+/**
+ * A network module for connecting over TCP.
+ */
+public class TCPNetworkModule implements NetworkModule {
+ private static final String CLASS_NAME = TCPNetworkModule.class.getName();
+ private Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT,CLASS_NAME);
+
+ protected Socket socket;
+ private SocketFactory factory;
+ private String host;
+ private int port;
+ private int conTimeout;
+
+ /**
+ * Constructs a new TCPNetworkModule using the specified host and
+ * port. The supplied SocketFactory is used to supply the network
+ * socket.
+ * @param factory the {@link SocketFactory} to be used to set up this connection
+ * @param host The server hostname
+ * @param port The server port
+ * @param resourceContext The Resource Context
+ */
+ public TCPNetworkModule(SocketFactory factory, String host, int port, String resourceContext) {
+ log.setResourceName(resourceContext);
+ this.factory = factory;
+ this.host = host;
+ this.port = port;
+
+ }
+
+ /**
+ * Starts the module, by creating a TCP socket to the server.
+ * @throws IOException if there is an error creating the socket
+ * @throws MqttException if there is an error connecting to the server
+ */
+ public void start() throws IOException, MqttException {
+ final String methodName = "start";
+ try {
+ // @TRACE 252=connect to host {0} port {1} timeout {2}
+ log.fine(CLASS_NAME,methodName, "252", new Object[] {host, Integer.valueOf(port), Long.valueOf(conTimeout*1000)});
+ SocketAddress sockaddr = new InetSocketAddress(host, port);
+ socket = factory.createSocket();
+ socket.connect(sockaddr, conTimeout*1000);
+ socket.setSoTimeout(1000);
+ }
+ catch (ConnectException ex) {
+ //@TRACE 250=Failed to create TCP socket
+ log.fine(CLASS_NAME,methodName,"250",null,ex);
+ throw new MqttException(MqttClientException.REASON_CODE_SERVER_CONNECT_ERROR, ex);
+ }
+ }
+
+ public InputStream getInputStream() throws IOException {
+ return socket.getInputStream();
+ }
+
+ public OutputStream getOutputStream() throws IOException {
+ return socket.getOutputStream();
+ }
+
+ /**
+ * Stops the module, by closing the TCP socket.
+ * @throws IOException if there is an error closing the socket
+ */
+ public void stop() throws IOException {
+ if (socket != null) {
+ // CDA: an attempt is made to stop the receiver cleanly before closing the socket.
+ // If the socket is forcibly closed too early, the blocking socket read in
+ // the receiver thread throws a SocketException.
+ // While this causes the receiver thread to exit, it also invalidates the
+ // SSL session preventing to perform an accelerated SSL handshake in the
+ // next connection.
+ //
+ // Also note that due to the blocking socket reads in the receiver thread,
+ // it's not possible to interrupt the thread. Using non blocking reads in
+ // combination with a socket timeout (see setSoTimeout()) would be a better approach.
+ //
+ // Please note that the Javadoc only says that an EOF is returned on
+ // subsequent reads of the socket stream.
+ // Anyway, at least with Oracle Java SE 7 on Linux systems, this causes a blocked read
+ // to return EOF immediately.
+ // This workaround should not cause any harm in general but you might
+ // want to move it in SSLNetworkModule.
+
+ socket.shutdownInput();
+ socket.close();
+ }
+ }
+
+ /**
+ * Set the maximum time to wait for a socket to be established
+ * @param timeout The connection timeout
+ */
+ public void setConnectTimeout(int timeout) {
+ this.conTimeout = timeout;
+ }
+
+ public String getServerURI() {
+ return "tcp://" + host + ":" + port;
+ }
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/internal/TCPNetworkModuleFactory.java b/src/main/java/org/eclipse/paho/mqttv5/client/internal/TCPNetworkModuleFactory.java
new file mode 100644
index 0000000..4c88601
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/internal/TCPNetworkModuleFactory.java
@@ -0,0 +1,64 @@
+/*
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ */
+package org.eclipse.paho.mqttv5.client.internal;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLSocketFactory;
+import org.eclipse.paho.mqttv5.client.MqttClientException;
+import org.eclipse.paho.mqttv5.client.MqttConnectionOptions;
+import org.eclipse.paho.mqttv5.client.spi.NetworkModuleFactory;
+import org.eclipse.paho.mqttv5.common.MqttException;
+
+public class TCPNetworkModuleFactory implements NetworkModuleFactory {
+
+ @Override
+ public Set getSupportedUriSchemes() {
+ return Collections.unmodifiableSet(new HashSet<>(Arrays.asList("tcp")));
+ }
+
+ @Override
+ public void validateURI(URI brokerUri) throws IllegalArgumentException {
+ String path = brokerUri.getPath();
+ if (path != null && !path.isEmpty()) {
+ throw new IllegalArgumentException("URI path must be empty \"" + brokerUri.toString() + "\"");
+ }
+ }
+
+ @Override
+ public NetworkModule createNetworkModule(URI brokerUri, MqttConnectionOptions options, String clientId)
+ throws MqttException
+ {
+ String host = brokerUri.getHost();
+ int port = brokerUri.getPort(); // -1 if not defined
+ if (port == -1) {
+ port = 1883;
+ }
+ String path = brokerUri.getPath();
+ if (path != null && !path.isEmpty()) {
+ throw new IllegalArgumentException(brokerUri.toString());
+ }
+ SocketFactory factory = options.getSocketFactory();
+ if (factory == null) {
+ factory = SocketFactory.getDefault();
+ } else if (factory instanceof SSLSocketFactory) {
+ throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_SOCKET_FACTORY_MISMATCH);
+ }
+ TCPNetworkModule networkModule = new TCPNetworkModule(factory, host, port, clientId);
+ networkModule.setConnectTimeout(options.getConnectionTimeout());
+ return networkModule;
+ }
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/internal/Token.java b/src/main/java/org/eclipse/paho/mqttv5/client/internal/Token.java
new file mode 100644
index 0000000..4648d22
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/internal/Token.java
@@ -0,0 +1,516 @@
+/*******************************************************************************
+ * Copyright (c) 2014, 2019 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Ian Craggs - MQTT 3.1.1 support
+ */
+
+package org.eclipse.paho.mqttv5.client.internal;
+
+import org.eclipse.paho.mqttv5.client.MqttActionListener;
+import org.eclipse.paho.mqttv5.client.MqttClientException;
+import org.eclipse.paho.mqttv5.client.MqttClientInterface;
+import org.eclipse.paho.mqttv5.client.logging.Logger;
+import org.eclipse.paho.mqttv5.client.logging.LoggerFactory;
+import org.eclipse.paho.mqttv5.common.MqttException;
+import org.eclipse.paho.mqttv5.common.MqttMessage;
+import org.eclipse.paho.mqttv5.common.packet.MqttAck;
+import org.eclipse.paho.mqttv5.common.packet.MqttConnAck;
+import org.eclipse.paho.mqttv5.common.packet.MqttPubAck;
+import org.eclipse.paho.mqttv5.common.packet.MqttPubComp;
+import org.eclipse.paho.mqttv5.common.packet.MqttPubRec;
+import org.eclipse.paho.mqttv5.common.packet.MqttPubRel;
+import org.eclipse.paho.mqttv5.common.packet.MqttSubAck;
+import org.eclipse.paho.mqttv5.common.packet.MqttUnsubAck;
+import org.eclipse.paho.mqttv5.common.packet.MqttWireMessage;
+
+public class Token {
+ private static final String CLASS_NAME = Token.class.getName();
+ private Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT, CLASS_NAME);
+
+ private volatile boolean completed = false;
+ private boolean pendingComplete = false;
+ private boolean sent = false;
+
+ private final Object responseLock = new Object();
+ private final Object sentLock = new Object();
+
+ protected MqttMessage message = null;
+ private MqttWireMessage response = null;
+ private MqttWireMessage request = null;
+ private MqttException exception = null;
+ private String[] topics = null;
+
+ private String key;
+
+ private MqttClientInterface client = null;
+ private MqttActionListener callback = null;
+
+ private Object userContext = null;
+
+ private int messageID = 0;
+ private boolean notified = false;
+
+ private int[] reasonCodes = null;
+
+ private boolean deliveryToken = false;
+
+ public Token(String logContext) {
+ log.setResourceName(logContext);
+ }
+
+ public int getMessageID() {
+ return messageID;
+ }
+
+ public void setMessageID(int messageID) {
+ this.messageID = messageID;
+ }
+
+ public void setDeliveryToken(boolean deliveryToken) {
+ this.deliveryToken = deliveryToken;
+ }
+
+ public boolean isDeliveryToken() {
+ return this.deliveryToken;
+ }
+
+ public boolean checkResult() throws MqttException {
+ if (getException() != null) {
+ throw getException();
+ }
+ return true;
+ }
+
+ public MqttException getException() {
+ return exception;
+ }
+
+ public boolean isComplete() {
+ return completed;
+ }
+
+ protected boolean isCompletePending() {
+ return pendingComplete;
+ }
+
+ protected boolean isInUse() {
+ return (getClient() != null && !isComplete());
+ }
+
+ public void setActionCallback(MqttActionListener listener) {
+ this.callback = listener;
+
+ }
+
+ public MqttActionListener getActionCallback() {
+ return callback;
+ }
+
+ public void waitForCompletion() throws MqttException {
+ waitForCompletion(-1);
+ }
+
+ public void waitForCompletion(long timeout) throws MqttException {
+ final String methodName = "waitForCompletion";
+ // @TRACE 407=key={0} wait max={1} token={2}
+ log.fine(CLASS_NAME, methodName, "407", new Object[] { getKey(), Long.valueOf(timeout), this });
+
+ MqttWireMessage resp = null;
+ try {
+ resp = waitForResponse(timeout);
+ } catch (MqttException e) {
+ if (e.getReasonCode() != MqttClientException.REASON_CODE_CLIENT_DISCONNECTING /*||
+ message.getId() != MqttWireMessage.MESSAGE_TYPE_DISCONNECT*/) {
+ throw e;
+ }
+ }
+ if (resp == null && !completed) {
+ // @TRACE 406=key={0} timed out token={1}
+ log.fine(CLASS_NAME, methodName, "406", new Object[] { getKey(), this });
+ exception = new MqttException(MqttClientException.REASON_CODE_CLIENT_TIMEOUT);
+ throw exception;
+ }
+ checkResult();
+ }
+
+ /**
+ * Waits for the message delivery to complete, but doesn't throw an exception in
+ * the case of a NACK. It does still throw an exception if something else goes
+ * wrong (e.g. an IOException). This is used for packets like CONNECT, which
+ * have useful information in the ACK that needs to be accessed.
+ *
+ * @return the {@link MqttWireMessage}
+ * @throws MqttException
+ * if there is an error whilst waiting for the response
+ */
+ protected MqttWireMessage waitForResponse() throws MqttException {
+ return waitForResponse(-1);
+ }
+
+ protected MqttWireMessage waitForResponse(long timeout) throws MqttException {
+ final String methodName = "waitForResponse";
+ synchronized (responseLock) {
+ // @TRACE 400=>key={0} timeout={1} sent={2} completed={3} hasException={4}
+ // response={5} token={6}
+ log.fine(
+ CLASS_NAME, methodName, "400", new Object[] { getKey(), Long.valueOf(timeout), Boolean.valueOf(sent),
+ Boolean.valueOf(completed), (exception == null) ? "false" : "true", response, this },
+ exception);
+
+ while (!this.completed) {
+ if (this.exception == null) {
+ try {
+ // @TRACE 408=key={0} wait max={1}
+ log.fine(CLASS_NAME, methodName, "408", new Object[] { getKey(), Long.valueOf(timeout) });
+
+ if (timeout <= 0) {
+ responseLock.wait();
+ } else {
+ responseLock.wait(timeout);
+ }
+ } catch (InterruptedException e) {
+ exception = new MqttException(e);
+ }
+ }
+ if (!this.completed) {
+ if (this.exception != null) {
+ // @TRACE 401=failed with exception
+ log.fine(CLASS_NAME, methodName, "401", null, exception);
+ throw exception;
+ }
+
+ if (timeout > 0) {
+ // time up and still not completed
+ break;
+ }
+ }
+ }
+ }
+ // @TRACE 402=key={0} response={1}
+ log.fine(CLASS_NAME, methodName, "402", new Object[] { getKey(), this.response });
+ return this.response;
+ }
+
+ /**
+ * Update the token with any new reason codes, currently only used for PubRec
+ *
+ * @param msg
+ * response message.
+ * @param ex
+ * if there was a problem store the exception in the token.
+ */
+ protected void update(MqttWireMessage msg, MqttException ex) {
+ final String methodName = "markComplete";
+ // @TRACE 411=>key={0} response={1} excep={2}
+ log.fine(CLASS_NAME, methodName, "411", new Object[] { getKey(), msg, ex });
+
+ synchronized (responseLock){
+ if(msg instanceof MqttPubRec) {
+ if(msg.getReasonCodes() != null) {
+ updateReasonCodes(msg.getReasonCodes());
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Updates the reasonCodes Array and extends it with any new reason codes.
+ * This allows message flows like Qos 2 to combine reason codes together across multiple messages e.g. PubRec and PubComp
+ * @param newReasonCodes - The additional Reason Codes.
+ */
+ protected void updateReasonCodes(int[] newReasonCodes) {
+ if(this.reasonCodes == null) {
+ this.reasonCodes = newReasonCodes;
+ } else {
+ int[] updatedReasonCodes = new int[this.reasonCodes.length + newReasonCodes.length];
+ System.arraycopy(this.reasonCodes, 0, updatedReasonCodes, 0, this.reasonCodes.length);
+ System.arraycopy(newReasonCodes, 0, updatedReasonCodes, this.reasonCodes.length, newReasonCodes.length);
+ this.reasonCodes = updatedReasonCodes;
+ }
+ }
+
+ /**
+ * Mark the token as complete and ready for users to be notified.
+ *
+ * @param msg
+ * response message. Optional - there are no response messages for
+ * some flows
+ * @param ex
+ * if there was a problem store the exception in the token.
+ */
+ protected void markComplete(MqttWireMessage msg, MqttException ex) {
+ final String methodName = "markComplete";
+ // @TRACE 404=>key={0} response={1} excep={2}
+ log.fine(CLASS_NAME, methodName, "404", new Object[] { getKey(), msg, ex });
+
+ synchronized (responseLock) {
+ // If reason codes are available, store them here.
+ if (msg instanceof MqttPubAck || msg instanceof MqttPubComp || msg instanceof MqttPubRec
+ || msg instanceof MqttPubRel || msg instanceof MqttSubAck || msg instanceof MqttUnsubAck) {
+ if (msg.getReasonCodes() != null) {
+ updateReasonCodes(msg.getReasonCodes());
+ }
+ }
+
+ // ACK means that everything was OK, so mark the message for garbage collection.
+ if (msg instanceof MqttAck) {
+ this.message = null;
+ }
+
+ this.pendingComplete = true;
+ this.response = msg;
+ this.exception = ex;
+ }
+ }
+
+ /**
+ * Notifies this token that a response message (an ACK or NACK) has been
+ * received.
+ */
+ protected void notifyComplete() {
+ final String methodName = "notifyComplete";
+ // @TRACE 411=>key={0} response={1} excep={2}
+ log.fine(CLASS_NAME, methodName, "404", new Object[] { getKey(), this.response, this.exception });
+
+ synchronized (responseLock) {
+ // If pending complete is set then normally the token can be marked
+ // as complete and users notified. An abnormal error may have
+ // caused the client to shutdown beween pending complete being set
+ // and notifying the user. In this case - the action must be failed.
+ if (exception == null && pendingComplete) {
+ completed = true;
+ pendingComplete = false;
+ } else {
+ pendingComplete = false;
+ }
+
+ responseLock.notifyAll();
+ }
+ synchronized (sentLock) {
+ sent = true;
+ sentLock.notifyAll();
+ }
+ }
+
+ // /**
+ // * Notifies this token that an exception has occurred. This is only
+ // * used for things like IOException, and not for MQTT NACKs.
+ // */
+ // protected void notifyException() {
+ // final String methodName = "notifyException";
+ // //@TRACE 405=token={0} excep={1}
+ // log.fine(CLASS_NAME,methodName, "405",new Object[]{this,this.exception});
+ // synchronized (responseLock) {
+ // responseLock.notifyAll();
+ // }
+ // synchronized (sentLock) {
+ // sentLock.notifyAll();
+ // }
+ // }
+
+ public void waitUntilSent() throws MqttException {
+ final String methodName = "waitUntilSent";
+ synchronized (sentLock) {
+ synchronized (responseLock) {
+ if (this.exception != null) {
+ throw this.exception;
+ }
+ }
+ while (!sent) {
+ try {
+ // @TRACE 409=wait key={0}
+ log.fine(CLASS_NAME, methodName, "409", new Object[] { getKey() });
+
+ sentLock.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+
+ while (!sent) {
+ if (this.exception == null) {
+ throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_UNEXPECTED_ERROR);
+ }
+ throw this.exception;
+ }
+ }
+ }
+
+ /**
+ * Notifies this token that the associated message has been sent (i.e. written
+ * to the TCP/IP socket).
+ */
+ protected void notifySent() {
+ final String methodName = "notifySent";
+ // @TRACE 403=> key={0}
+ log.fine(CLASS_NAME, methodName, "403", new Object[] { getKey() });
+ synchronized (responseLock) {
+ this.response = null;
+ this.completed = false;
+ }
+ synchronized (sentLock) {
+ sent = true;
+ sentLock.notifyAll();
+ }
+ }
+
+ public MqttClientInterface getClient() {
+ return client;
+ }
+
+ protected void setClient(MqttClientInterface client) {
+ this.client = client;
+ }
+
+ public void reset() throws MqttException {
+ final String methodName = "reset";
+ if (isInUse()) {
+ // Token is already in use - cannot reset
+ throw new MqttException(MqttClientException.REASON_CODE_TOKEN_INUSE);
+ }
+ // @TRACE 410=> key={0}
+ log.fine(CLASS_NAME, methodName, "410", new Object[] { getKey() });
+
+ client = null;
+ completed = false;
+ response = null;
+ sent = false;
+ exception = null;
+ userContext = null;
+ }
+
+ public MqttMessage getMessage() {
+ return message;
+ }
+
+ public MqttWireMessage getWireMessage() {
+ return response;
+ }
+
+ public void setMessage(MqttMessage msg) {
+ this.message = msg;
+ }
+
+ public String[] getTopics() {
+ return topics;
+ }
+
+ public void setTopics(String[] topics) {
+ this.topics = topics;
+ }
+
+ public Object getUserContext() {
+ return userContext;
+ }
+
+ public void setUserContext(Object userContext) {
+ this.userContext = userContext;
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setException(MqttException exception) {
+ synchronized (responseLock) {
+ this.exception = exception;
+ }
+ }
+
+ public boolean isNotified() {
+ return notified;
+ }
+
+ public void setNotified(boolean notified) {
+ this.notified = notified;
+ }
+
+ public String toString() {
+ StringBuffer tok = new StringBuffer();
+ tok.append("key=").append(getKey());
+ tok.append(" ,topics=");
+ if (getTopics() != null) {
+ for (int i = 0; i < getTopics().length; i++) {
+ tok.append(getTopics()[i]).append(", ");
+ }
+ }
+ tok.append(" ,usercontext=").append(getUserContext());
+ tok.append(" ,isComplete=").append(isComplete());
+ tok.append(" ,isNotified=").append(isNotified());
+ tok.append(" ,exception=").append(getException());
+ tok.append(" ,actioncallback=").append(getActionCallback());
+
+ return tok.toString();
+ }
+
+ public int[] getGrantedQos() {
+ int[] val = new int[0];
+ if (response instanceof MqttSubAck) {
+ // TODO - Work out how to map multiple returncodes
+ if (response != null) {
+ val = ((MqttSubAck) response).getReturnCodes();
+ }
+ }
+ return val;
+ }
+
+ public boolean getSessionPresent() {
+ boolean val = false;
+ if (response instanceof MqttConnAck) {
+ if (response != null) {
+ val = ((MqttConnAck) response).getSessionPresent();
+ }
+ }
+ return val;
+ }
+
+ public MqttWireMessage getResponse() {
+ return response;
+ }
+
+ /**
+ * Returns the reason codes from the MqttWireMessage.
+ * These will be present if the messages is of the following types:
+ *
+ *
CONNACK - 1 Reason Code Max.
+ *
PUBACK - 1 Reason Code Max.
+ *
PUBREC - 1 Reason Code Max.
+ *
PUBCOMP - 1 Reason Code Max.
+ *
PUBREL - 1 Reason Code Max.
+ *
SUBACK - 1 or more Reason Codes.
+ *
UNSUBACK - 1 or more Reason Codes.
+ *
AUTH - 1 Reason Code Max.
+ *
+ *
+ * Warning: This method may be removed in favour of MqttWireMessage.getReasonCodes()
+ *
+ * May be null if this message does not contain any Reason Codes.
+ * @return An array of return codes, or null.
+ */
+ public int[] getReasonCodes() {
+ return reasonCodes;
+ }
+
+ public void setRequestMessage(MqttWireMessage requestMessage) {
+ this.request = requestMessage;
+ }
+
+ public MqttWireMessage getRequestMessage() {
+ return this.request;
+ }
+
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/logging/JSR47Logger.java b/src/main/java/org/eclipse/paho/mqttv5/client/logging/JSR47Logger.java
new file mode 100644
index 0000000..8071d7b
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/logging/JSR47Logger.java
@@ -0,0 +1,278 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.mqttv5.client.logging;
+import java.text.MessageFormat;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+import java.util.logging.MemoryHandler;
+
+/**
+ * Implementation of the the logger interface that uses java.uti.logging
+ *
+ * A Logger that utilises Java's built in logging facility - java.util.logging.
+ *
A sample java.util.logging properties file - jsr47min.properties is provided that demonstrates
+ * how to run with a memory based trace facility that runs with minimal performance
+ * overhead. The memory buffer can be dumped when a log/trace record is written matching
+ * the MemoryHandlers trigger level or when the push method is invoked on the MemoryHandler.
+ * {@link org.eclipse.paho.mqttv5.client.util.Debug Debug} provides method to make it easy
+ * to dump the memory buffer as well as other useful debug info.
+ */
+public class JSR47Logger implements Logger {
+ private java.util.logging.Logger julLogger = null;
+ private ResourceBundle logMessageCatalog = null;
+ private ResourceBundle traceMessageCatalog = null;
+ private String catalogID = null;
+ private String resourceName = null;
+ private String loggerName = null;
+
+ /**
+ *
+ * @param logMsgCatalog The resource bundle associated with this logger
+ * @param loggerID The suffix for the loggerName (will be appeneded to org.eclipse.paho.mqttv5.client
+ * @param resourceContext A context for the logger e.g. clientID or appName...
+ */
+ public void initialise(ResourceBundle logMsgCatalog, String loggerID, String resourceContext ) {
+ this.traceMessageCatalog = logMessageCatalog;
+ this.resourceName = resourceContext;
+// loggerName = "org.eclipse.paho.mqttv5.client." + ((null == loggerID || 0 == loggerID.length()) ? "internal" : loggerID);
+ loggerName = loggerID;
+ this.julLogger = java.util.logging.Logger.getLogger(loggerName);
+ this.logMessageCatalog = logMsgCatalog;
+ this.traceMessageCatalog = logMsgCatalog;
+ this.catalogID = logMessageCatalog.getString("0");
+
+ }
+
+ public void setResourceName(String logContext) {
+ this.resourceName = logContext;
+ }
+
+ public boolean isLoggable(int level) {
+ return julLogger.isLoggable(mapJULLevel(level)); // || InternalTracer.isLoggable(level);
+ }
+
+ public void severe(String sourceClass, String sourceMethod, String msg) {
+ log(SEVERE, sourceClass, sourceMethod, msg, null, null);
+ }
+
+ public void severe(String sourceClass, String sourceMethod, String msg, Object[] inserts) {
+ log(SEVERE, sourceClass, sourceMethod, msg, inserts, null);
+ }
+
+ public void severe(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable thrown) {
+ log(SEVERE, sourceClass, sourceMethod, msg, inserts, thrown);
+ }
+
+ public void warning(String sourceClass, String sourceMethod, String msg) {
+ log(WARNING, sourceClass, sourceMethod, msg, null, null);
+ }
+
+ public void warning(String sourceClass, String sourceMethod, String msg, Object[] inserts) {
+ log(WARNING, sourceClass, sourceMethod, msg, inserts, null);
+ }
+
+ public void warning(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable thrown) {
+ log(WARNING, sourceClass, sourceMethod, msg, inserts, thrown);
+ }
+
+ public void info(String sourceClass, String sourceMethod, String msg) {
+ log(INFO, sourceClass, sourceMethod, msg, null, null);
+ }
+
+ public void info(String sourceClass, String sourceMethod, String msg, Object[] inserts) {
+ log(INFO, sourceClass, sourceMethod, msg, inserts, null);
+ }
+
+ public void info(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable thrown) {
+ log(INFO, sourceClass, sourceMethod, msg, inserts, thrown);
+ }
+
+ public void config(String sourceClass, String sourceMethod, String msg) {
+ log(CONFIG, sourceClass, sourceMethod, msg, null, null);
+ }
+
+ public void config(String sourceClass, String sourceMethod, String msg, Object[] inserts) {
+ log(CONFIG, sourceClass, sourceMethod, msg, inserts, null);
+ }
+
+ public void config(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable thrown) {
+ log(CONFIG, sourceClass, sourceMethod, msg, inserts, thrown);
+ }
+
+ public void log(int level, String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable thrown) {
+// InternalTracer.log(this.catalogID, level, sourceClass, sourceMethod, msg, inserts, thrown);
+ java.util.logging.Level julLevel = mapJULLevel(level);
+ if (julLogger.isLoggable(julLevel)) {
+ logToJsr47(julLevel, sourceClass, sourceMethod, this.catalogID, this.logMessageCatalog, msg, inserts, thrown);
+ }
+ }
+
+// public void setTrace(Trace trace) {
+// InternalTracer.setTrace(trace);
+// }
+
+ public void fine(String sourceClass, String sourceMethod, String msg) {
+ trace(FINE, sourceClass, sourceMethod, msg, null, null);
+ }
+
+ public void fine(String sourceClass, String sourceMethod, String msg, Object[] inserts) {
+ trace(FINE, sourceClass, sourceMethod, msg, inserts, null);
+ }
+
+ public void fine(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable ex) {
+ trace(FINE, sourceClass, sourceMethod, msg, inserts, ex);
+ }
+
+ public void finer(String sourceClass, String sourceMethod, String msg) {
+ trace(FINER, sourceClass, sourceMethod, msg, null, null);
+ }
+
+ public void finer(String sourceClass, String sourceMethod, String msg, Object[] inserts) {
+ trace(FINER, sourceClass, sourceMethod, msg, inserts, null);
+ }
+
+ public void finer(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable ex) {
+ trace(FINER, sourceClass, sourceMethod, msg, inserts, ex);
+ }
+
+ public void finest(String sourceClass, String sourceMethod, String msg) {
+ trace(FINEST, sourceClass, sourceMethod, msg, null, null);
+ }
+
+ public void finest(String sourceClass, String sourceMethod, String msg, Object[] inserts) {
+ trace(FINEST, sourceClass, sourceMethod, msg, inserts, null);
+ }
+
+ public void finest(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable ex) {
+ trace(FINEST, sourceClass, sourceMethod, msg, inserts, ex);
+ }
+
+
+ public void trace(int level, String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable ex) {
+ java.util.logging.Level julLevel = mapJULLevel(level);
+ boolean isJULLoggable = julLogger.isLoggable(julLevel);
+// if (FINE == level || isJULLoggable || InternalTracer.isLoggable(level)) {
+// InternalTracer.traceForced(level, sourceClass, sourceMethod, msg, inserts);
+// }
+ if (isJULLoggable) {
+ logToJsr47(julLevel, sourceClass, sourceMethod, this.catalogID, this.traceMessageCatalog, msg, inserts, ex);
+ }
+ }
+
+
+ private String getResourceMessage(ResourceBundle messageCatalog, String msg) {
+ String message;
+ try {
+ message = messageCatalog.getString(msg);
+ } catch (MissingResourceException e) {
+ // This is acceptable, simply return the given msg string.
+ message = msg;
+ }
+ return message;
+ }
+
+ private void logToJsr47(java.util.logging.Level julLevel, String sourceClass, String sourceMethod, String catalogName,
+ ResourceBundle messageCatalog, String msg, Object[] inserts, Throwable thrown) {
+// LogRecord logRecord = new LogRecord(julLevel, msg);
+ String formattedWithArgs = msg;
+ if (!msg.contains("=====")) {
+ formattedWithArgs = MessageFormat.format(getResourceMessage(messageCatalog, msg), inserts);
+ }
+ LogRecord logRecord = new LogRecord(julLevel, resourceName + ": " +formattedWithArgs);
+
+ logRecord.setSourceClassName(sourceClass);
+ logRecord.setSourceMethodName(sourceMethod);
+ logRecord.setLoggerName(loggerName);
+// logRecord.setResourceBundleName(catalogName);
+// logRecord.setResourceBundle(messageCatalog);
+// if (null != inserts) {
+// logRecord.setParameters(inserts);
+// }
+ if (null != thrown) {
+ logRecord.setThrown(thrown);
+ }
+
+ julLogger.log(logRecord);
+ }
+
+ private java.util.logging.Level mapJULLevel(int level) {
+ java.util.logging.Level julLevel = null;
+
+ switch (level) {
+ case SEVERE:
+ julLevel = java.util.logging.Level.SEVERE;
+ break;
+ case WARNING:
+ julLevel = java.util.logging.Level.WARNING;
+ break;
+ case INFO:
+ julLevel = java.util.logging.Level.INFO;
+ break;
+ case CONFIG:
+ julLevel = java.util.logging.Level.CONFIG;
+ break;
+ case FINE:
+ julLevel = java.util.logging.Level.FINE;
+ break;
+ case FINER:
+ julLevel = java.util.logging.Level.FINER;
+ break;
+ case FINEST:
+ julLevel = java.util.logging.Level.FINEST;
+ break;
+
+ default:
+ }
+
+ return julLevel;
+ }
+
+ public String formatMessage(String msg, Object[] inserts) {
+ String formatString;
+ try {
+ formatString = logMessageCatalog.getString(msg);
+ } catch (MissingResourceException e) {
+ formatString = msg;
+ }
+ return formatString;
+ }
+
+ public void dumpTrace() {
+ dumpMemoryTrace47(julLogger);
+ }
+
+ protected static void dumpMemoryTrace47(java.util.logging.Logger logger) {
+ MemoryHandler mHand = null;
+
+ if (logger!= null) {
+ Handler[] handlers = logger.getHandlers();
+
+ for (Handler handler : handlers) {
+ if (handler instanceof MemoryHandler) {
+ synchronized (handler) {
+ mHand = ((MemoryHandler) handler);
+ mHand.push();
+ return;
+ } // synchronized (handler).
+ }
+ } // for handlers...
+ dumpMemoryTrace47(logger.getParent());
+ }
+ }
+
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/logging/Logger.java b/src/main/java/org/eclipse/paho/mqttv5/client/logging/Logger.java
new file mode 100644
index 0000000..4a5fcf7
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/logging/Logger.java
@@ -0,0 +1,579 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.mqttv5.client.logging;
+
+import java.util.ResourceBundle;
+
+/**
+ * A Logger object is used to send log and trace messages to a platform
+ * specific logging implementation. Loggers are named, using a hierarchical
+ * dot-separated name-space.
+ * Logger names can be arbitrary strings, but they should normally be based on
+ * the component or the package name of the logged component
+ *
+ * Logger objects may be obtained by calls on one of the getLogger factory
+ * methods. These will either create a new Logger or return a suitable existing
+ * Logger.
+ *
+ *
+ * The int levels define a set of standard logging levels that can be used to
+ * control logging output. The logging levels are ordered and are specified by
+ * ordered integers. Enabling logging at a given level also enables logging at
+ * all higher levels.
+ *
+ * Clients should use the the convenience methods such as severe() and fine() or
+ * one of the predefined level constants such as Logger.SEVERE and Logger.FINE
+ * with the appropriate log(int level...) or trace(int level...) methods.
+ *
+ * The levels in descending order are:
+ *
+ *
SEVERE (log - highest value)
+ *
WARNING (log)
+ *
INFO (log)
+ *
CONFIG (log)
+ *
FINE (trace)
+ *
FINER (trace)
+ *
FINEST (trace - lowest value)
+ *
+ *
+ */
+public interface Logger {
+ /**
+ * SEVERE is a message level indicating a serious failure.
+ *
+ * In general SEVERE messages should describe events that are of
+ * considerable importance and which will prevent normal program execution.
+ * They should be reasonably intelligible to end users and to system
+ * administrators.
+ */
+ int SEVERE = 1;
+ /**
+ * WARNING is a message level indicating a potential problem.
+ *
+ * In general WARNING messages should describe events that will be of
+ * interest to end users or system managers, or which indicate potential
+ * problems.
+ */
+ int WARNING = 2;
+ /**
+ * INFO is a message level for informational messages.
+ *
+ * Typically INFO messages will be written to the console or its equivalent.
+ * So the INFO level should only be used for reasonably significant messages
+ * that will make sense to end users and system admins.
+ */
+ int INFO = 3;
+ /**
+ * CONFIG is a message level for static configuration messages.
+ *
+ * CONFIG messages are intended to provide a variety of static configuration
+ * information, to assist in debugging problems that may be associated with
+ * particular configurations. For example, CONFIG message might include the
+ * CPU type, the graphics depth, the GUI look-and-feel, etc.
+ */
+ int CONFIG = 4;
+ /**
+ * FINE is a message level providing tracing information.
+ *
+ * All of FINE, FINER, and FINEST are intended for relatively detailed
+ * tracing. The exact meaning of the three levels will vary between
+ * subsystems, but in general, FINEST should be used for the most voluminous
+ * detailed output, FINER for somewhat less detailed output, and FINE for
+ * the lowest volume (and most important) messages.
+ *
+ * In general the FINE level should be used for information that will be
+ * broadly interesting to developers who do not have a specialized interest
+ * in the specific subsystem.
+ *
+ * FINE messages might include things like minor (recoverable) failures.
+ * Issues indicating potential performance problems are also worth logging
+ * as FINE.
+ */
+ int FINE = 5;
+ /**
+ * FINER indicates a fairly detailed tracing message. By default logging
+ * calls for entering, returning, or throwing an exception are traced at
+ * this level.
+ */
+ int FINER = 6;
+ /**
+ * FINEST indicates a highly detailed tracing message.
+ */
+ int FINEST = 7;
+
+ void initialise(ResourceBundle messageCatalog, String loggerID, String resourceName);
+
+ /**
+ * Set a name that can be used to provide context with each log record.
+ * This overrides the value passed in on initialise
+ * @param logContext The Log context name
+ */
+ void setResourceName(String logContext);
+
+ /**
+ * Check if a message of the given level would actually be logged by this
+ * logger. This check is based on the Loggers effective level, which may be
+ * inherited from its parent.
+ *
+ * @param level
+ * a message logging level.
+ * @return true if the given message level is currently being logged.
+ */
+ boolean isLoggable(int level);
+
+ /**
+ * Log a message, specifying source class and method, if the logger is
+ * currently enabled for the given message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used.
+ */
+ void severe(String sourceClass, String sourceMethod, String msg);
+
+ /**
+ * Log a message, specifying source class and method, with an array of
+ * object arguments, if the logger is currently enabled for the given
+ * message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used. The formatter uses java.text.MessageFormat
+ * style formatting to format parameters, so for example a format
+ * string "{0} {1}" would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message.
+ */
+ void severe(String sourceClass, String sourceMethod, String msg, Object[] inserts);
+
+ /**
+ * Log a message, specifying source class and method, with an array of
+ * object arguments and a throwable, if the logger is currently enabled for
+ * the given message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used. The formatter uses java.text.MessageFormat
+ * style formatting to format parameters, so for example a format
+ * string "{0} {1}" would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message.
+ * @param thrown
+ * Throwable associated with log message.
+ */
+ void severe(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable thrown);
+
+ /**
+ * Log a message, specifying source class and method, if the logger is
+ * currently enabled for the given message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used.
+ */
+ void warning(String sourceClass, String sourceMethod, String msg);
+
+ /**
+ * Log a message, specifying source class and method, with an array of
+ * object arguments, if the logger is currently enabled for the given
+ * message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used. The formatter uses java.text.MessageFormat
+ * style formatting to format parameters, so for example a format
+ * string "{0} {1}" would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message.
+ */
+ void warning(String sourceClass, String sourceMethod, String msg, Object[] inserts);
+
+ /**
+ * Log a message, specifying source class and method, with an array of
+ * object arguments and a throwable, if the logger is currently enabled for
+ * the given message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used. The formatter uses java.text.MessageFormat
+ * style formatting to format parameters, so for example a format
+ * string "{0} {1}" would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message.
+ * @param thrown
+ * Throwable associated with log message.
+ */
+ void warning(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable thrown);
+
+ /**
+ * Log a message, specifying source class and method, if the logger is
+ * currently enabled for the given message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used.
+ */
+ void info(String sourceClass, String sourceMethod, String msg);
+
+ /**
+ * Log a message, specifying source class and method, with an array of
+ * object arguments, if the logger is currently enabled for the given
+ * message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used. The formatter uses java.text.MessageFormat
+ * style formatting to format parameters, so for example a format
+ * string "{0} {1}" would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message.
+ */
+ void info(String sourceClass, String sourceMethod, String msg, Object[] inserts);
+
+ /**
+ * Log a message, specifying source class and method, with an array of
+ * object arguments and a throwable, if the logger is currently enabled for
+ * the given message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used. The formatter uses java.text.MessageFormat
+ * style formatting to format parameters, so for example a format
+ * string "{0} {1}" would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message.
+ * @param thrown
+ * Throwable associated with log message.
+ */
+ void info(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable thrown);
+
+ /**
+ * Log a message, specifying source class and method, if the logger is
+ * currently enabled for the given message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used.
+ */
+ void config(String sourceClass, String sourceMethod, String msg);
+
+ /**
+ * Log a message, specifying source class and method, with an array of
+ * object arguments, if the logger is currently enabled for the given
+ * message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used. The formatter uses java.text.MessageFormat
+ * style formatting to format parameters, so for example a format
+ * string "{0} {1}" would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message.
+ */
+ void config(String sourceClass, String sourceMethod, String msg, Object[] inserts);
+
+ /**
+ * Log a message, specifying source class and method, with an array of
+ * object arguments and a throwable, if the logger is currently enabled for
+ * the given message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used. The formatter uses java.text.MessageFormat
+ * style formatting to format parameters, so for example a format
+ * string "{0} {1}" would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message.
+ * @param thrown
+ * Throwable associated with log message.
+ */
+ void config(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable thrown);
+
+ /**
+ * Trace a message, specifying source class and method, if the logger is
+ * currently enabled for the given message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message catalog for the message or the actual
+ * message itself. During formatting, if the logger has a mapping
+ * for the msg string, then the msg string is replaced by the
+ * value. Otherwise the original msg string is used.
+ */
+ void fine(String sourceClass, String sourceMethod, String msg);
+
+ /**
+ * Trace a message, specifying source class and method, with an array of
+ * object arguments, if the logger is currently enabled for the given
+ * message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message catalog for the message or the actual
+ * message itself. During formatting, if the logger has a mapping
+ * for the msg string, then the msg string is replaced by the
+ * value. Otherwise the original msg string is used. The
+ * formatter uses java.text.MessageFormat style formatting to
+ * format parameters, so for example a format string "{0} {1}"
+ * would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message.
+ */
+ void fine(String sourceClass, String sourceMethod, String msg, Object[] inserts);
+
+ void fine(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable ex);
+
+ /**
+ * Trace a message, specifying source class and method, if the logger is
+ * currently enabled for the given message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message catalog for the message or the actual
+ * message itself. During formatting, if the logger has a mapping
+ * for the msg string, then the msg string is replaced by the
+ * value. Otherwise the original msg string is used.
+ */
+ void finer(String sourceClass, String sourceMethod, String msg);
+
+ /**
+ * Trace a message, specifying source class and method, with an array of
+ * object arguments, if the logger is currently enabled for the given
+ * message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message catalog for the message or the actual
+ * message itself. During formatting, if the logger has a mapping
+ * for the msg string, then the msg string is replaced by the
+ * value. Otherwise the original msg string is used. The
+ * formatter uses java.text.MessageFormat style formatting to
+ * format parameters, so for example a format string "{0} {1}"
+ * would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message.
+ */
+ void finer(String sourceClass, String sourceMethod, String msg, Object[] inserts);
+
+ void finer(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable ex);
+
+ /**
+ * Trace a message, specifying source class and method, if the logger is
+ * currently enabled for the given message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message catalog for the message or the actual
+ * message itself. During formatting, if the logger has a mapping
+ * for the msg string, then the msg string is replaced by the
+ * value. Otherwise the original msg string is used.
+ */
+ void finest(String sourceClass, String sourceMethod, String msg);
+
+ /**
+ * Trace a message, specifying source class and method, with an array of
+ * object arguments, if the logger is currently enabled for the given
+ * message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message catalog for the message or the actual
+ * message itself. During formatting, if the logger has a mapping
+ * for the msg string, then the msg string is replaced by the
+ * value. Otherwise the original msg string is used. The
+ * formatter uses java.text.MessageFormat style formatting to
+ * format parameters, so for example a format string "{0} {1}"
+ * would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message.
+ */
+ void finest(String sourceClass, String sourceMethod, String msg, Object[] inserts);
+
+ void finest(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable ex);
+
+ /**
+ * Log a message, specifying source class and method, with an array of
+ * object arguments and a throwable, if the logger is currently enabled for
+ * the given message level.
+ *
+ * @param level
+ * One of the message level identifiers, e.g. SEVERE.
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used. The formatter uses java.text.MessageFormat
+ * style formatting to format parameters, so for example a format
+ * string "{0} {1}" would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message, may be null.
+ * @param thrown
+ * Throwable associated with log message.
+ */
+ void log(int level, String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable thrown);
+
+ /**
+ * Log a trace message, specifying source class and method, with an array of
+ * object arguments and a throwable, if the logger is currently enabled for
+ * the given message level.
+ *
+ * @param level
+ * One of the message level identifiers, e.g. SEVERE.
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message catalog for the message or the actual
+ * message itself. During formatting, if the logger has a mapping
+ * for the msg string, then the msg string is replaced by the
+ * value. Otherwise the original msg string is used. The
+ * formatter uses java.text.MessageFormat style formatting to
+ * format parameters, so for example a format string "{0} {1}"
+ * would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message, may be null.
+ * @param ex
+ * Throwable associated with log message.
+ */
+ void trace(int level, String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable ex);
+
+ /**
+ * Format a log message without causing it to be written to the log.
+ *
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used. The formatter uses java.text.MessageFormat
+ * style formatting to format parameters, so for example a format
+ * string "{0} {1}" would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message.
+ * @return The formatted message for the current locale.
+ */
+ String formatMessage(String msg, Object[] inserts);
+
+ void dumpTrace();
+}
\ No newline at end of file
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/logging/LoggerFactory.java b/src/main/java/org/eclipse/paho/mqttv5/client/logging/LoggerFactory.java
new file mode 100644
index 0000000..4ee11d3
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/logging/LoggerFactory.java
@@ -0,0 +1,155 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.mqttv5.client.logging;
+
+import java.lang.reflect.Method;
+
+/**
+ * LoggerFactory will create a logger instance ready for use by the caller.
+ *
+ * The default is to create a logger that utilises the Java's built in
+ * logging facility java.util.logging (JSR47). It is possible to override
+ * this for systems where JSR47 is not available or an alternative logging
+ * facility is needed by using setLogger and passing the the class name of
+ * a logger that implements {@link Logger}
+ */
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+/**
+ * A factory that returns a logger for use by the MQTT client.
+ *
+ * The default log and trace facility uses Java's build in log facility:-
+ * java.util.logging. For systems where this is not available or where
+ * an alternative logging framework is required the logging facility can be
+ * replaced using {@link org.eclipse.paho.mqttv5.client.logging.LoggerFactory#setLogger(String)}
+ * which takes an implementation of the {@link org.eclipse.paho.mqttv5.client.logging.Logger}
+ * interface.
+ */
+public class LoggerFactory {
+ /**
+ * Default message catalog.
+ */
+ public final static String MQTT_CLIENT_MSG_CAT = "org.eclipse.paho.mqttv5.client.internal.nls.logcat";
+ private static final String CLASS_NAME = LoggerFactory.class.getName();
+
+ private static String overrideloggerClassName = null;
+ /**
+ * Default logger that uses java.util.logging.
+ */
+ private static String jsr47LoggerClassName = JSR47Logger.class.getName();
+
+ /**
+ * Find or create a logger for a named package/class.
+ * If a logger has already been created with the given name
+ * it is returned. Otherwise a new logger is created. By default a logger
+ * that uses java.util.logging will be returned.
+ *
+ * @param messageCatalogName the resource bundle containing the logging messages.
+ * @param loggerID unique name to identify this logger.
+ * @return a suitable Logger.
+ */
+ public static Logger getLogger(String messageCatalogName, String loggerID) {
+ String loggerClassName = overrideloggerClassName;
+ Logger logger = null;
+
+ if (loggerClassName == null) {
+ loggerClassName = jsr47LoggerClassName;
+ }
+// logger = getJSR47Logger(ResourceBundle.getBundle(messageCatalogName), loggerID, null) ;
+ logger = getLogger(loggerClassName, ResourceBundle.getBundle(messageCatalogName), loggerID, null) ;
+// }
+
+ if (null == logger) {
+ throw new MissingResourceException("Error locating the logging class", CLASS_NAME, loggerID);
+ }
+
+ return logger;
+ }
+
+
+ /**
+ * Return an instance of a logger
+ *
+ * @param the class name of the load to load
+ * @param messageCatalog the resource bundle containing messages
+ * @param loggerID an identifier for the logger
+ * @param resourceName a name or context to associate with this logger instance.
+ * @return a ready for use logger
+ */
+ private static Logger getLogger(String loggerClassName, ResourceBundle messageCatalog, String loggerID, String resourceName) { //, FFDC ffdc) {
+ Logger logger = null;
+ Class logClass = null;
+
+ try {
+ logClass = Class.forName(loggerClassName);
+ } catch (NoClassDefFoundError ncdfe) {
+ return null;
+ } catch (ClassNotFoundException cnfe) {
+ return null;
+ }
+ if (null != logClass) {
+ // Now instantiate the log
+ try {
+ logger = (Logger)logClass.newInstance();
+ } catch (IllegalAccessException e) {
+ return null;
+ } catch (InstantiationException e) {
+ return null;
+ } catch (ExceptionInInitializerError e) {
+ return null;
+ } catch (SecurityException e) {
+ return null;
+ }
+ logger.initialise(messageCatalog, loggerID, resourceName);
+ }
+
+ return logger;
+ }
+
+ /**
+ * When run in JSR47, this allows access to the properties in the logging.properties
+ * file.
+ * If not run in JSR47, or the property isn't set, returns null.
+ * @param name the property to return
+ * @return the property value, or null if it isn't set or JSR47 isn't being used
+ */
+ public static String getLoggingProperty(String name) {
+ String result = null;
+ try {
+ // Hide behind reflection as java.util.logging is guaranteed to be
+ // available.
+ Class logManagerClass = Class.forName("java.util.logging.LogManager");
+ Method m1 = logManagerClass.getMethod("getLogManager", new Class[]{});
+ Object logManagerInstance = m1.invoke(null, null);
+ Method m2 = logManagerClass.getMethod("getProperty", new Class[]{String.class});
+ result = (String)m2.invoke(logManagerInstance,new Object[]{name});
+ } catch(Exception e) {
+ // Any error, assume JSR47 isn't available and return null
+ result = null;
+ }
+ return result;
+ }
+
+ /**
+ * Set the class name of the logger that the LoggerFactory will load
+ * If not set getLogger will attempt to create a logger
+ * appropriate for the platform.
+ * @param loggerClassName - Logger implementation class name to use.
+ */
+ public static void setLogger(String loggerClassName) {
+ LoggerFactory.overrideloggerClassName = loggerClassName;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/logging/SimpleLogFormatter.java b/src/main/java/org/eclipse/paho/mqttv5/client/logging/SimpleLogFormatter.java
new file mode 100644
index 0000000..6266f3f
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/logging/SimpleLogFormatter.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * Copyright (c) 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ */
+
+package org.eclipse.paho.mqttv5.client.logging;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.MessageFormat;
+import java.util.Date;
+import java.util.logging.Formatter;
+import java.util.logging.LogRecord;
+
+/**
+ * SimpleLogFormatter prints a single line
+ * log record in human readable form.
+ */
+public class SimpleLogFormatter extends Formatter {
+
+ private static final String LS = System.getProperty("line.separator");
+ /**
+ * Constructs a SimpleFormatter object.
+ */
+ public SimpleLogFormatter() {
+ super();
+ }
+
+ /**
+ * Format the logrecord as a single line with well defined columns.
+ */
+ public String format(LogRecord r) {
+ StringBuffer sb = new StringBuffer();
+ sb.append(r.getLevel().getName()).append("\t");
+ sb.append(MessageFormat.format("{0, date, yy-MM-dd} {0, time, kk:mm:ss.SSSS} ",
+ new Object[] { new Date(r.getMillis()) })+"\t");
+ String cnm = r.getSourceClassName();
+ String cn="";
+ if (cnm != null) {
+ int cnl = cnm.length();
+ if (cnl>20) {
+ cn = r.getSourceClassName().substring(cnl-19);
+ } else {
+ char[] sp = {' '};
+ StringBuffer sb1= new StringBuffer().append(cnm);
+ cn = sb1.append(sp,0, 1).toString();
+ }
+ }
+ sb.append(cn).append("\t").append(" ");
+ sb.append(left(r.getSourceMethodName(),23,' ')).append("\t");
+ sb.append(r.getThreadID()).append("\t");
+ sb.append(formatMessage(r)).append(LS);
+ if (null != r.getThrown()) {
+ sb.append("Throwable occurred: ");
+ Throwable t = r.getThrown();
+ PrintWriter pw = null;
+ try {
+ StringWriter sw = new StringWriter();
+ pw = new PrintWriter(sw);
+ t.printStackTrace(pw);
+ sb.append(sw.toString());
+ } finally {
+ if (pw != null) {
+ try {
+ pw.close();
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Left justify a string.
+ *
+ * @param s the string to justify
+ * @param width the field width to justify within
+ * @param fillChar the character to fill with
+ *
+ * @return the justified string.
+ */
+ public static String left(String s, int width, char fillChar) {
+ if (s.length() >= width) {
+ return s;
+ }
+ StringBuffer sb = new StringBuffer(width);
+ sb.append(s);
+ for (int i = width - s.length(); --i >= 0;) {
+ sb.append(fillChar);
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/logging/jsr47min.properties b/src/main/java/org/eclipse/paho/mqttv5/client/logging/jsr47min.properties
new file mode 100644
index 0000000..4009555
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/logging/jsr47min.properties
@@ -0,0 +1,83 @@
+# Properties file which configures the operation of the JDK logging facility.
+#
+# The configuration in this file is the suggesgted configuration
+# for collecting trace for helping debug problems related to the
+# Paho MQTT client. It configures trace to be continuosly collected
+# in memory with minimal impact on performance.
+#
+# When the push trigger (by default a Severe level message) or a
+# specific request is made to "push" the in memory trace then it
+# is "pushed" to the configured target handler. By default
+# this is the standard java.util.logging.FileHandler. The Paho Debug
+# class can be used to push the memory trace to its target
+#
+# To enable trace either:
+# - use this properties file as is and set the logging facility up
+# to use it by configuring the util logging system property e.g.
+#
+# >java -Djava.util.logging.config.file=\jsr47min.properties
+#
+# - This contents of this file can also be merged with another
+# java.util.logging config file to ensure provide wider logging
+# and trace including Paho trace
+
+# Global logging properties.
+# ------------------------------------------
+# The set of handlers to be loaded upon startup.
+# Comma-separated list of class names.
+# - Root handlers are not enabled by default - just handlers on the Paho packages.
+#handlers=java.util.logging.MemoryHandler,java.util.logging.FileHandler, java.util.logging.ConsoleHandler
+
+# Default global logging level.
+# Loggers and Handlers may override this level
+#.level=INFO
+
+# Loggers
+# ------------------------------------------
+# A memoryhandler is attached to the paho packages
+# and the level specified to collected all trace related
+# to paho packages. This will override any root/global
+# level handlers if set.
+org.eclipse.paho.mqttv5.client.handlers=java.util.logging.MemoryHandler
+org.eclipse.paho.mqttv5.client.level=ALL
+# It is possible to set more granular trace on a per class basis e.g.
+#org.eclipse.paho.mqttv5.client.internal.ClientComms.level=ALL
+
+# Handlers
+# -----------------------------------------
+# Note: the target handler that is associated with the MemoryHandler is not a root handler
+# and hence not returned when getting the handlers from root. It appears accessing
+# target handler programatically is not possible as target is a private variable in
+# class MemoryHandler
+java.util.logging.MemoryHandler.level=ALL
+java.util.logging.MemoryHandler.size=10000
+java.util.logging.MemoryHandler.push=ALL
+java.util.logging.MemoryHandler.target=java.util.logging.FileHandler
+#java.util.logging.MemoryHandler.target=java.util.logging.ConsoleHandler
+
+
+# --- FileHandler ---
+# Override of global logging level
+java.util.logging.FileHandler.level=ALL
+
+# Naming style for the output file:
+# (The output file is placed in the directory
+# defined by the "user.home" System property.)
+# See java.util.logging for more options
+java.util.logging.FileHandler.pattern=%h/ibm/paho/trace/paho%u.log
+
+# Limiting size of output file in bytes:
+java.util.logging.FileHandler.limit=200000
+
+# Number of output files to cycle through, by appending an
+# integer to the base file name:
+java.util.logging.FileHandler.count=3
+
+# Style of output (Simple or XML):
+java.util.logging.FileHandler.formatter=org.eclipse.paho.mqttv5.client.logging.SimpleLogFormatter
+
+# --- ConsoleHandler ---
+# Override of global logging level
+#java.util.logging.ConsoleHandler.level=INFO
+#java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
+#java.util.logging.ConsoleHandler.formatter=org.eclipse.paho.mqttv5.client.logging.SimpleLogFormatter
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/logging/package.html b/src/main/java/org/eclipse/paho/mqttv5/client/logging/package.html
new file mode 100644
index 0000000..19c41d8
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/logging/package.html
@@ -0,0 +1,5 @@
+
+Provides helpers and utilities.
+
+
+
\ No newline at end of file
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/persist/MemoryPersistence.java b/src/main/java/org/eclipse/paho/mqttv5/client/persist/MemoryPersistence.java
new file mode 100644
index 0000000..8b09b5e
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/persist/MemoryPersistence.java
@@ -0,0 +1,111 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.mqttv5.client.persist;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import org.eclipse.paho.mqttv5.client.MqttClientPersistence;
+import org.eclipse.paho.mqttv5.common.MqttPersistable;
+import org.eclipse.paho.mqttv5.common.MqttPersistenceException;
+
+/**
+ * Persistence that uses memory
+ *
+ * In cases where reliability is not required across client or device
+ * restarts memory this memory persistence can be used. In cases where
+ * reliability is required like when clean session is set to false
+ * then a non-volatile form of persistence should be used.
+ *
+ */
+public class MemoryPersistence implements MqttClientPersistence {
+
+ private Hashtable data;
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.mqttv5.client.MqttClientPersistence#close()
+ */
+ public void close() throws MqttPersistenceException {
+ if (data != null) {
+ data.clear();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.mqttv5.client.MqttClientPersistence#keys()
+ */
+ public Enumeration keys() throws MqttPersistenceException {
+ checkIsOpen();
+ return data.keys();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.mqttv5.client.MqttClientPersistence#get(java.lang.String)
+ */
+ public MqttPersistable get(String key) throws MqttPersistenceException {
+ checkIsOpen();
+ return (MqttPersistable)data.get(key);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.mqttv5.client.MqttClientPersistence#open(java.lang.String, java.lang.String)
+ */
+ public void open(String clientId) throws MqttPersistenceException {
+ this.data = new Hashtable();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.mqttv5.client.MqttClientPersistence#put(java.lang.String, org.eclipse.paho.mqttv5.client.MqttPersistable)
+ */
+ public void put(String key, MqttPersistable persistable) throws MqttPersistenceException {
+ checkIsOpen();
+ data.put(key, persistable);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.mqttv5.client.MqttClientPersistence#remove(java.lang.String)
+ */
+ public void remove(String key) throws MqttPersistenceException {
+ checkIsOpen();
+ data.remove(key);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.mqttv5.client.MqttClientPersistence#clear()
+ */
+ public void clear() throws MqttPersistenceException {
+ checkIsOpen();
+ data.clear();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.mqttv5.client.MqttClientPersistence#containsKey(java.lang.String)
+ */
+ public boolean containsKey(String key) throws MqttPersistenceException {
+ checkIsOpen();
+ return data.containsKey(key);
+ }
+
+ /**
+ * Checks whether the persistence has been opened.
+ * @throws MqttPersistenceException if the persistence has not been opened.
+ */
+ private void checkIsOpen() throws MqttPersistenceException {
+ if (data == null) {
+ throw new MqttPersistenceException();
+ }
+ }
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/persist/MqttDefaultFilePersistence.java b/src/main/java/org/eclipse/paho/mqttv5/client/persist/MqttDefaultFilePersistence.java
new file mode 100644
index 0000000..b6482a2
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/persist/MqttDefaultFilePersistence.java
@@ -0,0 +1,300 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.mqttv5.client.persist;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import org.eclipse.paho.mqttv5.client.MqttClientPersistence;
+import org.eclipse.paho.mqttv5.client.internal.FileLock;
+import org.eclipse.paho.mqttv5.client.internal.MqttPersistentData;
+import org.eclipse.paho.mqttv5.common.MqttPersistable;
+import org.eclipse.paho.mqttv5.common.MqttPersistenceException;
+
+/**
+ * An implementation of the {@link MqttClientPersistence} interface that provides
+ * file based persistence.
+ *
+ * A directory is specified when the Persistence object is created. When the persistence
+ * is then opened (see {@link #open(String)}), a sub-directory is made beneath the base
+ * for this client ID and connection key. This allows one persistence base directory
+ * to be shared by multiple clients.
+ *
+ * The sub-directory's name is created from a concatenation of the client ID and connection key
+ * with any instance of '/', '\\', ':' or ' ' removed.
+ */
+public class MqttDefaultFilePersistence implements MqttClientPersistence {
+ private static final String MESSAGE_FILE_EXTENSION = ".msg";
+ private static final String MESSAGE_BACKUP_FILE_EXTENSION = ".bup";
+ private static final String LOCK_FILENAME = ".lck";
+
+ private File dataDir;
+ private File clientDir = null;
+ private FileLock fileLock = null;
+
+ //TODO
+ private static FilenameFilter FILENAME_FILTER;
+
+ private static FilenameFilter getFilenameFilter(){
+ if(FILENAME_FILTER == null){
+ FILENAME_FILTER = new PersistenceFileNameFilter(MESSAGE_FILE_EXTENSION);
+ }
+ return FILENAME_FILTER;
+ }
+
+ public MqttDefaultFilePersistence() { //throws MqttPersistenceException {
+ this(System.getProperty("user.dir"));
+ }
+
+ /**
+ * Create an file-based persistent data store within the specified directory.
+ * @param directory the directory to use.
+ */
+ public MqttDefaultFilePersistence(String directory) { //throws MqttPersistenceException {
+ dataDir = new File(directory);
+ }
+
+ public void open(String clientId) throws MqttPersistenceException {
+
+ if (dataDir.exists() && !dataDir.isDirectory()) {
+ throw new MqttPersistenceException();
+ } else if (!dataDir.exists() ) {
+ if (!dataDir.mkdirs()) {
+ throw new MqttPersistenceException();
+ }
+ }
+ if (!dataDir.canWrite()) {
+ throw new MqttPersistenceException();
+ }
+
+
+ StringBuffer keyBuffer = new StringBuffer();
+ for (int i=0;i keys() throws MqttPersistenceException {
+ checkIsOpen();
+ File[] files = getFiles();
+ Vector result = new Vector(files.length);
+ for (File file : files) {
+ String filename = file.getName();
+ String key = filename.substring(0, filename.length() - MESSAGE_FILE_EXTENSION.length());
+ result.addElement(key);
+ }
+ return result.elements();
+ }
+
+ private File[] getFiles() throws MqttPersistenceException {
+ checkIsOpen();
+ File[] files = clientDir.listFiles(getFilenameFilter());
+ if (files == null) {
+ throw new MqttPersistenceException();
+ }
+ return files;
+ }
+
+ private boolean isSafeChar(char c) {
+ return Character.isJavaIdentifierPart(c) || c=='-';
+ }
+
+ /**
+ * Identifies any backup files in the specified directory and restores them
+ * to their original file. This will overwrite any existing file of the same
+ * name. This is safe as a stray backup file will only exist if a problem
+ * occurred whilst writing to the original file.
+ * @param dir The directory in which to scan and restore backups
+ */
+ private void restoreBackups(File dir) throws MqttPersistenceException {
+ File[] files = dir.listFiles(new PersistenceFileFilter(MESSAGE_BACKUP_FILE_EXTENSION));
+
+ if (files == null) {
+ throw new MqttPersistenceException();
+ }
+
+ for (File file : files) {
+ File originalFile = new File(dir, file.getName().substring(0, file.getName().length() - MESSAGE_BACKUP_FILE_EXTENSION.length()));
+ boolean result = file.renameTo(originalFile);
+ if (!result) {
+ originalFile.delete();
+ file.renameTo(originalFile);
+ }
+ }
+ }
+
+ public boolean containsKey(String key) throws MqttPersistenceException {
+ checkIsOpen();
+ File file = new File(clientDir, key+MESSAGE_FILE_EXTENSION);
+ return file.exists();
+ }
+
+ public void clear() throws MqttPersistenceException {
+ checkIsOpen();
+ File[] files = getFiles();
+ for (File file : files) {
+ file.delete();
+ }
+ clientDir.delete();
+ }
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/persist/PersistenceFileFilter.java b/src/main/java/org/eclipse/paho/mqttv5/client/persist/PersistenceFileFilter.java
new file mode 100644
index 0000000..54f3e82
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/persist/PersistenceFileFilter.java
@@ -0,0 +1,18 @@
+package org.eclipse.paho.mqttv5.client.persist;
+
+import java.io.File;
+import java.io.FileFilter;
+
+public class PersistenceFileFilter implements FileFilter{
+
+ private final String fileExtension;
+
+ public PersistenceFileFilter(String fileExtension){
+ this.fileExtension = fileExtension;
+ }
+
+ public boolean accept(File pathname) {
+ return pathname.getName().endsWith(fileExtension);
+ }
+
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/persist/PersistenceFileNameFilter.java b/src/main/java/org/eclipse/paho/mqttv5/client/persist/PersistenceFileNameFilter.java
new file mode 100644
index 0000000..b982353
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/persist/PersistenceFileNameFilter.java
@@ -0,0 +1,18 @@
+package org.eclipse.paho.mqttv5.client.persist;
+
+import java.io.File;
+import java.io.FilenameFilter;
+
+public class PersistenceFileNameFilter implements FilenameFilter{
+
+ private final String fileExtension;
+
+ public PersistenceFileNameFilter(String fileExtension){
+ this.fileExtension = fileExtension;
+ }
+
+ public boolean accept(File dir, String name) {
+ return name.endsWith(fileExtension);
+ }
+
+}
diff --git a/src/main/java/org/eclipse/paho/mqttv5/client/security/SSLSocketFactoryFactory.java b/src/main/java/org/eclipse/paho/mqttv5/client/security/SSLSocketFactoryFactory.java
new file mode 100644
index 0000000..d311e20
--- /dev/null
+++ b/src/main/java/org/eclipse/paho/mqttv5/client/security/SSLSocketFactoryFactory.java
@@ -0,0 +1,1358 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ * and the Eclipse Distribution License is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.mqttv5.client.security;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Properties;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import org.eclipse.paho.mqttv5.client.logging.Logger;
+import org.eclipse.paho.mqttv5.common.MqttSecurityException;
+
+
+/**
+ * An SSLSocketFactoryFactory provides a socket factory and a server socket
+ * factory that then can be used to create SSL client sockets or SSL server
+ * sockets.
+ *
+ * The SSLSocketFactoryFactory is configured using IBM SSL properties, i.e.
+ * properties of the format "com.ibm.ssl.propertyName", e.g.
+ * "com.ibm.ssl.keyStore". The class supports multiple configurations, each
+ * configuration is identified using a name or configuration ID. The
+ * configuration ID with "null" is used as a default configuration. When a
+ * socket factory is being created for a given configuration, properties of that
+ * configuration are first picked. If a property is not defined there, then that
+ * property is looked up in the default configuration. Finally, if a property
+ * element is still not found, then the corresponding system property is
+ * inspected, i.e. javax.net.ssl.keyStore. If the system property is not set
+ * either, then the system's default value is used (if available) or an
+ * exception is thrown.
+ *
+ * The SSLSocketFacotryFactory can be reconfigured at any time. A
+ * reconfiguration does not affect existing socket factories.
+ *
+ * All properties share the same key space; i.e. the configuration ID is not
+ * part of the property keys.
+ *
+ * The methods should be called in the following order:
+ *
+ *
isSupportedOnJVM(): to check whether this class is supported on
+ * the runtime platform. Not all runtimes support SSL/TLS.
+ *
SSLSocketFactoryFactory(): the constructor. Clients
+ * (in the same JVM) may share an SSLSocketFactoryFactory, or have one each.
+ *
initialize(properties, configID): to initialize this object with
+ * the required SSL properties for a configuration. This may be called multiple
+ * times, once for each required configuration.It may be called again to change the required SSL
+ * properties for a particular configuration
+ *
getEnabledCipherSuites(configID): to later set the enabled
+ * cipher suites on the socket [see below].
+ *
+ *
+ *
For an MQTT server:
+
+ *
+ *
getKeyStore(configID): Optionally, to check that if there is no
+ * keystore, then that all the enabled cipher suits are anonymous.
+ *
createServerSocketFactory(configID): to create an
+ * SSLServerSocketFactory.
+ *
getClientAuthentication(configID): to later set on the
+ * SSLServerSocket (itself created from the SSLServerSocketFactory) whether
+ * client authentication is needed.
+ *
+ *
+ *
For an MQTT client:
+ *
+ *
createSocketFactory(configID): to create an SSLSocketFactory.
+ *
+ *
+ *
+ */
+public class SSLSocketFactoryFactory {
+ private static final String CLASS_NAME = "org.eclipse.paho.mqttv5.client.internal.security.SSLSocketFactoryFactory";
+ /**
+ * Property keys specific to the client).
+ */
+ public static final String SSLPROTOCOL="com.ibm.ssl.protocol";
+ public static final String JSSEPROVIDER="com.ibm.ssl.contextProvider";
+ public static final String KEYSTORE="com.ibm.ssl.keyStore";
+ public static final String KEYSTOREPWD="com.ibm.ssl.keyStorePassword";
+ public static final String KEYSTORETYPE="com.ibm.ssl.keyStoreType";
+ public static final String KEYSTOREPROVIDER="com.ibm.ssl.keyStoreProvider";
+ public static final String KEYSTOREMGR="com.ibm.ssl.keyManager";
+ public static final String TRUSTSTORE="com.ibm.ssl.trustStore";
+ public static final String TRUSTSTOREPWD="com.ibm.ssl.trustStorePassword";
+ public static final String TRUSTSTORETYPE="com.ibm.ssl.trustStoreType";
+ public static final String TRUSTSTOREPROVIDER="com.ibm.ssl.trustStoreProvider";
+ public static final String TRUSTSTOREMGR="com.ibm.ssl.trustManager";
+ public static final String CIPHERSUITES="com.ibm.ssl.enabledCipherSuites";
+ public static final String CLIENTAUTH="com.ibm.ssl.clientAuthentication";
+
+ /**
+ * Property keys used for java system properties
+ */
+ public static final String SYSKEYSTORE="javax.net.ssl.keyStore";
+ public static final String SYSKEYSTORETYPE="javax.net.ssl.keyStoreType";
+ public static final String SYSKEYSTOREPWD="javax.net.ssl.keyStorePassword";
+ public static final String SYSTRUSTSTORE="javax.net.ssl.trustStore";
+ public static final String SYSTRUSTSTORETYPE="javax.net.ssl.trustStoreType";
+ public static final String SYSTRUSTSTOREPWD="javax.net.ssl.trustStorePassword";
+ public static final String SYSKEYMGRALGO="ssl.KeyManagerFactory.algorithm";
+ public static final String SYSTRUSTMGRALGO="ssl.TrustManagerFactory.algorithm";
+
+
+ public static final String DEFAULT_PROTOCOL = "TLS"; // "SSL_TLS" is not supported by DesktopEE
+
+ private static final String[] propertyKeys = {SSLPROTOCOL, JSSEPROVIDER,
+ KEYSTORE, KEYSTOREPWD, KEYSTORETYPE, KEYSTOREPROVIDER, KEYSTOREMGR,
+ TRUSTSTORE, TRUSTSTOREPWD, TRUSTSTORETYPE, TRUSTSTOREPROVIDER,
+ TRUSTSTOREMGR, CIPHERSUITES, CLIENTAUTH};
+
+ private Hashtable configs; // a hashtable that maps configIDs to properties.
+
+ private Properties defaultProperties;
+
+ private static final byte[] key = { (byte) 0x9d, (byte) 0xa7, (byte) 0xd9,
+ (byte) 0x80, (byte) 0x05, (byte) 0xb8, (byte) 0x89, (byte) 0x9c };
+
+ private static final String xorTag = "{xor}";
+
+ private Logger logger = null;
+
+
+ /**
+ * Not all of the JVM/Platforms support all of its
+ * security features. This method determines if is supported.
+ *
+ * @return whether dependent classes can be instantiated on the current
+ * JVM/platform.
+ *
+ * @throws Error
+ * if any unexpected error encountered whilst checking. Note
+ * this should not be a ClassNotFoundException, which should
+ * cause the method to return false.
+ */
+ public static boolean isSupportedOnJVM() throws LinkageError, ExceptionInInitializerError {
+ String requiredClassname = "javax.net.ssl.SSLServerSocketFactory";
+ try {
+ Class.forName(requiredClassname);
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * Create new instance of class.
+ * Constructor used by clients.
+ */
+ public SSLSocketFactoryFactory() {
+ configs = new Hashtable();
+ }
+
+ /**
+ * Create new instance of class.
+ * Constructor used by the broker.
+ * @param logger the {@link Logger} to be used
+ */
+ public SSLSocketFactoryFactory(Logger logger) {
+ this();
+ this.logger = logger;
+ }
+
+ /**
+ * Checks whether a key belongs to the supported IBM SSL property keys.
+ *
+ * @param key
+ * @return whether a key belongs to the supported IBM SSL property keys.
+ */
+ private boolean keyValid(String key) {
+ int i = 0;
+ while (i < propertyKeys.length) {
+ if (propertyKeys[i].equals(key)) {
+ break;
+ }
+ ++i;
+ }
+ return i < propertyKeys.length;
+ }
+
+ /**
+ * Checks whether the property keys belong to the supported IBM SSL property
+ * key set.
+ *
+ * @param properties
+ * @throws IllegalArgumentException
+ * if any of the properties is not a valid IBM SSL property key.
+ */
+ private void checkPropertyKeys(Properties properties)
+ throws IllegalArgumentException {
+ Set