package com.external.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
*/
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 InterruptedException {
synchronized( _receipts ) {
while (!hasReceipt(receipt_id))
_receipts.wait();
}
}
public boolean waitOnReceipt( String receipt_id, long timeout )
throws 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
}
}
}