/*
* 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 v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dave Locke - initial API and implementation and/or initial documentation
*/
package org.eclipse.paho.client.mqttv3;
import java.util.Hashtable;
import java.util.Properties;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import org.eclipse.paho.client.mqttv3.internal.ClientComms;
import org.eclipse.paho.client.mqttv3.internal.ConnectActionListener;
import org.eclipse.paho.client.mqttv3.internal.ExceptionHelper;
import org.eclipse.paho.client.mqttv3.internal.LocalNetworkModule;
import org.eclipse.paho.client.mqttv3.internal.NetworkModule;
import org.eclipse.paho.client.mqttv3.internal.SSLNetworkModule;
import org.eclipse.paho.client.mqttv3.internal.TCPNetworkModule;
import org.eclipse.paho.client.mqttv3.internal.security.SSLSocketFactoryFactory;
import org.eclipse.paho.client.mqttv3.internal.wire.MqttDisconnect;
import org.eclipse.paho.client.mqttv3.internal.wire.MqttPublish;
import org.eclipse.paho.client.mqttv3.internal.wire.MqttSubscribe;
import org.eclipse.paho.client.mqttv3.internal.wire.MqttUnsubscribe;
import org.eclipse.paho.client.mqttv3.logging.Logger;
import org.eclipse.paho.client.mqttv3.logging.LoggerFactory;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence;
import org.eclipse.paho.client.mqttv3.util.Debug;
/**
* Lightweight client for talking to an MQTT server using non-blocking methods
* that allow an operation to run in the background.
*
* <p>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.4.2 and up.
* </p>
* <p>An application can connect to an MQTT server using:
* <ul>
* <li>A plain TCP socket
* <li>A secure SSL/TLS socket
* </ul>
* </p>
* <p>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.
* </p>
* <p>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.
* </p>
* <p>If connecting with {@link MqttConnectOptions#setCleanSession(boolean)} set to true it
* is safe to use memory persistence as all state is cleared when a client disconnects. If
* connecting with cleanSession set to false in order to provide reliable message delivery
* then a persistent message store such as the default one should be used.
* </p>
* <p>The message store interface is pluggable. Different stores can be used by implementing
* the {@link MqttClientPersistence} interface and passing it to the clients constructor.
* </p>
*
* @see IMqttAsyncClient
*/
public class MqttAsyncClient implements IMqttAsyncClient { // DestinationProvider {
private String clientId;
private String serverURI;
protected ClientComms comms;
private Hashtable topics;
private MqttClientPersistence persistence;
final static String className = MqttAsyncClient.class.getName();
public Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT,className);
/**
* Create an MqttAsyncClient that is used to communicate with an MQTT server.
* <p>
* 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 MqttConnectOptions#setServerURIs(String[]) setServerURIs} method
* on MqttConnectOptions.
*
* <p>The <code>serverURI</code> parameter is typically used with the
* the <code>clientId</code> 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.
*
* <p>The address of the server to connect to is specified as a URI. Two types of
* connection are supported <code>tcp://</code> for a TCP connection and
* <code>ssl://</code> for a TCP connection secured by SSL/TLS.
* For example:
* <ul>
* <li><code>tcp://localhost:1883</code></li>
* <li><code>ssl://localhost:8883</code></li>
* </ul>
* If the port is not specified, it will
* default to 1883 for <code>tcp://</code>" URIs, and 8883 for <code>ssl://</code> URIs.
* </p>
*
* <p>
* A client identifier <code>clientId</code> must be specified and be less that 23 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.
* <p>A convenience method is provided to generate a random client id that
* should satisfy this criteria - {@link #generateClientId()}. 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.
* </p>
* <p>
* In Java SE, SSL can be configured in one of several ways, which the
* client will use in the following order:
* </p>
* <ul>
* <li><strong>Supplying an <code>SSLSocketFactory</code></strong> - applications can
* use {@link MqttConnectOptions#setSocketFactory(SocketFactory)} to supply
* a factory with the appropriate SSL settings.</li>
* <li><strong>SSL Properties</strong> - applications can supply SSL settings as a
* simple Java Properties using {@link MqttConnectOptions#setSSLProperties(Properties)}.</li>
* <li><strong>Use JVM settings</strong> - There are a number of standard
* Java system properties that can be used to configure key and trust stores.</li>
* </ul>
*
* <p>In Java ME, the platform settings are used for SSL connections.</p>
*
* <p>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 MqttConnectOptions#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 23 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.
* <p>
* 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 MqttConnectOptions#setServerURIs(String[]) setServerURIs} method
* on MqttConnectOptions.
*
* <p>The <code>serverURI</code> parameter is typically used with the
* the <code>clientId</code> 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.
*
* <p>The address of the server to connect to is specified as a URI. Two types of
* connection are supported <code>tcp://</code> for a TCP connection and
* <code>ssl://</code> for a TCP connection secured by SSL/TLS.
* For example:
* <ul>
* <li><code>tcp://localhost:1883</code></li>
* <li><code>ssl://localhost:8883</code></li>
* </ul>
* If the port is not specified, it will
* default to 1883 for <code>tcp://</code>" URIs, and 8883 for <code>ssl://</code> URIs.
* </p>
*
* <p>
* A client identifier <code>clientId</code> must be specified and be less that 23 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.
* <p>A convenience method is provided to generate a random client id that
* should satisfy this criteria - {@link #generateClientId()}. 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.
* </p>
* <p>
* In Java SE, SSL can be configured in one of several ways, which the
* client will use in the following order:
* </p>
* <ul>
* <li><strong>Supplying an <code>SSLSocketFactory</code></strong> - applications can
* use {@link MqttConnectOptions#setSocketFactory(SocketFactory)} to supply
* a factory with the appropriate SSL settings.</li>
* <li><strong>SSL Properties</strong> - applications can supply SSL settings as a
* simple Java Properties using {@link MqttConnectOptions#setSSLProperties(Properties)}.</li>
* <li><strong>Use JVM settings</strong> - There are a number of standard
* Java system properties that can be used to configure key and trust stores.</li>
* </ul>
*
* <p>In Java ME, the platform settings are used for SSL connections.</p>
* <p>
* 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 MqttConnectOptions#setCleanSession(boolean)} must be set
* to false. In the event that only QoS 0 messages are sent or received or
* cleanSession is set to true then a safe store is not needed.
* </p>
* <p>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 <code>null</code>.</p>
*
* @param serverURI the address of the server to connect to, specified as a URI. Can be overridden using
* {@link MqttConnectOptions#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 23 characters in length
* @throws MqttException if any other problem was encountered
*/
public MqttAsyncClient(String serverURI, String clientId, MqttClientPersistence persistence) throws MqttException {
final String methodName = "MqttAsyncClient";
log.setResourceName(clientId);
if (clientId == null || clientId.length() == 0) {
throw new IllegalArgumentException("Null or zero length clientId");
}
// Count characters, surrogate pairs count as one character.
int clientIdLength = 0;
for (int i = 0; i < clientId.length() - 1; i++) {
if (Character_isHighSurrogate(clientId.charAt(i)))
i++;
clientIdLength++;
}
if ( clientIdLength > 23) {
throw new IllegalArgumentException("ClientId longer than 23 characters");
}
MqttConnectOptions.validateURI(serverURI);
this.serverURI = serverURI;
this.clientId = clientId;
this.persistence = persistence;
if (this.persistence == null) {
this.persistence = new MemoryPersistence();
}
// @TRACE 101=<init> ClientID={0} ServerURI={1} PersistenceType={2}
log.fine(className,methodName,"101",new Object[]{clientId,serverURI,persistence});
this.persistence.open(clientId, serverURI);
this.comms = new ClientComms(this, this.persistence);
this.persistence.close();
this.topics = new Hashtable();
}
/**
* @param ch
* @return returns 'true' if the character is a high-surrogate code unit
*/
protected static boolean Character_isHighSurrogate(char ch) {
char MIN_HIGH_SURROGATE = '\uD800';
char MAX_HIGH_SURROGATE = '\uDBFF';
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.
* @return a network module appropriate to the specified address.
*/
// may need an array of these network modules
protected NetworkModule[] createNetworkModules(String address, MqttConnectOptions options) throws MqttException, MqttSecurityException {
final String methodName = "createNetworkModules";
// @TRACE 116=URI={0}
log.fine(className, 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(className, 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 Connect options
* @return a network module appropriate to the specified address.
*/
private NetworkModule createNetworkModule(String address, MqttConnectOptions options) throws MqttException, MqttSecurityException {
final String methodName = "createNetworkModule";
// @TRACE 115=URI={0}
log.fine(className,methodName, "115", new Object[] {address});
NetworkModule netModule;
String shortAddress;
String host;
int port;
SocketFactory factory = options.getSocketFactory();
int serverURIType = MqttConnectOptions.validateURI(address);
switch (serverURIType) {
case MqttConnectOptions.URI_TYPE_TCP :
shortAddress = address.substring(6);
host = getHostName(shortAddress);
port = getPort(shortAddress, 1883);
if (factory == null) {
factory = SocketFactory.getDefault();
}
else if (factory instanceof SSLSocketFactory) {
throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_SOCKET_FACTORY_MISMATCH);
}
netModule = new TCPNetworkModule(factory, host, port, clientId);
((TCPNetworkModule)netModule).setConnectTimeout(options.getConnectionTimeout());
break;
case MqttConnectOptions.URI_TYPE_SSL:
shortAddress = address.substring(6);
host = getHostName(shortAddress);
port = getPort(shortAddress, 8883);
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(MqttException.REASON_CODE_SOCKET_FACTORY_MISMATCH);
}
// Create the network module...
netModule = new SSLNetworkModule((SSLSocketFactory) factory, host, port, clientId);
((SSLNetworkModule)netModule).setSSLhandshakeTimeout(options.getConnectionTimeout());
// Ciphers suites need to be set, if they are available
if (factoryFactory != null) {
String[] enabledCiphers = factoryFactory.getEnabledCipherSuites(null);
if (enabledCiphers != null) {
((SSLNetworkModule) netModule).setEnabledCiphers(enabledCiphers);
}
}
break;
case MqttConnectOptions.URI_TYPE_LOCAL :
netModule = new LocalNetworkModule(address.substring(8));
break;
default:
// This shouldn't happen, as long as validateURI() has been called.
netModule = null;
}
return netModule;
}
private int getPort(String uri, int defaultPort) {
int port;
int portIndex = uri.lastIndexOf(':');
if (portIndex == -1) {
port = defaultPort;
}
else {
port = Integer.valueOf(uri.substring(portIndex + 1)).intValue();
}
return port;
}
private String getHostName(String uri) {
int schemeIndex = uri.lastIndexOf('/');
int portIndex = uri.lastIndexOf(':');
if (portIndex == -1) {
portIndex = uri.length();
}
return uri.substring(schemeIndex + 1, portIndex);
}
/* (non-Javadoc)
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#connect(java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
*/
public IMqttToken connect(Object userContext, IMqttActionListener callback)
throws MqttException, MqttSecurityException {
return this.connect(new MqttConnectOptions(), userContext, callback);
}
/* (non-Javadoc)
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#connect()
*/
public IMqttToken connect() throws MqttException, MqttSecurityException {
return this.connect(null, null);
}
/* (non-Javadoc)
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#connect(org.eclipse.paho.client.mqttv3.MqttConnectOptions)
*/
public IMqttToken connect(MqttConnectOptions options) throws MqttException, MqttSecurityException {
return this.connect(options, null,null);
}
/* (non-Javadoc)
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#connect(org.eclipse.paho.client.mqttv3.MqttConnectOptions, java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
*/
public IMqttToken connect(MqttConnectOptions options, Object userContext, IMqttActionListener callback)
throws MqttException, MqttSecurityException {
final String methodName = "connect";
if (comms.isConnected()) {
throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_CLIENT_CONNECTED);
}
if (comms.isConnecting()) {
throw new MqttException(MqttException.REASON_CODE_CONNECT_IN_PROGRESS);
}
if (comms.isDisconnecting()) {
throw new MqttException(MqttException.REASON_CODE_CLIENT_DISCONNECTING);
}
if (comms.isClosed()) {
throw new MqttException(MqttException.REASON_CODE_CLIENT_CLOSED);
}
// @TRACE 103=cleanSession={0} connectionTimeout={1} TimekeepAlive={2} userName={3} password={4} will={5} userContext={6} callback={7}
log.fine(className,methodName, "103",
new Object[]{
new Boolean(options.isCleanSession()),
new Integer(options.getConnectionTimeout()),
new Integer(options.getKeepAliveInterval()),
options.getUserName(),
((null == options.getPassword())?"[null]":"[notnull]"),
((null == options.getWillMessage())?"[null]":"[notnull]"),
userContext,
callback });
comms.setNetworkModules(createNetworkModules(serverURI, options));
// 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);
userToken.setActionCallback(connectActionListener);
userToken.setUserContext(this);
comms.setNetworkModuleIndex(0);
connectActionListener.connect();
return userToken;
}
/* (non-Javadoc)
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnect(java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
*/
public IMqttToken disconnect( Object userContext, IMqttActionListener callback) throws MqttException {
return this.disconnect(30000, userContext, callback);
}
/* (non-Javadoc)
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnect()
*/
public IMqttToken disconnect() throws MqttException {
return this.disconnect(null, null);
}
/* (non-Javadoc)
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnect(long)
*/
public IMqttToken disconnect(long quiesceTimeout) throws MqttException {
return this.disconnect(quiesceTimeout, null, null);
}
/* (non-Javadoc)
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnect(long, java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
*/
public IMqttToken disconnect(long quiesceTimeout, Object userContext, IMqttActionListener callback) throws MqttException {
final String methodName = "disconnect";
// @TRACE 104=> quiesceTimeout={0} userContext={1} callback={2}
log.fine(className,methodName, "104",new Object[]{ new Long(quiesceTimeout), userContext, callback});
MqttToken token = new MqttToken(getClientId());
token.setActionCallback(callback);
token.setUserContext(userContext);
MqttDisconnect disconnect = new MqttDisconnect();
try {
comms.disconnect(disconnect, quiesceTimeout, token);
} catch (MqttException ex) {
//@TRACE 105=< exception
log.fine(className,methodName,"105",null,ex);
throw ex;
}
//@TRACE 108=<
log.fine(className,methodName,"108");
return token;
}
/* (non-Javadoc)
* @see IMqttAsyncClient#isConnected()
*/
public boolean isConnected() {
return comms.isConnected();
}
/* (non-Javadoc)
* @see IMqttAsyncClient#getClientId()
*/
public String getClientId() {
return clientId;
}
/* (non-Javadoc)
* @see IMqttAsyncClient#getServerURI()
*/
public String getServerURI() {
return serverURI;
}
/**
* Get a topic object which can be used to publish messages.
* <p>There are two alternative methods that should be used in preference to this one when publishing a message:
* <ul>
* <li>{@link MqttAsyncClient#publish(String, MqttMessage, MqttDeliveryToken)} to publish a message in a non-blocking manner or
* <li>{@link MqttClient#publishBlock(String, MqttMessage, MqttDeliveryToken)} to publish a message in a blocking manner
* </ul>
* </p>
* <p>When you build an application,
* the design of the topic tree should take into account the following principles
* of topic name syntax and semantics:</p>
*
* <ul>
* <li>A topic must be at least one character long.</li>
* <li>Topic names are case sensitive. For example, <em>ACCOUNTS</em> and <em>Accounts</em> are
* two different topics.</li>
* <li>Topic names can include the space character. For example, <em>Accounts
* payable</em> is a valid topic.</li>
* <li>A leading "/" creates a distinct topic. For example, <em>/finance</em> is
* different from <em>finance</em>. <em>/finance</em> matches "+/+" and "/+", but
* not "+".</li>
* <li>Do not include the null character (Unicode<samp class="codeph"> \x0000</samp>) in
* any topic.</li>
* </ul>
*
* <p>The following principles apply to the construction and content of a topic
* tree:</p>
*
* <ul>
* <li>The length is limited to 64k but within that there are no limits to the
* number of levels in a topic tree.</li>
* <li>There can be any number of root nodes; that is, there can be any number
* of topic trees.</li>
* </ul>
* </p>
*
* @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) {
validateTopic(topic);
MqttTopic result = (MqttTopic)topics.get(topic);
if (result == null) {
result = new MqttTopic(topic, comms);
topics.put(topic,result);
}
return result;
}
/* (non-Javadoc)
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#subscribe(java.lang.String, int, java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
*/
public IMqttToken subscribe(String topicFilter, int qos, Object userContext, IMqttActionListener callback) throws MqttException {
return this.subscribe(new String[] {topicFilter}, new int[] {qos}, userContext, callback);
}
/* (non-Javadoc)
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#subscribe(java.lang.String, int)
*/
public IMqttToken subscribe(String topicFilter, int qos) throws MqttException {
return this.subscribe(new String[] {topicFilter}, new int[] {qos}, null, null);
}
/* (non-Javadoc)
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#subscribe(java.lang.String[], int[])
*/
public IMqttToken subscribe(String[] topicFilters, int[] qos) throws MqttException {
return this.subscribe(topicFilters, qos, null, null);
}
/* (non-Javadoc)
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#subscribe(java.lang.String[], int[], java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
*/
public IMqttToken subscribe(String[] topicFilters, int[] qos, Object userContext, IMqttActionListener callback) throws MqttException {
final String methodName = "subscribe";
if (topicFilters.length != qos.length) {
throw new IllegalArgumentException();
}
String subs = "";
for (int i=0;i<topicFilters.length;i++) {
if (i>0) {
subs+=", ";
}
subs+=topicFilters[i]+":"+qos[i];
}
//@TRACE 106=Subscribe topic={0} userContext={1} callback={2}
log.fine(className,methodName,"106",new Object[]{subs, userContext, callback});
MqttToken token = new MqttToken(getClientId());
token.setActionCallback(callback);
token.setUserContext(userContext);
token.internalTok.setTopics(topicFilters);
MqttSubscribe register = new MqttSubscribe(topicFilters, qos);
comms.sendNoWait(register, token);
//@TRACE 109=<
log.fine(className,methodName,"109");
return token;
}
/* (non-Javadoc)
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#unsubscribe(java.lang.String, java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
*/
public IMqttToken unsubscribe(String topicFilter, Object userContext, IMqttActionListener callback) throws MqttException {
return unsubscribe(new String[] {topicFilter}, userContext, callback);
}
/* (non-Javadoc)
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#unsubscribe(java.lang.String)
*/
public IMqttToken unsubscribe(String topicFilter) throws MqttException {
return unsubscribe(new String[] {topicFilter}, null, null);
}
/* (non-Javadoc)
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#unsubscribe(java.lang.String[])
*/
public IMqttToken unsubscribe(String[] topicFilters) throws MqttException {
return unsubscribe(topicFilters, null, null);
}
/* (non-Javadoc)
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#unsubscribe(java.lang.String[], java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
*/
public IMqttToken unsubscribe(String[] topicFilters, Object userContext, IMqttActionListener callback) throws MqttException {
final String methodName = "unsubscribe";
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(className, methodName,"107",new Object[]{subs, userContext, callback});
MqttToken token = new MqttToken(getClientId());
token.setActionCallback(callback);
token.setUserContext(userContext);
token.internalTok.setTopics(topicFilters);
MqttUnsubscribe unregister = new MqttUnsubscribe(topicFilters);
comms.sendNoWait(unregister, token);
//@TRACE 110=<
log.fine(className,methodName,"110");
return token;
}
/* (non-Javadoc)
* @see IMqttAsyncClient#setCallback(MqttCallback)
*/
public void setCallback(MqttCallback callback) {
comms.setCallback(callback);
}
/**
* Returns a randomly generated client identifier based on the current user's login
* name and the system time.
* <p>When cleanSession is set to false, an application must ensure it uses the
* same client identifier when it reconnects to the server to resume state and maintain
* assured message delivery.</p>
* @return a generated client identifier
* @see MqttConnectOptions#setCleanSession(boolean)
*/
public static String generateClientId() {
return (System.getProperty("user.name") + "." + System.currentTimeMillis());
}
/* (non-Javadoc)
* @see IMqttAsyncClient#getPendingDeliveryTokens()
*/
public IMqttDeliveryToken[] getPendingDeliveryTokens() {
return comms.getPendingDeliveryTokens();
}
/* (non-Javadoc)
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#publish(java.lang.String, byte[], int, boolean, java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
*/
public IMqttDeliveryToken publish(String topic, byte[] payload, int qos,
boolean retained, Object userContext, IMqttActionListener callback) throws MqttException,
MqttPersistenceException {
MqttMessage message = new MqttMessage(payload);
message.setQos(qos);
message.setRetained(retained);
return this.publish(topic, message, userContext, callback);
}
/* (non-Javadoc)
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#publish(java.lang.String, byte[], int, boolean)
*/
public IMqttDeliveryToken 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.client.mqttv3.IMqttAsyncClient#publish(java.lang.String, org.eclipse.paho.client.mqttv3.MqttMessage)
*/
public IMqttDeliveryToken publish(String topic, MqttMessage message) throws MqttException, MqttPersistenceException {
return this.publish(topic, message, null, null);
}
/* (non-Javadoc)
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#publish(java.lang.String, org.eclipse.paho.client.mqttv3.MqttMessage, java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
*/
public IMqttDeliveryToken publish(String topic, MqttMessage message, Object userContext, IMqttActionListener callback) throws MqttException,
MqttPersistenceException {
final String methodName = "publish";
//@TRACE 111=< topic={0} message={1}userContext={1} callback={2}
log.fine(className,methodName,"111", new Object[] {topic, userContext, callback});
validateTopic(topic);
MqttDeliveryToken token = new MqttDeliveryToken(getClientId());
token.setActionCallback(callback);
token.setUserContext(userContext);
token.setMessage(message);
token.internalTok.setTopics(new String[] {topic});
MqttPublish pubMsg = new MqttPublish(topic, message);
comms.sendNoWait(pubMsg, token);
//@TRACE 112=<
log.fine(className,methodName,"112");
return token;
}
/* (non-Javadoc)
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#close()
*/
public void close() throws MqttException {
final String methodName = "close";
//@TRACE 113=<
log.fine(className,methodName,"113");
comms.close();
//@TRACE 114=>
log.fine(className,methodName,"114");
}
/**
* Return a debug object that can be used to help solve problems.
*/
public Debug getDebug() {
return new Debug(clientId,comms);
}
/**
* Checks a topic is valid when publishing a message.
* <p>Checks the topic does not contain a wild card character.</p>
* @param topic to validate
* @throws IllegalArgumentException if the topic is not valid
*/
static public void validateTopic(String topic) {
if ((topic.indexOf('#') == -1) && (topic.indexOf('+') == -1)) {
return;
}
// The topic string does not comply with topic string rules.
throw new IllegalArgumentException();
}
}