/*******************************************************************************
* Copyright (c) 2009 MATERNA Information & Communications. 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. For further
* project-related information visit http://www.ws4d.org. The most recent
* version of the JMEDS framework can be obtained from
* http://sourceforge.net/projects/ws4d-javame.
******************************************************************************/
package org.ws4d.java.dispatch;
import org.ws4d.java.DPWSFramework;
import org.ws4d.java.communication.ProtocolData;
import org.ws4d.java.concurrency.LockSupport;
import org.ws4d.java.message.Message;
import org.ws4d.java.structures.ArrayList;
import org.ws4d.java.structures.HashMap;
import org.ws4d.java.structures.HashMap.Entry;
import org.ws4d.java.structures.Iterator;
import org.ws4d.java.structures.List;
import org.ws4d.java.util.Log;
/**
* This class is the place to register {@link MessageListener listeners} for
* messages crossing the DPWS framework. This is especially useful for clients
* interested in receiving specific types of messages or for traffic analyzers
* willing to get notified about each DPWS message sent or received.
* <p>
* When registering, a listener can declare its interest for certain messages by
* passing a {@link MessageSelector} to the method
* {@link #addMessageListener(MessageListener, MessageSelector)}. The
* <code>MessageSelector</code>'s {@link MessageSelector#matches(Message)}
* method is used to determine whether a given message matches this interest or
* not. The DPWS framework provides two standard implementations of
* <code>MessageSelector</code>: {@link AllMessageSelector}, which simply marks
* every message as interesting, and {@link DefaultMessageSelector} allowing
* discrimination based on a message's type and/or its target endpoint address
* (i.e. the WS-Addressing [destination] property of the message).
* </p>
*/
public class MessageInformer {
private static MessageInformer instance;
// key = MessageListener, value = MessageSelector
private final HashMap listeners = new HashMap();
// SYNC: this lock support instance protects the listeners map
private final LockSupport listenersLock = new LockSupport();
private final List queuedMessages = new ArrayList();
// SYNC: this lock support instance protects the messages queue
private final LockSupport queuedMessagesLock = new LockSupport();
/*
* this object is used for notifications of the delivery thread after new
* messages have arrived
*/
private final Object notifier = new Object();
private volatile boolean stopRunning = true;
/**
* Returns the singleton instance of this class.
*
* @return the singleton message informer
*/
public static synchronized MessageInformer getInstance() {
if (instance == null) {
instance = new MessageInformer();
}
return instance;
}
/*
* disallow any instances
*/
private MessageInformer() {
super();
}
/**
* Starts the message delivery loop of this message informer instance. Does
* nothing, if the message informer is already running.
*/
public void start() {
if (!stopRunning) {
return;
}
Runnable r = new Runnable() {
/*
* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
public void run() {
while (true) {
try {
deliverMessages();
if (stopRunning) {
return;
}
} catch (Throwable t) {
Log.error("MessageInformer: uncaught exception broke out: " + t);
}
}
}
};
stopRunning = false;
// get thread from platform toolkit / thread pool
DPWSFramework.getThreadPool().execute(r);
}
/**
* Stops the message informer, i.e. terminates the message delivery loop.
*/
public void stop() {
stopRunning = true;
synchronized (notifier) {
notifier.notify();
}
}
/**
* Adds the specified message listener to this message informer. The
* listener will receive notifications about messages running through the
* DPWS framework that match the specified message selector. If
* <code>sel</code> is <code>null</code>, the listener will be registered
* with an {@link AllMessageSelector} associated to it and thus will receive
* notifications about <em>every</em> message.
*
* @param listener the listener to register
* @param sel the selector determining which messages to deliver to the
* newly registered listener
*/
public void addMessageListener(MessageListener listener, MessageSelector sel) {
if (listener == null) {
return;
}
if (sel == null) {
sel = AllMessageSelector.INSTANCE;
}
try {
listenersLock.exclusiveLock();
listeners.put(listener, sel);
} finally {
listenersLock.releaseExclusiveLock();
}
}
/**
* Removes the specified message listener, if it was previously registered
* within this message informer.
*
* @param listener the listener to remove
*/
public void removeMessageListener(MessageListener listener) {
if (listener == null) {
return;
}
try {
listenersLock.exclusiveLock();
listeners.remove(listener);
} finally {
listenersLock.releaseExclusiveLock();
}
}
/**
* Returns an array containing all currently registered message listeners.
*
* @return an array consisting of all currently registered listeners
*/
public MessageListener[] getMessageListeners() {
MessageListener[] result;
try {
listenersLock.sharedLock();
result = (MessageListener[]) listeners.keySet().toArray(new MessageListener[listeners.size()]);
} finally {
listenersLock.releaseSharedLock();
}
return result;
}
/**
* Forwards a single message to all registered listeners.
*
* @param msg the message to forward
* @param protocolData transport-specific addressing information attached to
* the message
*/
public void forwardMessage(Message msg, ProtocolData protocolData) {
forwardMessageInternal(msg, protocolData, true);
}
/**
* Forwards an array of messages to all registered listeners at once.
*
* @param msgs the messages to forward
*/
// XXX do we need this API???
void forwardMessages(Message[] msgs) {
if (msgs == null) {
return;
}
for (int i = 0; i < msgs.length; i++) {
forwardMessageInternal(msgs[i], null, false);
}
synchronized (notifier) {
notifier.notify();
}
}
private void forwardMessageInternal(Message msg, ProtocolData protocolData, boolean notifyRunner) {
if (msg == null) {
return;
}
try {
queuedMessagesLock.exclusiveLock();
queuedMessages.add(new MessageEntry(msg, protocolData));
} finally {
queuedMessagesLock.releaseExclusiveLock();
}
if (notifyRunner) {
synchronized (notifier) {
notifier.notify();
}
}
}
private void deliverMessages() {
try {
queuedMessagesLock.sharedLock();
//(INGO) potentially DANGEROUS because the variable size in class List is not volatile
while (queuedMessages.size() == 0) {
queuedMessagesLock.releaseSharedLock();
try {
synchronized (notifier) {
notifier.wait();
}
} catch (InterruptedException e) {
// void
}
queuedMessagesLock.sharedLock();
if (stopRunning) {
try {
queuedMessagesLock.exclusiveLock();
queuedMessages.clear();
} finally {
queuedMessagesLock.releaseExclusiveLock();
}
return;
}
}
} finally {
queuedMessagesLock.releaseSharedLock();
}
MessageEntry messageEntry;
try {
queuedMessagesLock.exclusiveLock();
messageEntry = (MessageEntry) queuedMessages.remove(0);
} finally {
queuedMessagesLock.releaseExclusiveLock();
}
try {
listenersLock.sharedLock();
for (Iterator it2 = listeners.entrySet().iterator(); it2.hasNext();) {
Entry ent = (Entry) it2.next();
MessageSelector sel = (MessageSelector) ent.getValue();
Message msg = messageEntry.message;
if (sel.matches(msg)) {
MessageListener listener = (MessageListener) ent.getKey();
if (msg.isInbound()) {
listener.receivedInboundMessage(msg, messageEntry.protocolData);
} else {
listener.receivedOutboundMessage(msg, messageEntry.protocolData);
}
}
}
} finally {
listenersLock.releaseSharedLock();
}
}
private static final class MessageEntry {
final Message message;
final ProtocolData protocolData;
/**
* @param message
* @param protocolData
*/
MessageEntry(Message message, ProtocolData protocolData) {
super();
this.message = message;
this.protocolData = protocolData;
}
}
}