/**
* http://www.germane-software.com/software/Java/Gozirra/
*/
package org.chililog.client.stomp;
import java.util.*;
/**
* A Stomp messaging implementation.
*
* Messages are handled in one of two ways. If subscribe was called with a listener, then incoming messages are
* delivered to all listeners of that channel, and the message is deleted from the queue. If no listener was provided
* for that channel, then messages are placed in a queue and can be retrieved with getNext(). In all cases, when
* messages are retrieved, they are deleted from the queue.
*
* Notes: * FIXME: ERROR messages don't do anything.
*
* (c)2005 Sean Russell
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public abstract class Stomp {
/**
* A map of channel => listener pairs. String => Listener.
*/
private Map _listeners = new HashMap();
/**
* Things that are listening for communication errors. Contains Listeners.
*/
private List _error_listeners = new ArrayList();
/**
* A message queue; where messages that have no listeners are stored. Contains Messages.
*/
private Stack _queue = new Stack();
/**
* Incoming receipts (as String IDs)
*/
private List _receipts = new ArrayList();
/**
* True if connected to a server; false otherwise
*/
protected boolean _connected = false;
/**
* Incoming errors (as String messages)
*/
private List _errors = new ArrayList();
/**
* Disconnect from a server, including headers. Must be implemented by the child class. Should set the _connected
* flag to false.
*
* @param header
* A map of key/value headers
*/
public abstract void disconnect(Map header);
/**
* Transmit a message to a server. Must be implemented by the child class. The implementation must handle cases
* where the header and/or the body are null.
*
* @param command
* The Stomp command. If null, causes an error.
* @param header
* A map of headers. If null, an empty map is used.
* @param body
* The body of the message. May be empty.
*/
protected abstract void transmit(Command command, Map header, String body);
/**
* Disconnect from a server. Must be implemented by the child class.
*/
public void disconnect() {
disconnect(null);
}
/**
* Transmit a message to a server.
*
* @param command
* The Stomp command. If null, causes an error.
* @param header
* A map of headers. If null, an empty map is used.
*/
protected void transmit(Command command, Map header) {
transmit(command, header, null);
}
/**
* Transmit a message to a server.
*
* @param command
* The Stomp command. If null, causes an error.
*/
protected void transmit(Command command) {
transmit(command, null, null);
}
/**
* Begins a transaction. Messages will not be delivered to subscribers until commit() has been called.
*/
public void begin() {
transmit(Command.BEGIN);
}
/**
* Begins a transaction. Messages will not be delivered to subscribers until commit() has been called.
*
* @param header
* Additional headers to send to the server.
*/
public void begin(Map header) {
transmit(Command.BEGIN, header);
}
/**
* Commits a transaction, causing any messages sent since begin() was called to be delivered.
*/
public void commit() {
transmit(Command.COMMIT);
}
/**
* Commits a transaction, causing any messages sent since begin() was called to be delivered.
*
* @param header
* Additional headers to send to the server.
*/
public void commit(Map header) {
transmit(Command.BEGIN, header);
}
/**
* Commits a transaction, causing any messages sent since begin() was called to be delivered. This method does not
* return until the server has confirmed that the commit was successfull.
*/
public void commitW() throws InterruptedException {
commitW(null);
}
/**
* Commits a transaction, causing any messages sent since begin() was called to be delivered. This method does not
* return until the server has confirmed that the commit was successfull.
*/
public void commitW(Map header) throws InterruptedException {
String receipt = addReceipt(header);
transmit(Command.COMMIT, header);
waitOnReceipt(receipt);
}
/**
* Aborts a transaction. Messages sent since begin() was called are destroyed and are not sent to subscribers.
*/
public void abort() {
transmit(Command.ABORT);
}
/**
* Aborts a transaction. Messages sent since begin() was called are destroyed and are not sent to subscribers.
*
* @param header
* Additional headers to send to the server.
*/
public void abort(Map header) {
transmit(Command.ABORT, header);
}
/**
* Subscribe to a channel.
*
* @param name
* The name of the channel to listen on
*/
public void subscribe(String name) {
subscribe(name, null, null);
}
/**
* Subscribe to a channel.
*
* @param name
* The name of the channel to listen on
* @param header
* Additional headers to send to the server.
*/
public void subscribe(String name, Map header) {
subscribe(name, null, header);
}
/**
* Subscribe to a channel.
*
* @param name
* The name of the channel to listen on
* @param listener
* A listener to receive messages sent to the channel
*/
public void subscribe(String name, Listener listener) {
subscribe(name, listener, null);
}
/**
* Subscribe to a channel.
*
* @param name
* The name of the channel to listen on
* @param headers
* Additional headers to send to the server.
* @param listener
* A listener to receive messages sent to the channel
*/
public void subscribe(String name, Listener listener, Map headers) {
synchronized (_listeners) {
if (listener != null) {
List list = (List) _listeners.get(name);
if (list == null) {
list = new ArrayList();
_listeners.put(name, list);
}
if (!list.contains(listener))
list.add(listener);
}
}
if (headers == null)
headers = new HashMap();
headers.put("destination", name);
transmit(Command.SUBSCRIBE, headers);
}
private String addReceipt(Map header) {
if (header == null)
header = new HashMap();
String receipt = String.valueOf(hashCode()) + "&" + System.currentTimeMillis();
header.put("receipt", receipt);
return receipt;
}
/**
* Subscribe to a channel. This method blocks until it receives a receipt from the server.
*
* @param name
* The name of the channel to listen on
* @param headers
* Additional headers to send to the server.
* @param listener
* A listener to receive messages sent to the channel
*/
public void subscribeW(String name, Listener listener, Map header) throws InterruptedException {
String receipt = addReceipt(header);
subscribe(name, listener, header);
waitOnReceipt(receipt);
}
/**
* Subscribe to a channel. This method blocks until it receives a receipt from the server.
*
* @param name
* The name of the channel to listen on
* @param listener
* A listener to receive messages sent to the channel
*/
public void subscribeW(String name, Listener listener) throws InterruptedException {
subscribeW(name, listener, null);
}
/**
* Unsubscribe from a channel. Automatically unregisters all listeners of the channel. To re-subscribe with
* listeners, subscribe must be passed the listeners again.
*
* @param name
* The name of the channel to unsubscribe from.
*/
public void unsubscribe(String name) {
unsubscribe(name, (HashMap) null);
}
/**
* Unsubscribe a single listener from a channel. This does not send a message to the server unless the listener is
* the only listener of this channel.
*
* @param name
* The name of the channel to unsubscribe from.
* @param listener
* The listener to unsubscribe
*/
public void unsubscribe(String name, Listener l) {
synchronized (_listeners) {
List list = (List) _listeners.get(name);
if (list != null) {
list.remove(l);
if (list.size() == 0) {
unsubscribe(name);
}
}
}
}
/**
* Unsubscribe from a channel. Automatically unregisters all listeners of the channel. To re-subscribe with
* listeners, subscribe must be passed the listeners again.
*
* @param name
* The name of the channel to unsubscribe from.
* @param header
* Additional headers to send to the server.
*/
public void unsubscribe(String name, Map header) {
if (header == null)
header = new HashMap();
synchronized (_listeners) {
_listeners.remove(name);
}
header.put("destination", name);
transmit(Command.UNSUBSCRIBE, header);
}
/**
* Unsubscribe from a channel. Automatically unregisters all listeners of the channel. To re-subscribe with
* listeners, subscribe must be passed the listeners again. This method blocks until a receipt is received from the
* server.
*
* @param name
* The name of the channel to unsubscribe from.
*/
public void unsubscribeW(String name) throws InterruptedException {
unsubscribe(name, (HashMap) null);
}
/**
* Unsubscribe from a channel. Automatically unregisters all listeners of the channel. To re-subscribe with
* listeners, subscribe must be passed the listeners again. This method blocks until a receipt is received from the
* server.
*
* @param name
* The name of the channel to unsubscribe from.
*/
public void unsubscribeW(String name, Map header) throws InterruptedException {
String receipt = addReceipt(header);
unsubscribe(name, (HashMap) null);
waitOnReceipt(receipt);
}
/**
* Send a message to a channel synchronously. This method does not return until the server acknowledges with a
* receipt.
*
* @param dest
* The name of the channel to send the message to
* @param mesg
* The message to send.
*/
public void sendW(String dest, String mesg) throws InterruptedException {
sendW(dest, mesg, null);
}
/**
* Send a message to a channel synchronously. This method does not return until the server acknowledges with a
* receipt.
*
* @param dest
* The name of the channel to send the message to
* @param mesg
* The message to send.
*/
public void sendW(String dest, String mesg, Map header) throws InterruptedException {
String receipt = addReceipt(header);
send(dest, mesg, header);
waitOnReceipt(receipt);
}
/**
* Send a message to a channel.
*
* @param dest
* The name of the channel to send the message to
* @param mesg
* The message to send.
*/
public void send(String dest, String mesg) {
send(dest, mesg, null);
}
/**
* Send a message to a channel.
*
* @param dest
* The name of the channel to send the message to
* @param mesg
* The message to send.
* @param header
* Additional headers to send to the server.
*/
public void send(String dest, String mesg, Map header) {
if (header == null)
header = new HashMap();
header.put("destination", dest);
transmit(Command.SEND, header, mesg);
}
/**
* Get the next unconsumed message in the queue. This is non-blocking.
*
* @return the next message in the queue, or null if the queue contains no messages. This is non-blocking.
*/
public Message getNext() {
synchronized (_queue) {
return (Message) _queue.pop();
}
}
/**
* Get the next unconsumed message for a particular channel. This is non-blocking.
*
* @param name
* the name of the channel to search for
*
* @return the next message for the channel, or null if the queue contains no messages for the channel.
*/
public Message getNext(String name) {
synchronized (_queue) {
for (int idx = 0; idx < _queue.size(); idx++) {
Message m = (Message) _queue.get(idx);
if (m.headers().get("destination").equals(name)) {
_queue.remove(idx);
return m;
}
}
}
return null;
}
public void addErrorListener(Listener l) {
synchronized (_error_listeners) {
_error_listeners.add(l);
}
}
public void delErrorListener(Listener l) {
synchronized (_error_listeners) {
_error_listeners.remove(l);
}
}
/**
* Checks to see if a receipt has come in.
*
* @param receipt_id
* the id of the receipts to find
*/
public boolean hasReceipt(String receipt_id) {
synchronized (_receipts) {
for (Iterator i = _receipts.iterator(); i.hasNext();) {
String o = (String) i.next();
if (o.equals(receipt_id))
return true;
}
}
return false;
}
/**
* Deletes all receipts with a given ID
*
* @param receipt_id
* the id of the receipts to delete
*/
public void clearReceipt(String receipt_id) {
synchronized (_receipts) {
for (Iterator i = _receipts.iterator(); i.hasNext();) {
String o = (String) i.next();
if (o.equals(receipt_id))
i.remove();
}
}
}
/**
* Remove all of the receipts
*/
public void clearReceipts() {
synchronized (_receipts) {
_receipts.clear();
}
}
public void waitOnReceipt(String receipt_id) throws java.lang.InterruptedException {
synchronized (_receipts) {
while (!hasReceipt(receipt_id))
_receipts.wait();
}
}
public boolean waitOnReceipt(String receipt_id, long timeout) throws java.lang.InterruptedException {
synchronized (_receipts) {
while (!hasReceipt(receipt_id))
_receipts.wait(timeout);
if (_receipts.contains(receipt_id)) {
return true;
} else {
return false;
}
}
}
public boolean isConnected() {
return _connected;
}
public String nextError() {
/*
* StackTraceElement[] trace = new Throwable().getStackTrace(); StringBuffer buff = new StringBuffer(); for (int
* i=0; i<trace.length; buff.append( trace[i].getClassName()+":"+trace[i++].getLineNumber()+" << " ));
* System.err.println("In nextError with "+_errors.size()+" errors from "+buff.toString());
*/
synchronized (_errors) {
if (_errors.size() == 0)
return null;
return (String) _errors.remove(0);
}
}
public void receive(Command c, Map h, String b) {
if (c == Command.MESSAGE) {
String destination = (String) h.get("destination");
synchronized (_listeners) {
List listeners = (List) _listeners.get(destination);
if (listeners != null) {
listeners = new ArrayList(listeners);
for (Iterator i = listeners.iterator(); i.hasNext();) {
Listener l = (Listener) i.next();
try {
l.message(h, b);
} catch (Exception e) {
// Don't let listeners screw us over by throwing exceptions
}
}
} else {
_queue.push(new Message(c, h, b));
}
}
} else if (c == Command.CONNECTED) {
_connected = true;
} else if (c == Command.RECEIPT) {
_receipts.add(h.get("receipt-id"));
synchronized (_receipts) {
_receipts.notify();
}
} else if (c == Command.ERROR) {
if (_error_listeners.size() > 0) {
synchronized (_error_listeners) {
for (Iterator i = _error_listeners.iterator(); i.hasNext();) {
try {
((Listener) i.next()).message(h, b);
} catch (Exception e) {
// Don't let listeners screw us over by throwing exceptions
}
}
}
} else {
synchronized (_errors) {
_errors.add(b);
}
}
} else {
// FIXME
}
}
}