org.jboss.dashboard.commons.events.Publisher.java Source code

Java tutorial

Introduction

Here is the source code for org.jboss.dashboard.commons.events.Publisher.java

Source

/**
 * 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);
            }
        }
    }
}