package org.directwebremoting.proxy.openajax;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpSession;
import javax.swing.event.EventListenerList;
import org.directwebremoting.ScriptSession;
import org.directwebremoting.event.PublishEvent;
import org.directwebremoting.event.PublishListener;
import org.directwebremoting.event.SubscriptionEvent;
import org.directwebremoting.event.SubscriptionListener;
/**
* A Server-side hub to manage subscriptions
* @author Joe Walker [joe at getahead dot ltd dot uk]
*/
public class PubSubHub
{
/**
* Publishes (broadcasts) an event based on a library-specific prefix and
* event name.
* @param prefix The prefix that corresponds to this event. This must be a
* prefix that has been registered via OpenAjax.registerLibrary().
* @param name The name of the event to listen for. Names can be any string
*/
public void publish(String prefix, String name)
{
publish(ANY_HTTP_SESSION, ANY_SCRIPT_SESSION, prefix, name, null, null);
}
/**
* Publishes (broadcasts) an event based on a library-specific prefix and
* event name.
* @param prefix The prefix that corresponds to this event. This must be a
* prefix that has been registered via OpenAjax.registerLibrary().
* @param name The name of the event to listen for. Names can be any string
* @param publisherData Data to be sent to the Listener along with the
* eventHappened message
*/
public void publish(String prefix, String name, Object publisherData)
{
publish(ANY_HTTP_SESSION, ANY_SCRIPT_SESSION, prefix, name, publisherData, null);
}
/**
* Publishes (broadcasts) an event based on a library-specific prefix and
* event name.
* @param httpSessionId An HttpSession that we are publishing to. The value
* should not be <code>null</code>, but can be {@link #ANY_HTTP_SESSION} to
* denote a match for all {@link HttpSession}s.
* @param scriptSessionId A ScriptSession that we are publishing to. The value
* should not be <code>null</code>, but can be {@link #ANY_SCRIPT_SESSION}
* to denote a match for all {@link ScriptSession}s.
* @param prefix The prefix that corresponds to this event. This must be a
* prefix that has been registered via OpenAjax.registerLibrary().
* @param name The name of the event to listen for. Names can be any string
*/
public void publish(String httpSessionId, String scriptSessionId, String prefix, String name)
{
publish(httpSessionId, scriptSessionId, prefix, name, null, null);
}
/**
* Publishes (broadcasts) an event based on a library-specific prefix and
* event name.
* @param httpSessionId An HttpSession that we are publishing to. The value
* should not be <code>null</code>, but can be {@link #ANY_HTTP_SESSION} to
* denote a match for all {@link HttpSession}s.
* @param scriptSessionId A ScriptSession that we are publishing to. The value
* should not be <code>null</code>, but can be {@link #ANY_SCRIPT_SESSION}
* to denote a match for all {@link ScriptSession}s.
* @param prefix The prefix that corresponds to this event. This must be a
* prefix that has been registered via OpenAjax.registerLibrary().
* @param name The name of the event to listen for. Names can be any string
* @param publisherData Data to be sent to the Listener along with the
* eventHappened message
*/
public void publish(String httpSessionId, String scriptSessionId, String prefix, String name, Object publisherData)
{
publish(httpSessionId, scriptSessionId, prefix, name, publisherData, null);
}
/**
* Publishes (broadcasts) an event based on a library-specific prefix and
* event name.
* @param httpSessionId An HttpSession that we are publishing to. The value
* should not be <code>null</code>, but can be {@link #ANY_HTTP_SESSION} to
* denote a match for all {@link HttpSession}s.
* @param scriptSessionId A ScriptSession that we are publishing to. The value
* should not be <code>null</code>, but can be {@link #ANY_SCRIPT_SESSION}
* to denote a match for all {@link ScriptSession}s.
* @param prefix The prefix that corresponds to this event. This must be a
* prefix that has been registered via OpenAjax.registerLibrary().
* @param name The name of the event to listen for. Names can be any string
* @param publisherData Data to be sent to the Listener along with the
* eventHappened message
* @param hubsVisited A list of the hubs that the message has passed through
*/
public void publish(String httpSessionId, String scriptSessionId, String prefix, String name, Object publisherData, List<String> hubsVisited)
{
checkNulls(httpSessionId, scriptSessionId, prefix, name);
if (hubsVisited == null)
{
hubsVisited = new ArrayList<String>();
}
else
{
if (hubsVisited.contains(getHubId()))
{
return;
}
}
hubsVisited.add(getHubId());
List<PublishListener> matches = new ArrayList<PublishListener>();
matches.addAll(allListeners.keySet());
if (matches.size() != 0 && !httpSessionId.equals(ANY_HTTP_SESSION))
{
List<PublishListener> httpSessionMatches = httpSessions.get(httpSessionId);
if (httpSessionMatches != null)
{
matches.retainAll(httpSessionMatches);
}
else
{
matches.clear();
}
}
if (matches.size() != 0 && !scriptSessionId.equals(ANY_SCRIPT_SESSION))
{
List<PublishListener> scriptSessionMatches = scriptSessions.get(scriptSessionId);
if (scriptSessionMatches != null)
{
matches.retainAll(scriptSessionMatches);
}
else
{
matches.clear();
}
}
if (matches.size() != 0 && !prefix.equals(ANY_PREFIX))
{
List<PublishListener> prefixMatches = prefixes.get(prefix);
if (prefixMatches != null)
{
matches.retainAll(prefixMatches);
}
else
{
matches.clear();
}
}
if (matches.size() != 0 && !name.equals(ANY_NAME))
{
List<PublishListener> nameMatches = names.get(name);
if (nameMatches != null)
{
matches.retainAll(nameMatches);
}
else
{
matches.clear();
}
}
for (PublishListener listener : matches)
{
Object subscriberData = allListeners.get(listener);
PublishEvent event = new PublishEvent(this, httpSessionId, scriptSessionId, prefix, name, publisherData, subscriberData, hubsVisited);
listener.publishHappened(event);
}
}
/**
* @see #subscribe(String, String, String, String, PublishListener, Object)
*/
public void subscribe(String prefix, String name, PublishListener listener)
{
subscribe(ANY_HTTP_SESSION, ANY_SCRIPT_SESSION, prefix, name, listener, null);
}
/**
* @see #subscribe(String, String, String, String, PublishListener, Object)
*/
public void subscribe(String prefix, String name, PublishListener listener, Object subscriberData)
{
subscribe(ANY_HTTP_SESSION, ANY_SCRIPT_SESSION, prefix, name, listener, subscriberData);
}
/**
* @see #subscribe(String, String, String, String, PublishListener, Object)
*/
public void subscribe(String httpSessionId, String scriptSessionId, String prefix, String name, PublishListener listener)
{
subscribe(httpSessionId, scriptSessionId, prefix, name, listener, null);
}
/**
* Allows registration of interest in named events based on library-specific
* prefix and event name. Global event matching is provided by passing "*"
* in the prefix and/or name arguments. Optional arguments may be specified
* for executing the specified handler function in a provided scope and for
* further filtering events prior to application.
* <p>
* The callback function will receive the following parameters
* (see OpenAjax.publish() for description of publisherData):
* <pre>
* function(prefix, name, subscriberData, publisherData){ ... }
* </pre>
* @param httpSessionId An HttpSession that we are subscribing to. The value
* should not be <code>null</code>, but can be {@link #ANY_HTTP_SESSION} to
* denote a match for all {@link HttpSession}s.
* @param scriptSessionId A ScriptSession that we are subscribing to. The value
* should not be <code>null</code>, but can be {@link #ANY_SCRIPT_SESSION}
* to denote a match for all {@link ScriptSession}s.
* @param prefix The prefix that corresponds to this library. This is the
* same value that was previously passed to registerLibrary(). Can be "*" to
* match the provided event name across all libraries.
* @param name The name of the event to listen for. Names can be any string.
* Can be "*" to match all events in the specified toolkit (see prefix). If
* both name and prefix specify "*", all events in the system will be routed
* to the registered handler (modulo any filtering provided by filter).
* @param listener The object to deliver messages to
* @param subscriberData Data to be send to the Listener along with the
* eventHappened message
*/
public void subscribe(String httpSessionId, String scriptSessionId, String prefix, String name, PublishListener listener, Object subscriberData)
{
checkNulls(httpSessionId, scriptSessionId, prefix, name);
if (httpSessionId != null && httpSessionId != ANY_HTTP_SESSION)
{
List<PublishListener> listeners = httpSessions.get(httpSessionId);
if (listeners == null)
{
listeners = new ArrayList<PublishListener>();
httpSessions.put(httpSessionId, listeners);
}
listeners.add(listener);
}
if (scriptSessionId != null && scriptSessionId != ANY_SCRIPT_SESSION)
{
List<PublishListener> listeners = scriptSessions.get(scriptSessionId);
if (listeners == null)
{
listeners = new ArrayList<PublishListener>();
scriptSessions.put(scriptSessionId, listeners);
}
listeners.add(listener);
}
if (prefix != null && prefix != ANY_PREFIX)
{
List<PublishListener> listeners = prefixes.get(prefix);
if (listeners == null)
{
listeners = new ArrayList<PublishListener>();
prefixes.put(prefix, listeners);
}
listeners.add(listener);
}
if (name != null && name != ANY_NAME)
{
List<PublishListener> listeners = names.get(name);
if (listeners == null)
{
listeners = new ArrayList<PublishListener>();
names.put(name, listeners);
}
listeners.add(listener);
}
allListeners.put(listener, subscriberData);
fireSubscribeHappenedEvent(httpSessionId, scriptSessionId, prefix, name, listener);
}
/**
* @see #unsubscribe(String, String, String, String, PublishListener)
*/
public void unsubscribe(String prefix, String name, PublishListener listener)
{
unsubscribe(ANY_HTTP_SESSION, ANY_SCRIPT_SESSION, prefix, name, listener);
}
/**
* Removes a subscription to an event. In order for a subscription to be
* removed, the values of the parameters supplied to OpenAjax.unsubscribe()
* must exactly match the values of the parameters supplied to a previous
* call to OpenAjax.subscribe(). Note that it is possible that one
* invocation of OpenAjax.unsubscribe() might result in removal of multiple
* subscriptions.
* @param prefix The prefix that corresponds to this library. This is the
* same value that was previously passed to registerLibrary(). Can be "*" to
* match the provided event name across all libraries.
* @param name The name of the event to listen for. Names can be any string.
* Can be "*" to match all events in the specified toolkit (see prefix). If
* both name and prefix specify "*", all events in the system will be routed
* to the registered handler (modulo any filtering provided by filter).
* @param listener The object to deliver messages to
*/
public void unsubscribe(String httpSessionId, String scriptSessionId, String prefix, String name, PublishListener listener)
{
checkNulls(httpSessionId, scriptSessionId, prefix, name);
if (httpSessionId != null && httpSessionId != ANY_HTTP_SESSION)
{
List<PublishListener> listeners = httpSessions.get(httpSessionId);
if (listeners != null)
{
listeners.remove(listener);
if (listeners.size() == 0)
{
httpSessions.remove(httpSessionId);
}
}
}
if (scriptSessionId != null && scriptSessionId != ANY_SCRIPT_SESSION)
{
List<PublishListener> listeners = scriptSessions.get(scriptSessionId);
if (listeners != null)
{
listeners.remove(listener);
if (listeners.size() == 0)
{
scriptSessions.remove(scriptSessionId);
}
}
}
if (prefix != null && prefix != ANY_PREFIX)
{
List<PublishListener> listeners = prefixes.get(prefix);
if (listeners != null)
{
listeners.remove(listener);
if (listeners.size() == 0)
{
prefixes.remove(prefix);
}
}
}
if (name != null && name != ANY_NAME)
{
List<PublishListener> listeners = names.get(name);
if (listeners != null)
{
listeners.remove(listener);
if (listeners.size() == 0)
{
names.remove(name);
}
}
}
allListeners.remove(listener);
fireUnsubscribeHappenedEvent(httpSessionId, scriptSessionId, prefix, name, listener);
}
/**
* Maintain the list of {@link SubscriptionListener}s
* @param li the SubscriptionListener to add
*/
public void addSubscriptionListener(SubscriptionListener li)
{
subscriptionListeners.add(SubscriptionListener.class, li);
}
/**
* Maintain the list of {@link SubscriptionListener}s
* @param li the ScriptSessionListener to remove
*/
public void removeSubscriptionListener(SubscriptionListener li)
{
subscriptionListeners.remove(SubscriptionListener.class, li);
}
/**
* If other hubs wish to synchronize with the messages passed through this
* hub they need to be able to filter to keep the message storm down.
* @return A set of the names that this hub is interested in.
*/
public Set<String> getSubscribedNames()
{
return Collections.unmodifiableSet(names.keySet());
}
/**
* If other hubs wish to synchronize with the messages passed through this
* hub they need to be able to filter to keep the message storm down.
* @return A set of the prefixes that this hub is interested in.
*/
public Set<String> getSubscribedPrefixes()
{
return Collections.unmodifiableSet(prefixes.keySet());
}
/**
* To allow hubs to not create publish loops we need to know what
* @return The ID of this Hub
*/
public String getHubId()
{
if (hubId == null)
{
hubId = "org.directwebremoting.proxy.openajax.PubSubHub." + hashCode();
}
return hubId;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
return getHubId();
}
/**
* This should be called whenever a {@link ScriptSession} is destroyed
* @param httpSessionId The ID match of the {@link HttpSession} that was subscribed to
* @param scriptSessionId The ID match of the {@link ScriptSession} that was subscribed to
* @param prefix The prefix match that was subscribed to
* @param name The name match that was subscripbed to
* @param listener The subscribed object
*/
protected void fireSubscribeHappenedEvent(String httpSessionId, String scriptSessionId, String prefix, String name, PublishListener listener)
{
SubscriptionEvent ev = new SubscriptionEvent(this, httpSessionId, scriptSessionId, prefix, name, listener);
Object[] listeners = subscriptionListeners.getListenerList();
for (int i = 0; i < listeners.length - 2; i += 2)
{
if (listeners[i] == SubscriptionListener.class)
{
((SubscriptionListener) listeners[i + 1]).subscribeHappened(ev);
}
}
}
/**
* This should be called whenever a {@link ScriptSession} is created
* @param httpSessionId The ID match of the {@link HttpSession} that was subscribed to
* @param scriptSessionId The ID match of the {@link ScriptSession} that was subscribed to
* @param prefix The prefix match that was subscribed to
* @param name The name match that was subscripbed to
* @param listener The subscribed object
*/
protected void fireUnsubscribeHappenedEvent(String httpSessionId, String scriptSessionId, String prefix, String name, PublishListener listener)
{
SubscriptionEvent ev = new SubscriptionEvent(this, httpSessionId, scriptSessionId, prefix, name, listener);
Object[] listeners = subscriptionListeners.getListenerList();
for (int i = 0; i < listeners.length - 2; i += 2)
{
if (listeners[i] == SubscriptionListener.class)
{
((SubscriptionListener) listeners[i + 1]).unsubscribeHappened(ev);
}
}
}
/**
* The list of current {@link SubscriptionListener}s
*/
protected EventListenerList subscriptionListeners = new EventListenerList();
/**
* A constant to denote a match to any <code>prefix</code>.
*/
public static final String ANY_PREFIX = "*";
/**
* A constant to denote a match to any <code>name</code>.
*/
public static final String ANY_NAME = "*";
/**
* A constant to denote a match to any {@link HttpSession}.
*/
public static final String ANY_HTTP_SESSION = "*";
/**
* A constant to denote a match to any {@link ScriptSession}.
*/
public static final String ANY_SCRIPT_SESSION = "*";
/**
* Throw if any of the parameters are null
*/
private void checkNulls(String httpSessionId, String scriptSessionId, String prefix, String name)
{
if (httpSessionId == null)
{
throw new NullPointerException("httpSessionId may not be null. Use PubSubHub.ANY_HTTP_SESSION for broad matching");
}
if (scriptSessionId == null)
{
throw new NullPointerException("scriptSession may not be null. Use PubSubHub.ANY_SCRIPT_SESSION for broad matching");
}
if (prefix == null)
{
throw new NullPointerException("prefix may not be null. Use PubSubHub.ANY_PREFIX or \"*\" for broad matching");
}
if (name == null)
{
throw new NullPointerException("name may not be null. Use PubSubHub.ANY_NAME or \"*\" for broad matching");
}
}
private String hubId = null;
private Map<PublishListener, Object> allListeners = new HashMap<PublishListener, Object>();
private Map<String, List<PublishListener>> names = new HashMap<String, List<PublishListener>>();
private Map<String, List<PublishListener>> prefixes = new HashMap<String, List<PublishListener>>();
private Map<String, List<PublishListener>> httpSessions = new HashMap<String, List<PublishListener>>();
private Map<String, List<PublishListener>> scriptSessions = new HashMap<String, List<PublishListener>>();
}