001: package org.directwebremoting.proxy.openajax;
002:
003: import java.util.ArrayList;
004: import java.util.Collections;
005: import java.util.HashMap;
006: import java.util.List;
007: import java.util.Map;
008: import java.util.Set;
009:
010: import javax.servlet.http.HttpSession;
011: import javax.swing.event.EventListenerList;
012:
013: import org.directwebremoting.ScriptSession;
014: import org.directwebremoting.event.PublishEvent;
015: import org.directwebremoting.event.PublishListener;
016: import org.directwebremoting.event.SubscriptionEvent;
017: import org.directwebremoting.event.SubscriptionListener;
018:
019: /**
020: * A Server-side hub to manage subscriptions
021: * @author Joe Walker [joe at getahead dot ltd dot uk]
022: */
023: public class PubSubHub {
024: /**
025: * Publishes (broadcasts) an event based on a library-specific prefix and
026: * event name.
027: * @param prefix The prefix that corresponds to this event. This must be a
028: * prefix that has been registered via OpenAjax.registerLibrary().
029: * @param name The name of the event to listen for. Names can be any string
030: */
031: public void publish(String prefix, String name) {
032: publish(ANY_HTTP_SESSION, ANY_SCRIPT_SESSION, prefix, name,
033: null, null);
034: }
035:
036: /**
037: * Publishes (broadcasts) an event based on a library-specific prefix and
038: * event name.
039: * @param prefix The prefix that corresponds to this event. This must be a
040: * prefix that has been registered via OpenAjax.registerLibrary().
041: * @param name The name of the event to listen for. Names can be any string
042: * @param publisherData Data to be sent to the Listener along with the
043: * eventHappened message
044: */
045: public void publish(String prefix, String name, Object publisherData) {
046: publish(ANY_HTTP_SESSION, ANY_SCRIPT_SESSION, prefix, name,
047: publisherData, null);
048: }
049:
050: /**
051: * Publishes (broadcasts) an event based on a library-specific prefix and
052: * event name.
053: * @param httpSessionId An HttpSession that we are publishing to. The value
054: * should not be <code>null</code>, but can be {@link #ANY_HTTP_SESSION} to
055: * denote a match for all {@link HttpSession}s.
056: * @param scriptSessionId A ScriptSession that we are publishing to. The value
057: * should not be <code>null</code>, but can be {@link #ANY_SCRIPT_SESSION}
058: * to denote a match for all {@link ScriptSession}s.
059: * @param prefix The prefix that corresponds to this event. This must be a
060: * prefix that has been registered via OpenAjax.registerLibrary().
061: * @param name The name of the event to listen for. Names can be any string
062: */
063: public void publish(String httpSessionId, String scriptSessionId,
064: String prefix, String name) {
065: publish(httpSessionId, scriptSessionId, prefix, name, null,
066: null);
067: }
068:
069: /**
070: * Publishes (broadcasts) an event based on a library-specific prefix and
071: * event name.
072: * @param httpSessionId An HttpSession that we are publishing to. The value
073: * should not be <code>null</code>, but can be {@link #ANY_HTTP_SESSION} to
074: * denote a match for all {@link HttpSession}s.
075: * @param scriptSessionId A ScriptSession that we are publishing to. The value
076: * should not be <code>null</code>, but can be {@link #ANY_SCRIPT_SESSION}
077: * to denote a match for all {@link ScriptSession}s.
078: * @param prefix The prefix that corresponds to this event. This must be a
079: * prefix that has been registered via OpenAjax.registerLibrary().
080: * @param name The name of the event to listen for. Names can be any string
081: * @param publisherData Data to be sent to the Listener along with the
082: * eventHappened message
083: */
084: public void publish(String httpSessionId, String scriptSessionId,
085: String prefix, String name, Object publisherData) {
086: publish(httpSessionId, scriptSessionId, prefix, name,
087: publisherData, null);
088: }
089:
090: /**
091: * Publishes (broadcasts) an event based on a library-specific prefix and
092: * event name.
093: * @param httpSessionId An HttpSession that we are publishing to. The value
094: * should not be <code>null</code>, but can be {@link #ANY_HTTP_SESSION} to
095: * denote a match for all {@link HttpSession}s.
096: * @param scriptSessionId A ScriptSession that we are publishing to. The value
097: * should not be <code>null</code>, but can be {@link #ANY_SCRIPT_SESSION}
098: * to denote a match for all {@link ScriptSession}s.
099: * @param prefix The prefix that corresponds to this event. This must be a
100: * prefix that has been registered via OpenAjax.registerLibrary().
101: * @param name The name of the event to listen for. Names can be any string
102: * @param publisherData Data to be sent to the Listener along with the
103: * eventHappened message
104: * @param hubsVisited A list of the hubs that the message has passed through
105: */
106: public void publish(String httpSessionId, String scriptSessionId,
107: String prefix, String name, Object publisherData,
108: List<String> hubsVisited) {
109: checkNulls(httpSessionId, scriptSessionId, prefix, name);
110:
111: if (hubsVisited == null) {
112: hubsVisited = new ArrayList<String>();
113: } else {
114: if (hubsVisited.contains(getHubId())) {
115: return;
116: }
117: }
118: hubsVisited.add(getHubId());
119:
120: List<PublishListener> matches = new ArrayList<PublishListener>();
121: matches.addAll(allListeners.keySet());
122:
123: if (matches.size() != 0
124: && !httpSessionId.equals(ANY_HTTP_SESSION)) {
125: List<PublishListener> httpSessionMatches = httpSessions
126: .get(httpSessionId);
127: if (httpSessionMatches != null) {
128: matches.retainAll(httpSessionMatches);
129: } else {
130: matches.clear();
131: }
132: }
133:
134: if (matches.size() != 0
135: && !scriptSessionId.equals(ANY_SCRIPT_SESSION)) {
136: List<PublishListener> scriptSessionMatches = scriptSessions
137: .get(scriptSessionId);
138: if (scriptSessionMatches != null) {
139: matches.retainAll(scriptSessionMatches);
140: } else {
141: matches.clear();
142: }
143: }
144:
145: if (matches.size() != 0 && !prefix.equals(ANY_PREFIX)) {
146: List<PublishListener> prefixMatches = prefixes.get(prefix);
147: if (prefixMatches != null) {
148: matches.retainAll(prefixMatches);
149: } else {
150: matches.clear();
151: }
152: }
153:
154: if (matches.size() != 0 && !name.equals(ANY_NAME)) {
155: List<PublishListener> nameMatches = names.get(name);
156: if (nameMatches != null) {
157: matches.retainAll(nameMatches);
158: } else {
159: matches.clear();
160: }
161: }
162:
163: for (PublishListener listener : matches) {
164: Object subscriberData = allListeners.get(listener);
165: PublishEvent event = new PublishEvent(this , httpSessionId,
166: scriptSessionId, prefix, name, publisherData,
167: subscriberData, hubsVisited);
168: listener.publishHappened(event);
169: }
170: }
171:
172: /**
173: * @see #subscribe(String, String, String, String, PublishListener, Object)
174: */
175: public void subscribe(String prefix, String name,
176: PublishListener listener) {
177: subscribe(ANY_HTTP_SESSION, ANY_SCRIPT_SESSION, prefix, name,
178: listener, null);
179: }
180:
181: /**
182: * @see #subscribe(String, String, String, String, PublishListener, Object)
183: */
184: public void subscribe(String prefix, String name,
185: PublishListener listener, Object subscriberData) {
186: subscribe(ANY_HTTP_SESSION, ANY_SCRIPT_SESSION, prefix, name,
187: listener, subscriberData);
188: }
189:
190: /**
191: * @see #subscribe(String, String, String, String, PublishListener, Object)
192: */
193: public void subscribe(String httpSessionId, String scriptSessionId,
194: String prefix, String name, PublishListener listener) {
195: subscribe(httpSessionId, scriptSessionId, prefix, name,
196: listener, null);
197: }
198:
199: /**
200: * Allows registration of interest in named events based on library-specific
201: * prefix and event name. Global event matching is provided by passing "*"
202: * in the prefix and/or name arguments. Optional arguments may be specified
203: * for executing the specified handler function in a provided scope and for
204: * further filtering events prior to application.
205: * <p>
206: * The callback function will receive the following parameters
207: * (see OpenAjax.publish() for description of publisherData):
208: * <pre>
209: * function(prefix, name, subscriberData, publisherData){ ... }
210: * </pre>
211: * @param httpSessionId An HttpSession that we are subscribing to. The value
212: * should not be <code>null</code>, but can be {@link #ANY_HTTP_SESSION} to
213: * denote a match for all {@link HttpSession}s.
214: * @param scriptSessionId A ScriptSession that we are subscribing to. The value
215: * should not be <code>null</code>, but can be {@link #ANY_SCRIPT_SESSION}
216: * to denote a match for all {@link ScriptSession}s.
217: * @param prefix The prefix that corresponds to this library. This is the
218: * same value that was previously passed to registerLibrary(). Can be "*" to
219: * match the provided event name across all libraries.
220: * @param name The name of the event to listen for. Names can be any string.
221: * Can be "*" to match all events in the specified toolkit (see prefix). If
222: * both name and prefix specify "*", all events in the system will be routed
223: * to the registered handler (modulo any filtering provided by filter).
224: * @param listener The object to deliver messages to
225: * @param subscriberData Data to be send to the Listener along with the
226: * eventHappened message
227: */
228: public void subscribe(String httpSessionId, String scriptSessionId,
229: String prefix, String name, PublishListener listener,
230: Object subscriberData) {
231: checkNulls(httpSessionId, scriptSessionId, prefix, name);
232:
233: if (httpSessionId != null && httpSessionId != ANY_HTTP_SESSION) {
234: List<PublishListener> listeners = httpSessions
235: .get(httpSessionId);
236: if (listeners == null) {
237: listeners = new ArrayList<PublishListener>();
238: httpSessions.put(httpSessionId, listeners);
239: }
240: listeners.add(listener);
241: }
242:
243: if (scriptSessionId != null
244: && scriptSessionId != ANY_SCRIPT_SESSION) {
245: List<PublishListener> listeners = scriptSessions
246: .get(scriptSessionId);
247: if (listeners == null) {
248: listeners = new ArrayList<PublishListener>();
249: scriptSessions.put(scriptSessionId, listeners);
250: }
251: listeners.add(listener);
252: }
253:
254: if (prefix != null && prefix != ANY_PREFIX) {
255: List<PublishListener> listeners = prefixes.get(prefix);
256: if (listeners == null) {
257: listeners = new ArrayList<PublishListener>();
258: prefixes.put(prefix, listeners);
259: }
260: listeners.add(listener);
261: }
262:
263: if (name != null && name != ANY_NAME) {
264: List<PublishListener> listeners = names.get(name);
265: if (listeners == null) {
266: listeners = new ArrayList<PublishListener>();
267: names.put(name, listeners);
268: }
269: listeners.add(listener);
270: }
271:
272: allListeners.put(listener, subscriberData);
273: fireSubscribeHappenedEvent(httpSessionId, scriptSessionId,
274: prefix, name, listener);
275: }
276:
277: /**
278: * @see #unsubscribe(String, String, String, String, PublishListener)
279: */
280: public void unsubscribe(String prefix, String name,
281: PublishListener listener) {
282: unsubscribe(ANY_HTTP_SESSION, ANY_SCRIPT_SESSION, prefix, name,
283: listener);
284: }
285:
286: /**
287: * Removes a subscription to an event. In order for a subscription to be
288: * removed, the values of the parameters supplied to OpenAjax.unsubscribe()
289: * must exactly match the values of the parameters supplied to a previous
290: * call to OpenAjax.subscribe(). Note that it is possible that one
291: * invocation of OpenAjax.unsubscribe() might result in removal of multiple
292: * subscriptions.
293: * @param prefix The prefix that corresponds to this library. This is the
294: * same value that was previously passed to registerLibrary(). Can be "*" to
295: * match the provided event name across all libraries.
296: * @param name The name of the event to listen for. Names can be any string.
297: * Can be "*" to match all events in the specified toolkit (see prefix). If
298: * both name and prefix specify "*", all events in the system will be routed
299: * to the registered handler (modulo any filtering provided by filter).
300: * @param listener The object to deliver messages to
301: */
302: public void unsubscribe(String httpSessionId,
303: String scriptSessionId, String prefix, String name,
304: PublishListener listener) {
305: checkNulls(httpSessionId, scriptSessionId, prefix, name);
306:
307: if (httpSessionId != null && httpSessionId != ANY_HTTP_SESSION) {
308: List<PublishListener> listeners = httpSessions
309: .get(httpSessionId);
310: if (listeners != null) {
311: listeners.remove(listener);
312: if (listeners.size() == 0) {
313: httpSessions.remove(httpSessionId);
314: }
315: }
316: }
317:
318: if (scriptSessionId != null
319: && scriptSessionId != ANY_SCRIPT_SESSION) {
320: List<PublishListener> listeners = scriptSessions
321: .get(scriptSessionId);
322: if (listeners != null) {
323: listeners.remove(listener);
324: if (listeners.size() == 0) {
325: scriptSessions.remove(scriptSessionId);
326: }
327: }
328: }
329:
330: if (prefix != null && prefix != ANY_PREFIX) {
331: List<PublishListener> listeners = prefixes.get(prefix);
332: if (listeners != null) {
333: listeners.remove(listener);
334: if (listeners.size() == 0) {
335: prefixes.remove(prefix);
336: }
337: }
338: }
339:
340: if (name != null && name != ANY_NAME) {
341: List<PublishListener> listeners = names.get(name);
342: if (listeners != null) {
343: listeners.remove(listener);
344: if (listeners.size() == 0) {
345: names.remove(name);
346: }
347: }
348: }
349:
350: allListeners.remove(listener);
351: fireUnsubscribeHappenedEvent(httpSessionId, scriptSessionId,
352: prefix, name, listener);
353: }
354:
355: /**
356: * Maintain the list of {@link SubscriptionListener}s
357: * @param li the SubscriptionListener to add
358: */
359: public void addSubscriptionListener(SubscriptionListener li) {
360: subscriptionListeners.add(SubscriptionListener.class, li);
361: }
362:
363: /**
364: * Maintain the list of {@link SubscriptionListener}s
365: * @param li the ScriptSessionListener to remove
366: */
367: public void removeSubscriptionListener(SubscriptionListener li) {
368: subscriptionListeners.remove(SubscriptionListener.class, li);
369: }
370:
371: /**
372: * If other hubs wish to synchronize with the messages passed through this
373: * hub they need to be able to filter to keep the message storm down.
374: * @return A set of the names that this hub is interested in.
375: */
376: public Set<String> getSubscribedNames() {
377: return Collections.unmodifiableSet(names.keySet());
378: }
379:
380: /**
381: * If other hubs wish to synchronize with the messages passed through this
382: * hub they need to be able to filter to keep the message storm down.
383: * @return A set of the prefixes that this hub is interested in.
384: */
385: public Set<String> getSubscribedPrefixes() {
386: return Collections.unmodifiableSet(prefixes.keySet());
387: }
388:
389: /**
390: * To allow hubs to not create publish loops we need to know what
391: * @return The ID of this Hub
392: */
393: public String getHubId() {
394: if (hubId == null) {
395: hubId = "org.directwebremoting.proxy.openajax.PubSubHub."
396: + hashCode();
397: }
398:
399: return hubId;
400: }
401:
402: /* (non-Javadoc)
403: * @see java.lang.Object#toString()
404: */
405: @Override
406: public String toString() {
407: return getHubId();
408: }
409:
410: /**
411: * This should be called whenever a {@link ScriptSession} is destroyed
412: * @param httpSessionId The ID match of the {@link HttpSession} that was subscribed to
413: * @param scriptSessionId The ID match of the {@link ScriptSession} that was subscribed to
414: * @param prefix The prefix match that was subscribed to
415: * @param name The name match that was subscripbed to
416: * @param listener The subscribed object
417: */
418: protected void fireSubscribeHappenedEvent(String httpSessionId,
419: String scriptSessionId, String prefix, String name,
420: PublishListener listener) {
421: SubscriptionEvent ev = new SubscriptionEvent(this ,
422: httpSessionId, scriptSessionId, prefix, name, listener);
423: Object[] listeners = subscriptionListeners.getListenerList();
424: for (int i = 0; i < listeners.length - 2; i += 2) {
425: if (listeners[i] == SubscriptionListener.class) {
426: ((SubscriptionListener) listeners[i + 1])
427: .subscribeHappened(ev);
428: }
429: }
430: }
431:
432: /**
433: * This should be called whenever a {@link ScriptSession} is created
434: * @param httpSessionId The ID match of the {@link HttpSession} that was subscribed to
435: * @param scriptSessionId The ID match of the {@link ScriptSession} that was subscribed to
436: * @param prefix The prefix match that was subscribed to
437: * @param name The name match that was subscripbed to
438: * @param listener The subscribed object
439: */
440: protected void fireUnsubscribeHappenedEvent(String httpSessionId,
441: String scriptSessionId, String prefix, String name,
442: PublishListener listener) {
443: SubscriptionEvent ev = new SubscriptionEvent(this ,
444: httpSessionId, scriptSessionId, prefix, name, listener);
445: Object[] listeners = subscriptionListeners.getListenerList();
446: for (int i = 0; i < listeners.length - 2; i += 2) {
447: if (listeners[i] == SubscriptionListener.class) {
448: ((SubscriptionListener) listeners[i + 1])
449: .unsubscribeHappened(ev);
450: }
451: }
452: }
453:
454: /**
455: * The list of current {@link SubscriptionListener}s
456: */
457: protected EventListenerList subscriptionListeners = new EventListenerList();
458:
459: /**
460: * A constant to denote a match to any <code>prefix</code>.
461: */
462: public static final String ANY_PREFIX = "*";
463:
464: /**
465: * A constant to denote a match to any <code>name</code>.
466: */
467: public static final String ANY_NAME = "*";
468:
469: /**
470: * A constant to denote a match to any {@link HttpSession}.
471: */
472: public static final String ANY_HTTP_SESSION = "*";
473:
474: /**
475: * A constant to denote a match to any {@link ScriptSession}.
476: */
477: public static final String ANY_SCRIPT_SESSION = "*";
478:
479: /**
480: * Throw if any of the parameters are null
481: */
482: private void checkNulls(String httpSessionId,
483: String scriptSessionId, String prefix, String name) {
484: if (httpSessionId == null) {
485: throw new NullPointerException(
486: "httpSessionId may not be null. Use PubSubHub.ANY_HTTP_SESSION for broad matching");
487: }
488:
489: if (scriptSessionId == null) {
490: throw new NullPointerException(
491: "scriptSession may not be null. Use PubSubHub.ANY_SCRIPT_SESSION for broad matching");
492: }
493:
494: if (prefix == null) {
495: throw new NullPointerException(
496: "prefix may not be null. Use PubSubHub.ANY_PREFIX or \"*\" for broad matching");
497: }
498:
499: if (name == null) {
500: throw new NullPointerException(
501: "name may not be null. Use PubSubHub.ANY_NAME or \"*\" for broad matching");
502: }
503: }
504:
505: private String hubId = null;
506:
507: private Map<PublishListener, Object> allListeners = new HashMap<PublishListener, Object>();
508:
509: private Map<String, List<PublishListener>> names = new HashMap<String, List<PublishListener>>();
510:
511: private Map<String, List<PublishListener>> prefixes = new HashMap<String, List<PublishListener>>();
512:
513: private Map<String, List<PublishListener>> httpSessions = new HashMap<String, List<PublishListener>>();
514:
515: private Map<String, List<PublishListener>> scriptSessions = new HashMap<String, List<PublishListener>>();
516: }
|