jdk/src/share/classes/javax/management/namespace/VirtualEventManager.java
changeset 1156 bbc2d15aaf7a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/management/namespace/VirtualEventManager.java	Thu Sep 04 14:46:36 2008 +0200
@@ -0,0 +1,378 @@
+/*
+ * Copyright 2008 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.namespace;
+
+import com.sun.jmx.remote.util.ClassLogger;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import javax.management.InstanceNotFoundException;
+import javax.management.ListenerNotFoundException;
+import javax.management.MBeanNotificationInfo;
+import javax.management.Notification;
+import javax.management.NotificationEmitter;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.event.EventConsumer;
+
+/**
+ * <p>This class maintains a list of subscribers for ObjectName patterns and
+ * allows a notification to be sent to all subscribers for a given ObjectName.
+ * It is typically used in conjunction with {@link MBeanServerSupport}
+ * to implement a namespace with Virtual MBeans that can emit notifications.
+ * The {@code VirtualEventManager} keeps track of the listeners that have been
+ * added to each Virtual MBean. When an event occurs that should trigger a
+ * notification from a Virtual MBean, the {@link #publish publish} method can
+ * be used to send it to the appropriate listeners.</p>
+ * @since 1.7
+ */
+public class VirtualEventManager implements EventConsumer {
+    /**
+     * <p>Create a new {@code VirtualEventManager}.</p>
+     */
+    public VirtualEventManager() {
+    }
+
+    public void subscribe(
+            ObjectName name,
+            NotificationListener listener,
+            NotificationFilter filter,
+            Object handback) {
+
+        if (logger.traceOn())
+            logger.trace("subscribe", "" + name);
+
+        if (name == null)
+            throw new IllegalArgumentException("Null MBean name");
+
+        if (listener == null)
+            throw new IllegalArgumentException("Null listener");
+
+        Map<ObjectName, List<ListenerInfo>> map =
+                name.isPattern() ? patternSubscriptionMap : exactSubscriptionMap;
+
+        final ListenerInfo li = new ListenerInfo(listener, filter, handback);
+        List<ListenerInfo> list;
+
+        synchronized (map) {
+            list = map.get(name);
+            if (list == null) {
+                list = new ArrayList<ListenerInfo>();
+                map.put(name, list);
+            }
+            list.add(li);
+        }
+    }
+
+    public void unsubscribe(
+            ObjectName name, NotificationListener listener)
+            throws ListenerNotFoundException {
+
+        if (logger.traceOn())
+            logger.trace("unsubscribe2", "" + name);
+
+        if (name == null)
+            throw new IllegalArgumentException("Null MBean name");
+
+        if (listener == null)
+            throw new ListenerNotFoundException();
+
+        Map<ObjectName, List<ListenerInfo>> map =
+                name.isPattern() ? patternSubscriptionMap : exactSubscriptionMap;
+
+        final ListenerInfo li = new ListenerInfo(listener, null, null);
+        List<ListenerInfo> list;
+        synchronized (map) {
+            list = map.get(name);
+            if (list == null || !list.remove(li))
+                throw new ListenerNotFoundException();
+
+            if (list.isEmpty())
+                map.remove(name);
+        }
+    }
+
+    /**
+     * <p>Unsubscribes a listener which is listening to an MBean or a set of
+     * MBeans represented by an {@code ObjectName} pattern.</p>
+     *
+     * <p>The listener to be removed must have been added by the {@link
+     * #subscribe subscribe} method with the given {@code name}, {@code filter},
+     * and {@code handback}. If the {@code
+     * name} is a pattern, then the {@code subscribe} must have used the same
+     * pattern. If the same listener has been subscribed more than once to the
+     * {@code name} with the same filter and handback, only one listener is
+     * removed.</p>
+     *
+     * @param name The name of the MBean or an {@code ObjectName} pattern
+     * representing a set of MBeans to which the listener was subscribed.
+     * @param listener A listener that was previously subscribed to the
+     * MBean(s).
+     *
+     * @throws ListenerNotFoundException The given {@code listener} was not
+     * subscribed to the given {@code name}.
+     *
+     * @see #subscribe
+     */
+    public void unsubscribe(
+            ObjectName name, NotificationListener listener,
+            NotificationFilter filter, Object handback)
+            throws ListenerNotFoundException {
+
+        if (logger.traceOn())
+            logger.trace("unsubscribe4", "" + name);
+
+        if (name == null)
+            throw new IllegalArgumentException("Null MBean name");
+
+        if (listener == null)
+            throw new ListenerNotFoundException();
+
+        Map<ObjectName, List<ListenerInfo>> map =
+                name.isPattern() ? patternSubscriptionMap : exactSubscriptionMap;
+
+        List<ListenerInfo> list;
+        synchronized (map) {
+            list = map.get(name);
+            boolean removed = false;
+            for (Iterator<ListenerInfo> it = list.iterator(); it.hasNext(); ) {
+                ListenerInfo li = it.next();
+                if (li.equals(listener, filter, handback)) {
+                    it.remove();
+                    removed = true;
+                    break;
+                }
+            }
+            if (!removed)
+                throw new ListenerNotFoundException();
+
+            if (list.isEmpty())
+                map.remove(name);
+        }
+    }
+
+    /**
+     * <p>Sends a notification to the subscribers for a given MBean.</p>
+     *
+     * <p>For each listener subscribed with an {@code ObjectName} that either
+     * is equal to {@code emitterName} or is a pattern that matches {@code
+     * emitterName}, if the associated filter accepts the notification then it
+     * is forwarded to the listener.</p>
+     *
+     * @param emitterName The name of the MBean emitting the notification.
+     * @param n The notification being sent by the MBean called
+     * {@code emitterName}.
+     *
+     * @throws IllegalArgumentException If the emitterName of the
+     * notification is null or is an {@code ObjectName} pattern.
+     */
+    public void publish(ObjectName emitterName, Notification n) {
+        if (logger.traceOn())
+            logger.trace("publish", "" + emitterName);
+
+        if (n == null)
+            throw new IllegalArgumentException("Null notification");
+
+        if (emitterName == null) {
+            throw new IllegalArgumentException(
+                    "Null emitter name");
+        } else if (emitterName.isPattern()) {
+            throw new IllegalArgumentException(
+                    "The emitter must not be an ObjectName pattern");
+        }
+
+        final List<ListenerInfo> listeners = new ArrayList<ListenerInfo>();
+
+        // If there are listeners for this exact name, add them.
+        synchronized (exactSubscriptionMap) {
+            List<ListenerInfo> exactListeners =
+                    exactSubscriptionMap.get(emitterName);
+            if (exactListeners != null)
+                listeners.addAll(exactListeners);
+        }
+
+        // Loop over subscription patterns, and add all listeners for each
+        // one that matches the emitterName name.
+        synchronized (patternSubscriptionMap) {
+            for (ObjectName on : patternSubscriptionMap.keySet()) {
+                if (on.apply(emitterName))
+                    listeners.addAll(patternSubscriptionMap.get(on));
+            }
+        }
+
+        // Send the notification to all the listeners we found.
+        sendNotif(listeners, n);
+    }
+
+    /**
+     * <p>Returns a {@link NotificationEmitter} object which can be used to
+     * subscribe or unsubscribe for notifications with the named
+     * mbean.  The returned object implements {@link
+     * NotificationEmitter#addNotificationListener
+     * addNotificationListener(listener, filter, handback)} as
+     * {@link #subscribe this.subscribe(name, listener, filter, handback)}
+     * and the two {@code removeNotificationListener} methods from {@link
+     * NotificationEmitter} as the corresponding {@code unsubscribe} methods
+     * from this class.</p>
+     *
+     * @param name   The name of the MBean whose notifications are being
+     *        subscribed, or unsuscribed.
+     *
+     * @return A {@link NotificationEmitter}
+     *         that can be used to subscribe or unsubscribe for
+     *         notifications emitted by the named MBean, or {@code null} if
+     *         the MBean does not emit notifications and should not
+     *         be considered as a {@code NotificationBroadcaster}.  This class
+     *         never returns null but a subclass is allowed to.
+     *
+     * @throws InstanceNotFoundException if {@code name} does not exist.
+     * This implementation never throws {@code InstanceNotFoundException} but
+     * a subclass is allowed to override this method to do so.
+     */
+    public NotificationEmitter
+            getNotificationEmitterFor(final ObjectName name)
+            throws InstanceNotFoundException {
+        final NotificationEmitter emitter = new NotificationEmitter() {
+            public void addNotificationListener(NotificationListener listener,
+                    NotificationFilter filter, Object handback)
+                    throws IllegalArgumentException {
+                subscribe(name, listener, filter, handback);
+            }
+
+            public void removeNotificationListener(
+                    NotificationListener listener)
+                    throws ListenerNotFoundException {
+                unsubscribe(name, listener);
+            }
+
+            public void removeNotificationListener(NotificationListener listener,
+                                                   NotificationFilter filter,
+                                                   Object handback)
+                    throws ListenerNotFoundException {
+                unsubscribe(name, listener, filter, handback);
+            }
+
+            public MBeanNotificationInfo[] getNotificationInfo() {
+                // Never called.
+                return null;
+            }
+        };
+        return emitter;
+    }
+
+    // ---------------------------------
+    // private stuff
+    // ---------------------------------
+
+    private static class ListenerInfo {
+        public final NotificationListener listener;
+        public final NotificationFilter filter;
+        public final Object handback;
+
+        public ListenerInfo(NotificationListener listener,
+                NotificationFilter filter,
+                Object handback) {
+
+            if (listener == null) {
+                throw new IllegalArgumentException("Null listener.");
+            }
+
+            this.listener = listener;
+            this.filter = filter;
+            this.handback = handback;
+        }
+
+        /* Two ListenerInfo instances are equal if they have the same
+         * NotificationListener.  This means that we can use List.remove
+         * to implement the two-argument removeNotificationListener.
+         */
+        @Override
+        public boolean equals(Object o) {
+            if (o == this) {
+                return true;
+            }
+
+            if (!(o instanceof ListenerInfo)) {
+                return false;
+            }
+
+            return listener.equals(((ListenerInfo)o).listener);
+        }
+
+        /* Method that compares all four fields, appropriate for the
+         * four-argument removeNotificationListener.
+         */
+        boolean equals(
+                NotificationListener listener,
+                NotificationFilter filter,
+                Object handback) {
+            return (this.listener == listener && same(this.filter, filter)
+                    && same(this.handback, handback));
+        }
+
+        private static boolean same(Object x, Object y) {
+            if (x == y)
+                return true;
+            if (x == null)
+                return false;
+            return x.equals(y);
+        }
+
+        @Override
+        public int hashCode() {
+            return listener.hashCode();
+        }
+    }
+
+    private static void sendNotif(List<ListenerInfo> listeners, Notification n) {
+        for (ListenerInfo li : listeners) {
+            if (li.filter == null ||
+                    li.filter.isNotificationEnabled(n)) {
+                try {
+                    li.listener.handleNotification(n, li.handback);
+                } catch (Exception e) {
+                    logger.trace("sendNotif", "handleNotification", e);
+                }
+            }
+        }
+    }
+
+    // ---------------------------------
+    // private variables
+    // ---------------------------------
+
+    private final Map<ObjectName, List<ListenerInfo>> exactSubscriptionMap =
+            new HashMap<ObjectName, List<ListenerInfo>>();
+    private final Map<ObjectName, List<ListenerInfo>> patternSubscriptionMap =
+            new HashMap<ObjectName, List<ListenerInfo>>();
+
+    // trace issue
+    private static final ClassLogger logger =
+            new ClassLogger("javax.management.event", "EventManager");
+}