Java tutorial
/** * Copyright (C) 2012 JBoss Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jboss.dashboard.commons.events; import java.util.*; import java.lang.ref.WeakReference; import org.jboss.dashboard.database.hibernate.HibernateTxFragment; /** * The publisher party int the publish/subscribe GoF design pattern. * <p>WARNING: In order to solve the -<i>lapsed listener</i>- problem the reference * to the subscriber stored by the Publisher is weak. * This it means that if the Subscriber reference is lost then is also automatically removed from the * subscribers list. To avoid this issue ensure that your Subscriber instance is referenced in your object model * and exists as long as the Publisher is alive. */ public class Publisher { /** * Logger */ private transient static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory .getLog(Publisher.class); /** * The event representing all events. * <p>NOTE: <code>EVENT_ALL=0</code> is a reserved identifier. */ public static final int EVENT_ALL = 0; /** * The registered subscribers grouped by eventId. */ protected Map subscribers; public Publisher() { subscribers = Collections.synchronizedMap(new HashMap()); } /** * Register a subscriber interested in ALL events. */ public synchronized void subscribe(Subscriber subscriber) { if (subscriber == null) return; subscribe(subscriber, EVENT_ALL); } /** * Register a subscriber interested in a single event. * @param eventId The event interested in. * <p>NOTE: <code>EVENT_ALL=0</code> is a reserved identifier. */ public synchronized void subscribe(Subscriber subscriber, int eventId) { if (subscriber == null) return; // Discard duplicates. unsubscribe(subscriber, eventId); // Avoid subscribers to be "leaked" by the process manager. // Solve the -lapsed listener- problem. List eventSubscribers = (List) subscribers.get(new Integer(eventId)); if (eventSubscribers == null) { eventSubscribers = Collections.synchronizedList(new ArrayList()); subscribers.put(new Integer(eventId), eventSubscribers); } eventSubscribers.add(new WeakReference(subscriber)); } /** * Subscribe a collection subscribers for a concrete event. */ public synchronized void subscribe(Collection c, int eventId) { Iterator it = c.iterator(); while (it.hasNext()) { this.subscribe((Subscriber) it.next(), eventId); } } /** * Removes a registered subscriber. */ public synchronized void unsubscribe(Subscriber subscriber) { if (subscriber == null) return; Iterator it = subscribers.keySet().iterator(); while (it.hasNext()) { Integer eventId = (Integer) it.next(); unsubscribe(subscriber, eventId.intValue()); } } /** * Removes a registered subscriber. * @param eventId The event interested in. */ public synchronized void unsubscribe(Subscriber subscriber, int eventId) { if (subscriber == null) return; List eventSubscribers = (List) subscribers.get(new Integer(eventId)); if (eventSubscribers == null) return; Iterator it = eventSubscribers.iterator(); while (it.hasNext()) { WeakReference wr = (WeakReference) it.next(); Subscriber regSubscr = (Subscriber) wr.get(); if (regSubscr == null || regSubscr.equals(subscriber)) it.remove(); } } /** * Retrieve registered subscribers. * @return A map of eventId (Integer) / Subscriber (List). * If no subscribers are registered for an event then no entry is returned. */ public synchronized Map getSubscribers() { Map results = new HashMap(); Iterator it = subscribers.keySet().iterator(); while (it.hasNext()) { Integer eventId = (Integer) it.next(); results.put(eventId, getSubscribers(eventId.intValue())); } return results; } /** * Retrieve registered Subscribers for a specified event. */ public synchronized List getSubscribers(int eventId) { List results = Collections.synchronizedList(new ArrayList()); List eventSubscribers = (List) subscribers.get(new Integer(eventId)); if (eventSubscribers == null) return results; Iterator it = eventSubscribers.iterator(); while (it.hasNext()) { WeakReference wr = (WeakReference) it.next(); Subscriber subscriber = (Subscriber) wr.get(); if (subscriber == null) it.remove(); else results.add(subscriber); } return Collections.unmodifiableList(results); } /** * Interface used to fire events. * @param eventInfo The object where event occurs. */ public void notifyEvent(final int eventId, final Object eventInfo) { // Calculate the target subscribers. // Those subscribed to ALL_EVENT must also be taken. Set _subscribersToNotify = new HashSet(); _subscribersToNotify.addAll(getSubscribers(eventId)); _subscribersToNotify.addAll(getSubscribers(EVENT_ALL)); Iterator it = _subscribersToNotify.iterator(); while (it.hasNext()) { try { final Subscriber subscriber = (Subscriber) it.next(); switch (subscriber.getTransactionContext()) { // Notify to subscriber right now within the current transaction and thread. case Subscriber.TXCONTEXT_DEFAULT: subscriber.notifyEvent(eventId, eventInfo); break; // Notify to subscriber just before the current transaction completes succesfully. case Subscriber.TXCONTEXT_BEFORE_COMMIT: new HibernateTxFragment(false, true) { protected void beforeCommit() throws Exception { subscriber.notifyEvent(eventId, eventInfo); } }.execute(); break; // Notify to subscriber after the current transaction completes succesfully. case Subscriber.TXCONTEXT_AFTER_COMMIT: new HibernateTxFragment(false, true) { protected void afterCommit() throws Exception { subscriber.notifyEvent(eventId, eventInfo); } }.execute(); break; // Notify to subscriber within a new transaction launched in a separated thread. case Subscriber.TXCONTEXT_NEW_THREAD: new HibernateTxFragment(false, true) { protected void afterCommit() throws Exception { new Thread(new Runnable() { public void run() { subscriber.notifyEvent(eventId, eventInfo); } }).start(); } }.execute(); break; // Not supported notification mode. default: throw new IllegalArgumentException( "Subscriber notification mode not supported: " + subscriber.getTransactionContext()); } } catch (Throwable e) { if (e instanceof RuntimeException) throw (RuntimeException) e; else throw new RuntimeException("Exception occurred when firing event: " + eventId, e); } } } }