jdk/src/share/classes/javax/management/NotificationBroadcasterSupport.java
changeset 2 90ce3da70b43
child 833 bfa2bef7517c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/management/NotificationBroadcasterSupport.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,354 @@
+/*
+ * Copyright 1999-2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.management;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+
+import com.sun.jmx.remote.util.ClassLogger;
+
+/**
+ * <p>Provides an implementation of {@link
+ * javax.management.NotificationEmitter NotificationEmitter}
+ * interface.  This can be used as the super class of an MBean that
+ * sends notifications.</p>
+ *
+ * <p>By default, the notification dispatch model is synchronous.
+ * That is, when a thread calls sendNotification, the
+ * <code>NotificationListener.handleNotification</code> method of each listener
+ * is called within that thread. You can override this default
+ * by overriding <code>handleNotification</code> in a subclass, or by passing an
+ * Executor to the constructor.</p>
+ *
+ * <p>If the method call of a filter or listener throws an {@link Exception},
+ * then that exception does not prevent other listeners from being invoked.  However,
+ * if the method call of a filter or of {@code Executor.execute} or of
+ * {@code handleNotification} (when no {@code Excecutor} is specified) throws an
+ * {@link Error}, then that {@code Error} is propagated to the caller of
+ * {@link #sendNotification sendNotification}.</p>
+ *
+ * <p>Remote listeners added using the JMX Remote API (see JMXConnector) are not
+ * usually called synchronously.  That is, when sendNotification returns, it is
+ * not guaranteed that any remote listeners have yet received the notification.</p>
+ *
+ * @since 1.5
+ */
+public class NotificationBroadcasterSupport implements NotificationEmitter {
+    /**
+     * Constructs a NotificationBroadcasterSupport where each listener is invoked by the
+     * thread sending the notification. This constructor is equivalent to
+     * {@link NotificationBroadcasterSupport#NotificationBroadcasterSupport(Executor,
+     * MBeanNotificationInfo[] info) NotificationBroadcasterSupport(null, null)}.
+     */
+    public NotificationBroadcasterSupport() {
+        this(null, (MBeanNotificationInfo[]) null);
+    }
+
+    /**
+     * Constructs a NotificationBroadcasterSupport where each listener is invoked using
+     * the given {@link java.util.concurrent.Executor}. When {@link #sendNotification
+     * sendNotification} is called, a listener is selected if it was added with a null
+     * {@link NotificationFilter}, or if {@link NotificationFilter#isNotificationEnabled
+     * isNotificationEnabled} returns true for the notification being sent. The call to
+     * <code>NotificationFilter.isNotificationEnabled</code> takes place in the thread
+     * that called <code>sendNotification</code>. Then, for each selected listener,
+     * {@link Executor#execute executor.execute} is called with a command
+     * that calls the <code>handleNotification</code> method.
+     * This constructor is equivalent to
+     * {@link NotificationBroadcasterSupport#NotificationBroadcasterSupport(Executor,
+     * MBeanNotificationInfo[] info) NotificationBroadcasterSupport(executor, null)}.
+     * @param executor an executor used by the method <code>sendNotification</code> to
+     * send each notification. If it is null, the thread calling <code>sendNotification</code>
+     * will invoke the <code>handleNotification</code> method itself.
+     * @since 1.6
+     */
+    public NotificationBroadcasterSupport(Executor executor) {
+        this(executor, (MBeanNotificationInfo[]) null);
+    }
+
+    /**
+     * <p>Constructs a NotificationBroadcasterSupport with information
+     * about the notifications that may be sent.  Each listener is
+     * invoked by the thread sending the notification.  This
+     * constructor is equivalent to {@link
+     * NotificationBroadcasterSupport#NotificationBroadcasterSupport(Executor,
+     * MBeanNotificationInfo[] info)
+     * NotificationBroadcasterSupport(null, info)}.</p>
+     *
+     * <p>If the <code>info</code> array is not empty, then it is
+     * cloned by the constructor as if by {@code info.clone()}, and
+     * each call to {@link #getNotificationInfo()} returns a new
+     * clone.</p>
+     *
+     * @param info an array indicating, for each notification this
+     * MBean may send, the name of the Java class of the notification
+     * and the notification type.  Can be null, which is equivalent to
+     * an empty array.
+     *
+     * @since 1.6
+     */
+    public NotificationBroadcasterSupport(MBeanNotificationInfo... info) {
+        this(null, info);
+    }
+
+    /**
+     * <p>Constructs a NotificationBroadcasterSupport with information about the notifications that may be sent,
+     * and where each listener is invoked using the given {@link java.util.concurrent.Executor}.</p>
+     *
+     * <p>When {@link #sendNotification sendNotification} is called, a
+     * listener is selected if it was added with a null {@link
+     * NotificationFilter}, or if {@link
+     * NotificationFilter#isNotificationEnabled isNotificationEnabled}
+     * returns true for the notification being sent. The call to
+     * <code>NotificationFilter.isNotificationEnabled</code> takes
+     * place in the thread that called
+     * <code>sendNotification</code>. Then, for each selected
+     * listener, {@link Executor#execute executor.execute} is called
+     * with a command that calls the <code>handleNotification</code>
+     * method.</p>
+     *
+     * <p>If the <code>info</code> array is not empty, then it is
+     * cloned by the constructor as if by {@code info.clone()}, and
+     * each call to {@link #getNotificationInfo()} returns a new
+     * clone.</p>
+     *
+     * @param executor an executor used by the method
+     * <code>sendNotification</code> to send each notification. If it
+     * is null, the thread calling <code>sendNotification</code> will
+     * invoke the <code>handleNotification</code> method itself.
+     *
+     * @param info an array indicating, for each notification this
+     * MBean may send, the name of the Java class of the notification
+     * and the notification type.  Can be null, which is equivalent to
+     * an empty array.
+     *
+     * @since 1.6
+     */
+    public NotificationBroadcasterSupport(Executor executor,
+                                          MBeanNotificationInfo... info) {
+        this.executor = (executor != null) ? executor : defaultExecutor;
+
+        notifInfo = info == null ? NO_NOTIFICATION_INFO : info.clone();
+    }
+
+    /**
+     * Adds a listener.
+     *
+     * @param listener The listener to receive notifications.
+     * @param filter The filter object. If filter is null, no
+     * filtering will be performed before handling notifications.
+     * @param handback An opaque object to be sent back to the
+     * listener when a notification is emitted. This object cannot be
+     * used by the Notification broadcaster object. It should be
+     * resent unchanged with the notification to the listener.
+     *
+     * @exception IllegalArgumentException thrown if the listener is null.
+     *
+     * @see #removeNotificationListener
+     */
+    public void addNotificationListener(NotificationListener listener,
+                                        NotificationFilter filter,
+                                        Object handback) {
+
+        if (listener == null) {
+            throw new IllegalArgumentException ("Listener can't be null") ;
+        }
+
+        listenerList.add(new ListenerInfo(listener, filter, handback));
+    }
+
+    public void removeNotificationListener(NotificationListener listener)
+            throws ListenerNotFoundException {
+
+        ListenerInfo wildcard = new WildcardListenerInfo(listener);
+        boolean removed =
+            listenerList.removeAll(Collections.singleton(wildcard));
+        if (!removed)
+            throw new ListenerNotFoundException("Listener not registered");
+    }
+
+    public void removeNotificationListener(NotificationListener listener,
+                                           NotificationFilter filter,
+                                           Object handback)
+            throws ListenerNotFoundException {
+
+        ListenerInfo li = new ListenerInfo(listener, filter, handback);
+        boolean removed = listenerList.remove(li);
+        if (!removed) {
+            throw new ListenerNotFoundException("Listener not registered " +
+                                                "(with this filter and " +
+                                                "handback)");
+            // or perhaps not registered at all
+        }
+    }
+
+    public MBeanNotificationInfo[] getNotificationInfo() {
+        if (notifInfo.length == 0)
+            return notifInfo;
+        else
+            return notifInfo.clone();
+    }
+
+
+    /**
+     * Sends a notification.
+     *
+     * If an {@code Executor} was specified in the constructor, it will be given one
+     * task per selected listener to deliver the notification to that listener.
+     *
+     * @param notification The notification to send.
+     */
+    public void sendNotification(Notification notification) {
+
+        if (notification == null) {
+            return;
+        }
+
+        boolean enabled;
+
+        for (ListenerInfo li : listenerList) {
+            try {
+                enabled = li.filter == null ||
+                    li.filter.isNotificationEnabled(notification);
+            } catch (Exception e) {
+                if (logger.debugOn()) {
+                    logger.debug("sendNotification", e);
+                }
+
+                continue;
+            }
+
+            if (enabled) {
+                executor.execute(new SendNotifJob(notification, li));
+            }
+        }
+    }
+
+    /**
+     * <p>This method is called by {@link #sendNotification
+     * sendNotification} for each listener in order to send the
+     * notification to that listener.  It can be overridden in
+     * subclasses to change the behavior of notification delivery,
+     * for instance to deliver the notification in a separate
+     * thread.</p>
+     *
+     * <p>The default implementation of this method is equivalent to
+     * <pre>
+     * listener.handleNotification(notif, handback);
+     * </pre>
+     *
+     * @param listener the listener to which the notification is being
+     * delivered.
+     * @param notif the notification being delivered to the listener.
+     * @param handback the handback object that was supplied when the
+     * listener was added.
+     *
+     */
+    protected void handleNotification(NotificationListener listener,
+                                      Notification notif, Object handback) {
+        listener.handleNotification(notif, handback);
+    }
+
+    // private stuff
+    private static class ListenerInfo {
+        NotificationListener listener;
+        NotificationFilter filter;
+        Object handback;
+
+        ListenerInfo(NotificationListener listener,
+                     NotificationFilter filter,
+                     Object handback) {
+            this.listener = listener;
+            this.filter = filter;
+            this.handback = handback;
+        }
+
+        public boolean equals(Object o) {
+            if (!(o instanceof ListenerInfo))
+                return false;
+            ListenerInfo li = (ListenerInfo) o;
+            if (li instanceof WildcardListenerInfo)
+                return (li.listener == listener);
+            else
+                return (li.listener == listener && li.filter == filter
+                        && li.handback == handback);
+        }
+    }
+
+    private static class WildcardListenerInfo extends ListenerInfo {
+        WildcardListenerInfo(NotificationListener listener) {
+            super(listener, null, null);
+        }
+
+        public boolean equals(Object o) {
+            assert (!(o instanceof WildcardListenerInfo));
+            return o.equals(this);
+        }
+    }
+
+    private List<ListenerInfo> listenerList =
+        new CopyOnWriteArrayList<ListenerInfo>();
+
+    // since 1.6
+    private final Executor executor;
+    private final MBeanNotificationInfo[] notifInfo;
+
+    private final static Executor defaultExecutor = new Executor() {
+            // DirectExecutor using caller thread
+            public void execute(Runnable r) {
+                r.run();
+            }
+        };
+
+    private static final MBeanNotificationInfo[] NO_NOTIFICATION_INFO =
+        new MBeanNotificationInfo[0];
+
+    private class SendNotifJob implements Runnable {
+        public SendNotifJob(Notification notif, ListenerInfo listenerInfo) {
+            this.notif = notif;
+            this.listenerInfo = listenerInfo;
+        }
+
+        public void run() {
+            try {
+                handleNotification(listenerInfo.listener,
+                                   notif, listenerInfo.handback);
+            } catch (Exception e) {
+                if (logger.debugOn()) {
+                    logger.debug("SendNotifJob-run", e);
+                }
+            }
+        }
+
+        private final Notification notif;
+        private final ListenerInfo listenerInfo;
+    }
+
+    private static final ClassLogger logger =
+        new ClassLogger("javax.management", "NotificationBroadcasterSupport");
+}