/*
* 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.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import org.eclipse.paho.client.mqttv3.MqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.internal.trace.Trace;
import org.eclipse.paho.client.mqttv3.internal.wire.MqttAck;
import org.eclipse.paho.client.mqttv3.internal.wire.MqttConnack;
import org.eclipse.paho.client.mqttv3.internal.wire.MqttConnect;
import org.eclipse.paho.client.mqttv3.internal.wire.MqttDisconnect;
import org.eclipse.paho.client.mqttv3.internal.wire.MqttPingReq;
import org.eclipse.paho.client.mqttv3.internal.wire.MqttPingResp;
import org.eclipse.paho.client.mqttv3.internal.wire.MqttPubRel;
import org.eclipse.paho.client.mqttv3.internal.wire.MqttPublish;
import org.eclipse.paho.client.mqttv3.internal.wire.MqttWireMessage;
/**
* Provides a "token" based system for handling the
* threading. When a message is sent, a token is derived from that message
* and saved using the {@link #saveToken(MqttWireMessage)} method. The sending
* method then calls {@link Object#wait()} on that token. 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
* {@link Object#notify()}.
*/
public class CommsTokenStore {
/** Maps message-specific data (usually message IDs) to tokens */
private Hashtable tokens;
private MqttDeliveryTokenImpl pingToken;
private MqttDeliveryTokenImpl connectToken;
private MqttDeliveryTokenImpl disconnectToken;
private MqttException noMoreResponsesException = null;
private boolean noMoreResponses = false;
private Trace trace;
public CommsTokenStore(Trace trace) {
this.tokens = new Hashtable();
this.trace = trace;
pingToken = new MqttDeliveryTokenImpl(trace);
connectToken = new MqttDeliveryTokenImpl(trace);
disconnectToken = new MqttDeliveryTokenImpl(trace);
}
public MqttDeliveryTokenImpl getToken(MqttWireMessage message) {
Object key;
if (message instanceof MqttAck) {
return getTokenForAck((MqttAck)message);
}
else if (message instanceof MqttPingReq) {
key = pingToken;
}
else if (message instanceof MqttConnect) {
key = connectToken;
}
else if (message instanceof MqttDisconnect) {
key = disconnectToken;
}
else {
key = new Integer(message.getMessageId());
}
return (MqttDeliveryTokenImpl)tokens.get(key);
}
private MqttDeliveryTokenImpl getTokenForAck(MqttWireMessage message) {
MqttDeliveryTokenImpl token;
if (message instanceof MqttPingResp) {
token = pingToken;
}
else if (message instanceof MqttConnack) {
token = connectToken;
}
else {
token = (MqttDeliveryTokenImpl)tokens.get(new Integer(message.getMessageId()));
}
return token;
}
public MqttDeliveryTokenImpl removeToken(MqttWireMessage message) {
Object key;
if (message instanceof MqttConnack) {
key = connectToken;
} else if (message instanceof MqttDisconnect) {
key = disconnectToken;
} else {
key = new Integer(message.getMessageId());
}
if (trace.isOn()) {
//@TRACE 301=removeToken message={0} key={1}
trace.trace(Trace.FINE,301,new Object[]{message,key});
}
return (MqttDeliveryTokenImpl) tokens.remove(key);
}
/**
* 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.
*/
protected MqttDeliveryTokenImpl restoreToken(MqttPublish message) {
MqttDeliveryTokenImpl token;
Object key = new Integer(message.getMessageId());
if (this.tokens.containsKey(key)) {
token = (MqttDeliveryTokenImpl)this.tokens.get(key);
if (trace.isOn()) {
//@TRACE 302=restoreToken existing message={0} key={1} token={2}
trace.trace(Trace.FINE,302,new Object[]{message,key,token});
}
} else {
token = new MqttDeliveryTokenImpl(trace, message);
this.tokens.put(key, token);
if (trace.isOn()) {
//@TRACE 303=restoreToken creating new message={0} key={1} token={2}
trace.trace(Trace.FINE,303,new Object[]{message,key,token});
}
}
return token;
}
protected MqttDeliveryTokenImpl saveToken(MqttWireMessage message) {
Object key;
MqttDeliveryTokenImpl token;
if (message instanceof MqttPingReq) {
token = pingToken;
key = token;
}
else if (message instanceof MqttConnect) {
noMoreResponses = false;
noMoreResponsesException = null;
connectToken = new MqttDeliveryTokenImpl(trace);
token = connectToken;
key = token;
}
else if (message instanceof MqttDisconnect) {
disconnectToken = new MqttDeliveryTokenImpl(trace);
token = disconnectToken;
key = token;
}
else if (message instanceof MqttPubRel) {
// TODO: This could be brittle, as the key might not always be a message ID
key = new Integer(message.getMessageId());
token = getToken(message);
}
else if (message instanceof MqttPublish) {
key = new Integer(message.getMessageId());
token = new MqttDeliveryTokenImpl(trace, (MqttPublish) message);
}
else {
key = new Integer(message.getMessageId());
token = new MqttDeliveryTokenImpl(trace);
}
if (trace.isOn()) {
//@TRACE 300=saveToken message={0} key={1} token={2}
trace.trace(Trace.FINE,300,new Object[]{message,key,token.toString()});
}
this.tokens.put(key, token);
if (noMoreResponses) {
token.notifyException(noMoreResponsesException);
}
return token;
}
/**
* Called by the Receiver's thread to indicate the the specified
* response has been received. The MQTTAck object contains the
* details of what is being responded to.
*/
protected void responseReceived(MqttAck ack) {
MqttDeliveryTokenImpl token = getTokenForAck(ack);
removeToken(ack);
if (token != null) {
token.notifyReceived(ack);
}
//Else token == null - the only way the token will ever be null is if the
//server has sent a message the client knows nothing about - which the server
//should never do (unless it's broken somehow).
//As there's no ability to log in the client and we can't notify the app using
//the client in any other way, the only other choice is to swallow the problem.
}
/**
* Called by the Receiver's thread to indicate that no more responses are
* expected, due to a shutdown of the receiver;
*/
protected void noMoreResponses(MqttException reason) {
noMoreResponses = true;
noMoreResponsesException = reason;
Enumeration enumeration = tokens.elements();
Object token;
//@TRACE 304=noMoreResponses
trace.trace(Trace.FINE,304,null,reason);
while (enumeration.hasMoreElements()) {
token = enumeration.nextElement();
if (token != null) {
synchronized (token) {
((MqttDeliveryTokenImpl)token).notifyException(reason);
}
}
}
}
public MqttDeliveryToken[] getOutstandingTokens() {
Vector list = new Vector();
Enumeration enumeration = tokens.elements();
MqttDeliveryToken token;
while(enumeration.hasMoreElements()) {
token = (MqttDeliveryToken)enumeration.nextElement();
if (token != null) {
if (!(token.equals(pingToken) ||
token.equals(connectToken) ||
token.equals(disconnectToken))) {
list.addElement(token);
}
}
}
MqttDeliveryToken[] result = new MqttDeliveryToken[list.size()];
for(int i=0;i<list.size();i++) {
result[i] = (MqttDeliveryToken)list.elementAt(i);
}
return result;
}
/**
* Empties the token store without notifying any of the tokens.
* This should only be called when the client has disconnected
* cleanSession.
*/
public void clear() {
//@TRACE 305=clear
trace.trace(Trace.FINE,305);
tokens.clear();
}
}