/* * 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.internal; import java.io.IOException; import java.util.Vector; import org.eclipse.paho.client.mqttv3.MqttCallback; import org.eclipse.paho.client.mqttv3.MqttDeliveryToken; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttTopic; import org.eclipse.paho.client.mqttv3.internal.trace.Trace; import org.eclipse.paho.client.mqttv3.internal.wire.MqttPubAck; import org.eclipse.paho.client.mqttv3.internal.wire.MqttPubComp; import org.eclipse.paho.client.mqttv3.internal.wire.MqttPublish; /** * 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 int INBOUND_QUEUE_SIZE = 10; private MqttCallback mqttCallback; private ClientComms clientComms; private Vector messageQueue; private Vector completeQueue; private boolean running = false; private boolean quiescing = false; private Object lifecycle = new Object(); private Thread callbackThread; private Object workAvailable = new Object(); private Object spaceAvailable = new Object(); private boolean invoking = false; private Trace trace; CommsCallback(Trace trace, ClientComms clientComms) { this.trace = trace; this.clientComms = clientComms; this.messageQueue = new Vector(INBOUND_QUEUE_SIZE); this.completeQueue = new Vector(INBOUND_QUEUE_SIZE); } /** * Starts up the Sender thread. */ public void start() { if (running == false) { running = true; quiescing = false; callbackThread = new Thread(this, "MQTT Client Callback"); callbackThread.start(); } } /** * Stops the Receiver's thread. This call will block. */ public void stop() throws IOException { if (running) { // @TRACE 700=stop trace.trace(Trace.FINE,700); running = false; if (!Thread.currentThread().equals(callbackThread)) { try { synchronized (lifecycle) { synchronized (workAvailable) { // @TRACE 701=stop: notify workAvailable trace.trace(Trace.FINE,701); workAvailable.notifyAll(); } // @TRACE 702=stop: wait lifecycle trace.trace(Trace.FINE,702); // Wait for the thread to finish. lifecycle.wait(); } } catch (InterruptedException ex) { } } // @TRACE 703=stop complete trace.trace(Trace.FINE,703); } } public void setCallback(MqttCallback mqttCallback) { this.mqttCallback = mqttCallback; } public void run() { while(running) { // If no work is currently available, then wait until there is some... try { synchronized (workAvailable) { if (messageQueue.isEmpty() && completeQueue.isEmpty()) { // @TRACE 704=run: wait workAvailable trace.trace(Trace.FINE,704); workAvailable.wait(); } } } catch (InterruptedException e) { } if (running) { // Check for deliveryComplete callbacks... if (!completeQueue.isEmpty()) { if (mqttCallback != null) { MqttDeliveryToken token = (MqttDeliveryToken) completeQueue.elementAt(0); completeQueue.removeElementAt(0); if (trace.isOn()) { // @TRACE 705=run: deliveryComplete token={0} trace.trace(Trace.FINE,705, new Object[]{token}); } mqttCallback.deliveryComplete(token); } } // Check for messageArrived callbacks... if (!messageQueue.isEmpty()) { if (quiescing) { messageQueue.clear(); } else { // Ensure we're really connected before calling back with the message. // There is a window on connect where a publish could arrive before we've // finished the connect logic, causing the message to be lost. if (clientComms.isConnected()) { invoking = true; MqttPublish message = (MqttPublish) messageQueue.elementAt(0); messageQueue.removeElementAt(0); handleMessage(message); invoking = false; } } } } // Notify the spaceAvailable lock, to say that there's now some space // on the queue... synchronized (spaceAvailable) { // @TRACE 706=run: notify spaceAvailable trace.trace(Trace.FINE,706); spaceAvailable.notifyAll(); } } /* the following line was added to fix defect 62163. It is probable that the messageQueue.clear() in the code above is superfluous after * adding this, but I wanted to make the minimum change at this point */ messageQueue.clear(); synchronized (lifecycle) { // @TRACE 707=run: notify lifecycle trace.trace(Trace.FINE,707); lifecycle.notifyAll(); } } /** * This method is called when the connection to the server is lost. * * @param cause the reason behind the loss of connection. */ public void connectionLost(Throwable cause) { if (mqttCallback != null) { // @TRACE 708=run: connectionLost trace.trace(Trace.FINE,708,null,cause); mqttCallback.connectionLost(cause); } } /** * This method is called when a message arrives on a topic. * * @param sendMessage the MQTT SEND message. */ public void messageArrived(MqttPublish sendMessage) { if (mqttCallback != null) { // 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) { if (!quiescing && messageQueue.size() >= INBOUND_QUEUE_SIZE) { try { // @TRACE 709=messageArrived: wait spaceAvailable trace.trace(Trace.FINE,709); spaceAvailable.wait(); } catch (InterruptedException ex) { } } } if (!quiescing) { messageQueue.addElement(sendMessage); // Notify the CommsCallback thread that there's work to do... synchronized (workAvailable) { // @TRACE 710=messageArrived: notify workAvailable trace.trace(Trace.FINE,710); workAvailable.notifyAll(); } } } } public void quiesce() { this.quiescing = true; synchronized (spaceAvailable) { // @TRACE 711=quiesce: notify spaceAvailable trace.trace(Trace.FINE,711); // Unblock anything waiting for space... spaceAvailable.notifyAll(); } synchronized (spaceAvailable) { if (invoking) { // Wait until the last message has finished processing... try { // @TRACE 712=quiesce: wait spaceAvailable trace.trace(Trace.FINE,712); spaceAvailable.wait(); } catch (InterruptedException ex) { } } } } private void handleMessage(MqttPublish publishMessage) { // If disconnect() was called within a previous call to messageArrived, // we may just need to skip the processing of any messages we have in memory. if (clientComms.isConnected() && (mqttCallback != null)) { try { // Handle getting an MqttDestination object... String destName = publishMessage.getTopicName(); MqttTopic destination = null; if (destName != null) { destination = clientComms.getTopic(destName); } if (trace.isOn()) { // @TRACE 713=handleMessage: messageArrived topic={0} id={1} trace.trace(Trace.FINE,713,new Object[]{destination.getName(),new Integer(publishMessage.getMessageId())}); } mqttCallback.messageArrived(destination, publishMessage.getMessage()); if (publishMessage.getMessage().getQos() == 1) { this.clientComms.sendNoWait(new MqttPubAck(publishMessage)); } else if (publishMessage.getMessage().getQos() == 2) { this.clientComms.deliveryComplete(publishMessage); MqttPubComp pubComp = new MqttPubComp(publishMessage); this.clientComms.sendNoWait(pubComp); } } catch (Exception ex) { // @TRACE 714=handleMessage: messageArrived threw exception trace.trace(Trace.FINE,714,null, ex); clientComms.shutdownConnection(new MqttException(ex)); } } } public void deliveryComplete(MqttDeliveryToken token) { if (mqttCallback != null) { completeQueue.addElement(token); synchronized (workAvailable) { if (trace.isOn()) { // @TRACE 715=delieveryComplete: notify workAvailable. token={0} trace.trace(Trace.FINE,715, new Object[]{token}); } workAvailable.notifyAll(); } } } /** * Returns the thread used by this callback. */ protected Thread getThread() { return callbackThread; } }