PageRenderTime 28ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/org.eclipse.paho.mqttv5.client/src/main/java/org/eclipse/paho/mqttv5/client/internal/ClientComms.java

http://github.com/eclipse/paho.mqtt.java
Java | 978 lines | 618 code | 121 blank | 239 comment | 140 complexity | 2370747157834ce33998b40da11b5a65 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception
  1. package org.eclipse.paho.mqttv5.client.internal;
  2. /*******************************************************************************
  3. * Copyright (c) 2009, 2018 IBM Corp.
  4. *
  5. * All rights reserved. This program and the accompanying materials
  6. * are made available under the terms of the Eclipse Public License v2.0
  7. * and Eclipse Distribution License v1.0 which accompany this distribution.
  8. *
  9. * The Eclipse Public License is available at
  10. * https://www.eclipse.org/legal/epl-2.0
  11. * and the Eclipse Distribution License is available at
  12. * https://www.eclipse.org/org/documents/edl-v10.php
  13. *
  14. * Contributors:
  15. * Dave Locke - initial API and implementation and/or initial documentation
  16. * Ian Craggs - per subscription message handlers (bug 466579)
  17. * Ian Craggs - ack control (bug 472172)
  18. * James Sutton - checkForActivity Token (bug 473928)
  19. * James Sutton - Automatic Reconnect & Offline Buffering.
  20. */
  21. import java.util.Enumeration;
  22. import java.util.Properties;
  23. import java.util.Vector;
  24. import java.util.concurrent.ExecutorService;
  25. import org.eclipse.paho.mqttv5.client.BufferedMessage;
  26. import org.eclipse.paho.mqttv5.client.IMqttMessageListener;
  27. import org.eclipse.paho.mqttv5.client.MqttActionListener;
  28. import org.eclipse.paho.mqttv5.client.MqttCallback;
  29. import org.eclipse.paho.mqttv5.client.MqttClientException;
  30. import org.eclipse.paho.mqttv5.client.MqttClientInterface;
  31. import org.eclipse.paho.mqttv5.client.MqttClientPersistence;
  32. import org.eclipse.paho.mqttv5.client.MqttConnectionOptions;
  33. import org.eclipse.paho.mqttv5.client.MqttPingSender;
  34. import org.eclipse.paho.mqttv5.client.MqttToken;
  35. import org.eclipse.paho.mqttv5.client.MqttTopic;
  36. import org.eclipse.paho.mqttv5.client.TimerPingSender;
  37. import org.eclipse.paho.mqttv5.client.logging.Logger;
  38. import org.eclipse.paho.mqttv5.client.logging.LoggerFactory;
  39. import org.eclipse.paho.mqttv5.common.MqttException;
  40. import org.eclipse.paho.mqttv5.common.MqttMessage;
  41. import org.eclipse.paho.mqttv5.common.MqttPersistenceException;
  42. import org.eclipse.paho.mqttv5.common.packet.MqttConnAck;
  43. import org.eclipse.paho.mqttv5.common.packet.MqttConnect;
  44. import org.eclipse.paho.mqttv5.common.packet.MqttDisconnect;
  45. import org.eclipse.paho.mqttv5.common.packet.MqttProperties;
  46. import org.eclipse.paho.mqttv5.common.packet.MqttPublish;
  47. import org.eclipse.paho.mqttv5.common.packet.MqttWireMessage;
  48. /**
  49. * Handles client communications with the server. Sends and receives MQTT V5
  50. * messages.
  51. */
  52. public class ClientComms {
  53. public static String VERSION = "${project.version}";
  54. public static String BUILD_LEVEL = "L${build.level}";
  55. private static final String CLASS_NAME = ClientComms.class.getName();
  56. private Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT, CLASS_NAME);
  57. private static final byte CONNECTED = 0;
  58. private static final byte CONNECTING = 1;
  59. private static final byte DISCONNECTING = 2;
  60. private static final byte DISCONNECTED = 3;
  61. private static final byte CLOSED = 4;
  62. private MqttClientInterface client;
  63. private int networkModuleIndex;
  64. private NetworkModule[] networkModules;
  65. private CommsReceiver receiver;
  66. private CommsSender sender;
  67. private CommsCallback callback;
  68. private ClientState clientState;
  69. private MqttConnectionOptions conOptions;
  70. private MqttClientPersistence persistence;
  71. private MqttPingSender pingSender;
  72. private CommsTokenStore tokenStore;
  73. private boolean stoppingComms = false;
  74. private byte conState = DISCONNECTED;
  75. private final Object conLock = new Object(); // Used to synchronize connection state
  76. private boolean closePending = false;
  77. private boolean resting = false;
  78. private DisconnectedMessageBuffer disconnectedMessageBuffer;
  79. private ExecutorService executorService;
  80. private MqttConnectionState mqttConnection;
  81. /**
  82. * Creates a new ClientComms object, using the specified module to handle the
  83. * network calls.
  84. *
  85. * @param client
  86. * The {@link MqttClientInterface}
  87. * @param persistence
  88. * the {@link MqttClientPersistence} layer.
  89. * @param pingSender
  90. * the {@link TimerPingSender}
  91. * @param executorService
  92. * the {@link ExecutorService}
  93. * @param mqttSession
  94. * the {@link MqttSessionState}
  95. * @param mqttConnection
  96. * the {@link MqttConnectionState}
  97. * @throws MqttException
  98. * if an exception occurs whilst communicating with the server
  99. */
  100. public ClientComms(MqttClientInterface client, MqttClientPersistence persistence, MqttPingSender pingSender,
  101. ExecutorService executorService, MqttSessionState mqttSession, MqttConnectionState mqttConnection) throws MqttException {
  102. this.conState = DISCONNECTED;
  103. this.client = client;
  104. this.persistence = persistence;
  105. this.pingSender = pingSender;
  106. this.pingSender.init(this);
  107. this.executorService = executorService;
  108. this.mqttConnection = mqttConnection;
  109. this.tokenStore = new CommsTokenStore(getClient().getClientId());
  110. this.callback = new CommsCallback(this);
  111. this.clientState = new ClientState(persistence, tokenStore, this.callback, this, pingSender, this.mqttConnection);
  112. callback.setClientState(clientState);
  113. log.setResourceName(getClient().getClientId());
  114. }
  115. CommsReceiver getReceiver() {
  116. return receiver;
  117. }
  118. /**
  119. * Sends a message to the server. Does not check if connected this validation
  120. * must be done by invoking routines.
  121. *
  122. * @param message
  123. * @param token
  124. * @throws MqttException
  125. */
  126. void internalSend(MqttWireMessage message, MqttToken token) throws MqttException {
  127. final String methodName = "internalSend";
  128. // @TRACE 200=internalSend key={0} message={1} token={2}
  129. log.fine(CLASS_NAME, methodName, "200", new Object[] { message.getKey(), message, token });
  130. if (token.getClient() == null) {
  131. // Associate the client with the token - also marks it as in use.
  132. token.internalTok.setClient(getClient());
  133. } else {
  134. // Token is already in use - cannot reuse
  135. // @TRACE 213=fail: token in use: key={0} message={1} token={2}
  136. log.fine(CLASS_NAME, methodName, "213", new Object[] { message.getKey(), message, token });
  137. throw new MqttException(MqttClientException.REASON_CODE_TOKEN_INUSE);
  138. }
  139. try {
  140. // Persist if needed and send the message
  141. this.clientState.send(message, token);
  142. } catch (MqttException e) {
  143. token.internalTok.setClient(null); // undo client setting on error
  144. if (message instanceof MqttPublish) {
  145. this.clientState.undo((MqttPublish) message);
  146. }
  147. throw e;
  148. }
  149. }
  150. /**
  151. * Sends a message to the broker if in connected state, but only waits for the
  152. * message to be stored, before returning.
  153. *
  154. * @param message
  155. * The {@link MqttWireMessage} to send
  156. * @param token
  157. * The {@link MqttToken} to send.
  158. * @throws MqttException
  159. * if an error occurs sending the message
  160. */
  161. public void sendNoWait(MqttWireMessage message, MqttToken token) throws MqttException {
  162. final String methodName = "sendNoWait";
  163. if (isConnected() || (!isConnected() && message instanceof MqttConnect)
  164. || (isDisconnecting() && message instanceof MqttDisconnect)) {
  165. if (disconnectedMessageBuffer != null && disconnectedMessageBuffer.getMessageCount() != 0) {
  166. // @TRACE 507=Client Connected, Offline Buffer available, but not empty. Adding
  167. // message to buffer. message={0}
  168. log.fine(CLASS_NAME, methodName, "507", new Object[] { message.getKey() });
  169. // If the message is a publish, strip the topic alias:
  170. if(message instanceof MqttPublish && message.getProperties().getTopicAlias()!= null) {
  171. MqttProperties messageProps = message.getProperties();
  172. messageProps.setTopicAlias(null);
  173. message.setProperties(messageProps);
  174. }
  175. if (disconnectedMessageBuffer.isPersistBuffer()) {
  176. this.clientState.persistBufferedMessage(message);
  177. }
  178. disconnectedMessageBuffer.putMessage(message, token);
  179. } else {
  180. if (message instanceof MqttPublish) {
  181. // Override the QoS if the server has set a maximum
  182. if (this.mqttConnection.getMaximumQoS() != null
  183. && ((MqttPublish) message).getMessage().getQos() > this.mqttConnection.getMaximumQoS()) {
  184. MqttMessage mqttMessage = ((MqttPublish) message).getMessage();
  185. mqttMessage.setQos(this.mqttConnection.getMaximumQoS());
  186. ((MqttPublish) message).setMessage(mqttMessage);
  187. }
  188. // Override the Retain flag if the server has disabled it
  189. if (this.mqttConnection.isRetainAvailable() != null
  190. && ((MqttPublish) message).getMessage().isRetained()
  191. && (this.mqttConnection.isRetainAvailable() == false)) {
  192. MqttMessage mqttMessage = ((MqttPublish) message).getMessage();
  193. mqttMessage.setRetained(false);
  194. ((MqttPublish) message).setMessage(mqttMessage);
  195. }
  196. }
  197. this.internalSend(message, token);
  198. }
  199. } else if (disconnectedMessageBuffer != null && isResting()) {
  200. // @TRACE 508=Client Resting, Offline Buffer available. Adding message to
  201. // buffer. message={0}
  202. log.fine(CLASS_NAME, methodName, "508", new Object[] { message.getKey() });
  203. if (disconnectedMessageBuffer.isPersistBuffer()) {
  204. this.clientState.persistBufferedMessage(message);
  205. }
  206. disconnectedMessageBuffer.putMessage(message, token);
  207. } else {
  208. // @TRACE 208=failed: not connected
  209. log.fine(CLASS_NAME, methodName, "208");
  210. throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_CLIENT_NOT_CONNECTED);
  211. }
  212. }
  213. /**
  214. * Close and tidy up.
  215. *
  216. * Call each main class and let it tidy up e.g. releasing the token store which
  217. * normally survives a disconnect.
  218. *
  219. * @param force
  220. * force disconnection
  221. * @throws MqttException
  222. * if not disconnected
  223. */
  224. public void close(boolean force) throws MqttException {
  225. final String methodName = "close";
  226. synchronized (conLock) {
  227. if (!isClosed()) {
  228. // Must be disconnected before close can take place or if we are being forced
  229. if (!isDisconnected() || force) {
  230. // @TRACE 224=failed: not disconnected
  231. log.fine(CLASS_NAME, methodName, "224");
  232. if (isConnecting()) {
  233. throw new MqttException(MqttClientException.REASON_CODE_CONNECT_IN_PROGRESS);
  234. } else if (isConnected()) {
  235. throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_CLIENT_CONNECTED);
  236. } else if (isDisconnecting()) {
  237. closePending = true;
  238. return;
  239. }
  240. }
  241. conState = CLOSED;
  242. // Don't shut down an externally supplied executor service
  243. //shutdownExecutorService();
  244. // ShutdownConnection has already cleaned most things
  245. clientState.close();
  246. clientState = null;
  247. callback = null;
  248. persistence = null;
  249. sender = null;
  250. pingSender = null;
  251. receiver = null;
  252. networkModules = null;
  253. conOptions = null;
  254. tokenStore = null;
  255. }
  256. }
  257. }
  258. /**
  259. * Sends a connect message and waits for an ACK or NACK. Connecting is a special
  260. * case which will also start up the network connection, receive thread, and
  261. * keep alive thread.
  262. *
  263. * @param options
  264. * The {@link MqttConnectionOptions} for the connection
  265. * @param token
  266. * The {@link MqttToken} to track the connection
  267. * @throws MqttException
  268. * if an error occurs when connecting
  269. */
  270. public void connect(MqttConnectionOptions options, MqttToken token) throws MqttException {
  271. final String methodName = "connect";
  272. synchronized (conLock) {
  273. if (isDisconnected() && !closePending) {
  274. // @TRACE 214=state=CONNECTING
  275. log.fine(CLASS_NAME, methodName, "214");
  276. conState = CONNECTING;
  277. conOptions = options;
  278. MqttConnect connect = new MqttConnect(client.getClientId(), conOptions.getMqttVersion(),
  279. conOptions.isCleanStart(), conOptions.getKeepAliveInterval(),
  280. conOptions.getConnectionProperties(), conOptions.getWillMessageProperties());
  281. if (conOptions.getWillDestination() != null) {
  282. connect.setWillDestination(conOptions.getWillDestination());
  283. }
  284. if (conOptions.getWillMessage() != null) {
  285. connect.setWillMessage(conOptions.getWillMessage());
  286. }
  287. if (conOptions.getUserName() != null) {
  288. connect.setUserName(conOptions.getUserName());
  289. }
  290. if (conOptions.getPassword() != null) {
  291. connect.setPassword(conOptions.getPassword());
  292. }
  293. /*
  294. * conOptions.getUserName(), conOptions.getPassword(),
  295. * conOptions.getWillMessage(), conOptions.getWillDestination()
  296. */
  297. this.mqttConnection.setKeepAliveSeconds(conOptions.getKeepAliveInterval());
  298. this.clientState.setCleanStart(conOptions.isCleanStart());
  299. tokenStore.open();
  300. ConnectBG conbg = new ConnectBG(this, token, connect, executorService);
  301. conbg.start();
  302. } else {
  303. // @TRACE 207=connect failed: not disconnected {0}
  304. log.fine(CLASS_NAME, methodName, "207", new Object[] { Byte.valueOf(conState) });
  305. if (isClosed() || closePending) {
  306. throw new MqttException(MqttClientException.REASON_CODE_CLIENT_CLOSED);
  307. } else if (isConnecting()) {
  308. throw new MqttException(MqttClientException.REASON_CODE_CONNECT_IN_PROGRESS);
  309. } else if (isDisconnecting()) {
  310. throw new MqttException(MqttClientException.REASON_CODE_CLIENT_DISCONNECTING);
  311. } else {
  312. throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_CLIENT_CONNECTED);
  313. }
  314. }
  315. }
  316. }
  317. public void connectComplete(MqttConnAck cack, MqttException mex) throws MqttException {
  318. final String methodName = "connectComplete";
  319. int rc = cack.getReturnCode();
  320. synchronized (conLock) {
  321. if (rc == 0) {
  322. // We've successfully connected
  323. // @TRACE 215=state=CONNECTED
  324. log.fine(CLASS_NAME, methodName, "215");
  325. conState = CONNECTED;
  326. return;
  327. }
  328. }
  329. // @TRACE 204=connect failed: rc={0}
  330. log.fine(CLASS_NAME, methodName, "204", new Object[] { Integer.valueOf(rc) });
  331. throw mex;
  332. }
  333. /**
  334. * Shuts down the connection to the server. This may have been invoked as a
  335. * result of a user calling disconnect or an abnormal disconnection. The method
  336. * may be invoked multiple times in parallel as each thread when it receives an
  337. * error uses this method to ensure that shutdown completes successfully.
  338. *
  339. * @param token
  340. * the {@link MqttToken} To track closing the connection
  341. * @param reason
  342. * the {@link MqttException} thrown requiring the connection to be
  343. * shut down.
  344. * @param message
  345. * the {@link MqttDisconnect} that triggered the connection to be
  346. * shut down.
  347. */
  348. public void shutdownConnection(MqttToken token, MqttException reason, MqttDisconnect message) {
  349. final String methodName = "shutdownConnection";
  350. boolean wasConnected;
  351. MqttToken endToken = null; // Token to notify after disconnect completes
  352. // This method could concurrently be invoked from many places only allow it
  353. // to run once.
  354. synchronized (conLock) {
  355. if (stoppingComms || closePending || isClosed()) {
  356. return;
  357. }
  358. stoppingComms = true;
  359. // @TRACE 216=state=DISCONNECTING
  360. log.fine(CLASS_NAME, methodName, "216");
  361. wasConnected = (isConnected() || isDisconnecting());
  362. conState = DISCONNECTING;
  363. }
  364. // Update the token with the reason for shutdown if it
  365. // is not already complete.
  366. if (token != null && !token.isComplete()) {
  367. token.internalTok.setException(reason);
  368. }
  369. // Stop the thread that is used to call the user back
  370. // when actions complete
  371. if (callback != null) {
  372. callback.stop();
  373. }
  374. // Stop the thread that handles inbound work from the network
  375. if (receiver != null) {
  376. receiver.stop();
  377. }
  378. // Stop the network module, send and receive now not possible
  379. try {
  380. if (networkModules != null) {
  381. NetworkModule networkModule = networkModules[networkModuleIndex];
  382. if (networkModule != null) {
  383. networkModule.stop();
  384. }
  385. }
  386. } catch (Exception ioe) {
  387. // Ignore as we are shutting down
  388. }
  389. // Stop any new tokens being saved by app and throwing an exception if they do
  390. tokenStore.quiesce(new MqttException(MqttClientException.REASON_CODE_CLIENT_DISCONNECTING));
  391. // Notify any outstanding tokens with the exception of
  392. // con or discon which may be returned and will be notified at
  393. // the end
  394. endToken = handleOldTokens(token, reason);
  395. try {
  396. // Clean session handling and tidy up
  397. clientState.disconnected(reason);
  398. if (clientState.getCleanStart())
  399. callback.removeMessageListeners();
  400. } catch (Exception ex) {
  401. // Ignore as we are shutting down
  402. }
  403. if (sender != null) {
  404. sender.stop();
  405. }
  406. if (pingSender != null) {
  407. pingSender.stop();
  408. }
  409. try {
  410. if (disconnectedMessageBuffer == null && persistence != null) {
  411. persistence.close();
  412. }
  413. } catch (Exception ex) {
  414. // Ignore as we are shutting down
  415. }
  416. // All disconnect logic has been completed allowing the
  417. // client to be marked as disconnected.
  418. synchronized (conLock) {
  419. // @TRACE 217=state=DISCONNECTED
  420. log.fine(CLASS_NAME, methodName, "217");
  421. conState = DISCONNECTED;
  422. stoppingComms = false;
  423. }
  424. // Internal disconnect processing has completed. If there
  425. // is a disconnect token or a connect in error notify
  426. // it now. This is done at the end to allow a new connect
  427. // to be processed and now throw a currently disconnecting error.
  428. // any outstanding tokens and unblock any waiters
  429. if (endToken != null && callback != null) {
  430. callback.asyncOperationComplete(endToken);
  431. }
  432. if (wasConnected && callback != null) {
  433. // Let the user know client has disconnected either normally or abnormally
  434. callback.connectionLost(reason, message);
  435. }
  436. // While disconnecting, close may have been requested - try it now
  437. synchronized (conLock) {
  438. if (closePending) {
  439. try {
  440. close(true);
  441. } catch (Exception e) { // ignore any errors as closing
  442. }
  443. }
  444. }
  445. }
  446. // Tidy up. There may be tokens outstanding as the client was
  447. // not disconnected/quiseced cleanly! Work out what tokens still
  448. // need to be notified and waiters unblocked. Store the
  449. // disconnect or connect token to notify after disconnect is
  450. // complete.
  451. private MqttToken handleOldTokens(MqttToken token, MqttException reason) {
  452. final String methodName = "handleOldTokens";
  453. // @TRACE 222=>
  454. log.fine(CLASS_NAME, methodName, "222");
  455. MqttToken tokToNotifyLater = null;
  456. try {
  457. // First the token that was related to the disconnect / shutdown may
  458. // not be in the token table - temporarily add it if not
  459. if (token != null) {
  460. if (tokenStore.getToken(token.internalTok.getKey()) == null) {
  461. tokenStore.saveToken(token, token.internalTok.getKey());
  462. }
  463. }
  464. Vector<MqttToken> toksToNot = clientState.resolveOldTokens(reason);
  465. Enumeration<MqttToken> toksToNotE = toksToNot.elements();
  466. while (toksToNotE.hasMoreElements()) {
  467. MqttToken tok = (MqttToken) toksToNotE.nextElement();
  468. if (tok.internalTok.getKey().equals(MqttDisconnect.KEY)
  469. || tok.internalTok.getKey().equals(MqttConnect.KEY)) {
  470. // Its con or discon so remember and notify @ end of disc routine
  471. tokToNotifyLater = tok;
  472. } else {
  473. // notify waiters and callbacks of outstanding tokens
  474. // that a problem has occurred and disconnect is in
  475. // progress
  476. callback.asyncOperationComplete(tok);
  477. }
  478. }
  479. } catch (Exception ex) {
  480. // Ignore as we are shutting down
  481. }
  482. return tokToNotifyLater;
  483. }
  484. public void disconnect(MqttDisconnect disconnect, long quiesceTimeout, MqttToken token) throws MqttException {
  485. final String methodName = "disconnect";
  486. synchronized (conLock) {
  487. if (isClosed()) {
  488. // @TRACE 223=failed: in closed state
  489. log.fine(CLASS_NAME, methodName, "223");
  490. throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_CLIENT_CLOSED);
  491. } else if (isDisconnected()) {
  492. // @TRACE 211=failed: already disconnected
  493. log.fine(CLASS_NAME, methodName, "211");
  494. throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_CLIENT_ALREADY_DISCONNECTED);
  495. } else if (isDisconnecting()) {
  496. // @TRACE 219=failed: already disconnecting
  497. log.fine(CLASS_NAME, methodName, "219");
  498. throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_CLIENT_DISCONNECTING);
  499. } else if (Thread.currentThread() == callback.getThread()) {
  500. // @TRACE 210=failed: called on callback thread
  501. log.fine(CLASS_NAME, methodName, "210");
  502. // Not allowed to call disconnect() from the callback, as it will deadlock.
  503. throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_CLIENT_DISCONNECT_PROHIBITED);
  504. }
  505. // @TRACE 218=state=DISCONNECTING
  506. log.fine(CLASS_NAME, methodName, "218");
  507. conState = DISCONNECTING;
  508. DisconnectBG discbg = new DisconnectBG(disconnect, quiesceTimeout, token, executorService);
  509. discbg.start();
  510. }
  511. }
  512. public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout, int reasonCode,
  513. MqttProperties disconnectProperties) throws MqttException {
  514. disconnectForcibly(quiesceTimeout, disconnectTimeout, true, reasonCode, disconnectProperties);
  515. }
  516. /**
  517. * Disconnect the connection and reset all the states.
  518. *
  519. * @param quiesceTimeout
  520. * How long to wait whilst quiesing before messages are deleted.
  521. * @param disconnectTimeout
  522. * How long to wait whilst disconnecting
  523. * @param sendDisconnectPacket
  524. * If true, will send a disconnect packet
  525. * @param reasonCode
  526. * the disconnection reason code.
  527. * @param disconnectProperties
  528. * the {@link MqttProperties} to send in the Disconnect packet.
  529. * @throws MqttException
  530. * if an error occurs whilst disconnecting
  531. */
  532. public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout, boolean sendDisconnectPacket,
  533. int reasonCode, MqttProperties disconnectProperties) throws MqttException {
  534. conState = DISCONNECTING;
  535. // Allow current inbound and outbound work to complete
  536. if (clientState != null) {
  537. clientState.quiesce(quiesceTimeout);
  538. }
  539. MqttToken token = new MqttToken(client.getClientId());
  540. try {
  541. // Send disconnect packet
  542. if (sendDisconnectPacket) {
  543. internalSend(new MqttDisconnect(reasonCode, disconnectProperties), token);
  544. // Wait util the disconnect packet sent with timeout
  545. token.waitForCompletion(disconnectTimeout);
  546. }
  547. } catch (Exception ex) {
  548. // ignore, probably means we failed to send the disconnect packet.
  549. } finally {
  550. token.internalTok.markComplete(null, null);
  551. shutdownConnection(token, null, null);
  552. }
  553. }
  554. public boolean isConnected() {
  555. synchronized (conLock) {
  556. return conState == CONNECTED;
  557. }
  558. }
  559. public boolean isConnecting() {
  560. synchronized (conLock) {
  561. return conState == CONNECTING;
  562. }
  563. }
  564. public boolean isDisconnected() {
  565. synchronized (conLock) {
  566. return conState == DISCONNECTED;
  567. }
  568. }
  569. public boolean isDisconnecting() {
  570. synchronized (conLock) {
  571. return conState == DISCONNECTING;
  572. }
  573. }
  574. public boolean isClosed() {
  575. synchronized (conLock) {
  576. return conState == CLOSED;
  577. }
  578. }
  579. public boolean isResting() {
  580. synchronized (conLock) {
  581. return resting;
  582. }
  583. }
  584. public void setCallback(MqttCallback mqttCallback) {
  585. this.callback.setCallback(mqttCallback);
  586. }
  587. public void setReconnectCallback(MqttCallback callback) {
  588. this.callback.setReconnectCallback(callback);
  589. }
  590. public void setManualAcks(boolean manualAcks) {
  591. this.callback.setManualAcks(manualAcks);
  592. }
  593. public void messageArrivedComplete(int messageId, int qos) throws MqttException {
  594. this.callback.messageArrivedComplete(messageId, qos);
  595. }
  596. public void setMessageListener(Integer subscriptionId, String topicFilter, IMqttMessageListener messageListener) {
  597. this.callback.setMessageListener(subscriptionId, topicFilter, messageListener);
  598. }
  599. public void removeMessageListener(String topicFilter) {
  600. this.callback.removeMessageListener(topicFilter);
  601. }
  602. protected MqttTopic getTopic(String topic) {
  603. return new MqttTopic(topic, this);
  604. }
  605. public void setNetworkModuleIndex(int index) {
  606. this.networkModuleIndex = index;
  607. }
  608. public int getNetworkModuleIndex() {
  609. return networkModuleIndex;
  610. }
  611. public NetworkModule[] getNetworkModules() {
  612. return networkModules;
  613. }
  614. public void setNetworkModules(NetworkModule[] networkModules) {
  615. this.networkModules = networkModules;
  616. }
  617. public MqttToken[] getPendingTokens() {
  618. return tokenStore.getOutstandingDelTokens();
  619. }
  620. protected void deliveryComplete(MqttPublish msg) throws MqttPersistenceException {
  621. this.clientState.deliveryComplete(msg);
  622. }
  623. protected void deliveryComplete(int messageId) throws MqttPersistenceException {
  624. this.clientState.deliveryComplete(messageId);
  625. }
  626. public MqttClientInterface getClient() {
  627. return client;
  628. }
  629. public long getKeepAlive() {
  630. return this.mqttConnection.getKeepAlive();
  631. }
  632. public MqttState getClientState() {
  633. return clientState;
  634. }
  635. public MqttConnectionOptions getConOptions() {
  636. return conOptions;
  637. }
  638. public Properties getDebug() {
  639. Properties props = new Properties();
  640. props.put("conState", Integer.valueOf(conState));
  641. props.put("serverURI", getClient().getServerURI());
  642. props.put("callback", callback);
  643. props.put("stoppingComms", Boolean.valueOf(stoppingComms));
  644. return props;
  645. }
  646. // Kick off the connect processing in the background so that it does not block.
  647. // For instance
  648. // the socket could take time to create.
  649. private class ConnectBG implements Runnable {
  650. ClientComms clientComms = null;
  651. MqttToken conToken;
  652. MqttConnect conPacket;
  653. private String threadName;
  654. ConnectBG(ClientComms cc, MqttToken cToken, MqttConnect cPacket, ExecutorService executorService) {
  655. clientComms = cc;
  656. conToken = cToken;
  657. conPacket = cPacket;
  658. threadName = "MQTT Con: " + getClient().getClientId();
  659. }
  660. void start() {
  661. if (executorService == null) {
  662. new Thread(this).start();
  663. } else {
  664. executorService.execute(this);
  665. }
  666. }
  667. public void run() {
  668. Thread.currentThread().setName(threadName);
  669. final String methodName = "connectBG:run";
  670. MqttException mqttEx = null;
  671. // @TRACE 220=>
  672. log.fine(CLASS_NAME, methodName, "220");
  673. try {
  674. // Reset an exception on existing delivery tokens.
  675. // This will have been set if disconnect occurred before delivery was
  676. // fully processed.
  677. MqttToken[] toks = tokenStore.getOutstandingDelTokens();
  678. for (MqttToken tok : toks) {
  679. tok.internalTok.setException(null);
  680. }
  681. // Save the connect token in tokenStore as failure can occur before send
  682. tokenStore.saveToken(conToken, conPacket);
  683. // Connect to the server at the network level e.g. TCP socket and then
  684. // start the background processing threads before sending the connect
  685. // packet.
  686. NetworkModule networkModule = networkModules[networkModuleIndex];
  687. networkModule.start();
  688. receiver = new CommsReceiver(clientComms, clientState, tokenStore, networkModule.getInputStream());
  689. receiver.start("MQTT Rec: " + getClient().getClientId(), executorService);
  690. sender = new CommsSender(clientComms, clientState, tokenStore, networkModule.getOutputStream());
  691. sender.start("MQTT Snd: " + getClient().getClientId(), executorService);
  692. callback.start("MQTT Call: " + getClient().getClientId(), executorService);
  693. internalSend(conPacket, conToken);
  694. } catch (MqttException ex) {
  695. // @TRACE 212=connect failed: unexpected exception
  696. log.fine(CLASS_NAME, methodName, "212", null, ex);
  697. mqttEx = ex;
  698. } catch (Exception ex) {
  699. // @TRACE 209=connect failed: unexpected exception
  700. log.fine(CLASS_NAME, methodName, "209", null, ex);
  701. mqttEx = ExceptionHelper.createMqttException(ex);
  702. }
  703. if (mqttEx != null) {
  704. shutdownConnection(conToken, mqttEx, null);
  705. }
  706. }
  707. }
  708. // Kick off the disconnect processing in the background so that it does not
  709. // block. For instance
  710. // the quiesce
  711. private class DisconnectBG implements Runnable {
  712. MqttDisconnect disconnect;
  713. long quiesceTimeout;
  714. MqttToken token;
  715. private String threadName;
  716. DisconnectBG(MqttDisconnect disconnect, long quiesceTimeout, MqttToken token, ExecutorService executorService) {
  717. this.disconnect = disconnect;
  718. this.quiesceTimeout = quiesceTimeout;
  719. this.token = token;
  720. }
  721. void start() {
  722. threadName = "MQTT Disc: "+getClient().getClientId();
  723. if (executorService == null) {
  724. new Thread(this).start();
  725. } else {
  726. executorService.execute(this);
  727. }
  728. }
  729. public void run() {
  730. Thread.currentThread().setName(threadName);
  731. final String methodName = "disconnectBG:run";
  732. // @TRACE 221=>
  733. log.fine(CLASS_NAME, methodName, "221");
  734. // Allow current inbound and outbound work to complete
  735. clientState.quiesce(quiesceTimeout);
  736. try {
  737. internalSend(disconnect, token);
  738. // do not wait if the sender process is not running
  739. if (sender != null && sender.isRunning()) {
  740. token.internalTok.waitUntilSent();
  741. }
  742. }
  743. catch (MqttException ex) {
  744. }
  745. finally {
  746. token.internalTok.markComplete(null, null);
  747. if (sender == null || !sender.isRunning()) {
  748. // if the sender process is not running
  749. token.internalTok.notifyComplete();
  750. }
  751. shutdownConnection(token, null, null);
  752. }
  753. }
  754. }
  755. /*
  756. * Check and send a ping if needed and check for ping timeout. Need to send a
  757. * ping if nothing has been sent or received in the last keepalive interval.
  758. */
  759. public MqttToken checkForActivity() {
  760. return this.checkForActivity(null);
  761. }
  762. /*
  763. * Check and send a ping if needed and check for ping timeout. Need to send a
  764. * ping if nothing has been sent or received in the last keepalive interval.
  765. * Passes an IMqttActionListener to ClientState.checkForActivity so that the
  766. * callbacks are attached as soon as the token is created (Bug 473928)
  767. */
  768. public MqttToken checkForActivity(MqttActionListener pingCallback) {
  769. MqttToken token = null;
  770. try {
  771. token = clientState.checkForActivity(pingCallback);
  772. } catch (MqttException e) {
  773. handleRunException(e);
  774. } catch (Exception e) {
  775. handleRunException(e);
  776. }
  777. return token;
  778. }
  779. private void handleRunException(Exception ex) {
  780. final String methodName = "handleRunException";
  781. // @TRACE 804=exception
  782. log.fine(CLASS_NAME, methodName, "804", null, ex);
  783. MqttException mex;
  784. if (!(ex instanceof MqttException)) {
  785. mex = new MqttException(MqttClientException.REASON_CODE_CONNECTION_LOST, ex);
  786. } else {
  787. mex = (MqttException) ex;
  788. }
  789. shutdownConnection(null, mex, null);
  790. }
  791. /**
  792. * When Automatic reconnect is enabled, we want ClientComs to enter the
  793. * 'resting' state if disconnected. This will allow us to publish messages
  794. *
  795. * @param resting
  796. * if true, resting is enabled
  797. */
  798. public void setRestingState(boolean resting) {
  799. this.resting = resting;
  800. }
  801. public void setDisconnectedMessageBuffer(DisconnectedMessageBuffer disconnectedMessageBuffer) {
  802. this.disconnectedMessageBuffer = disconnectedMessageBuffer;
  803. }
  804. public int getBufferedMessageCount() {
  805. return this.disconnectedMessageBuffer.getMessageCount();
  806. }
  807. public MqttMessage getBufferedMessage(int bufferIndex) {
  808. MqttPublish send = (MqttPublish) this.disconnectedMessageBuffer.getMessage(bufferIndex).getMessage();
  809. return send.getMessage();
  810. }
  811. public void deleteBufferedMessage(int bufferIndex) {
  812. this.disconnectedMessageBuffer.deleteMessage(bufferIndex);
  813. }
  814. /**
  815. * When the client automatically reconnects, we want to send all messages from
  816. * the buffer first before allowing the user to send any messages
  817. */
  818. public void notifyReconnect() {
  819. final String methodName = "notifyReconnect";
  820. if (disconnectedMessageBuffer != null) {
  821. // @TRACE 509=Client Reconnected, Offline Buffer Available. Sending Buffered
  822. // Messages.
  823. log.fine(CLASS_NAME, methodName, "509");
  824. disconnectedMessageBuffer.setPublishCallback(new ReconnectDisconnectedBufferCallback(methodName));
  825. if (executorService == null) {
  826. new Thread(disconnectedMessageBuffer).start();
  827. } else {
  828. executorService.execute(disconnectedMessageBuffer);
  829. }
  830. }
  831. }
  832. class ReconnectDisconnectedBufferCallback implements IDisconnectedBufferCallback {
  833. final String methodName;
  834. ReconnectDisconnectedBufferCallback(String methodName) {
  835. this.methodName = methodName;
  836. }
  837. public void publishBufferedMessage(BufferedMessage bufferedMessage) throws MqttException {
  838. if (isConnected()) {
  839. // @TRACE 510=Publising Buffered message message={0}
  840. log.fine(CLASS_NAME, methodName, "510", new Object[] { bufferedMessage.getMessage().getKey() });
  841. internalSend(bufferedMessage.getMessage(), bufferedMessage.getToken());
  842. // Delete from persistence if in there
  843. clientState.unPersistBufferedMessage(bufferedMessage.getMessage());
  844. } else {
  845. // @TRACE 208=failed: not connected
  846. log.fine(CLASS_NAME, methodName, "208");
  847. throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_CLIENT_NOT_CONNECTED);
  848. }
  849. }
  850. }
  851. public int getActualInFlight() {
  852. return this.clientState.getActualInFlight();
  853. }
  854. public boolean doesSubscriptionIdentifierExist(int subscriptionIdentifier) {
  855. return this.callback.doesSubscriptionIdentifierExist(subscriptionIdentifier);
  856. }
  857. }