jdk/src/share/classes/javax/management/event/EventSubscriber.java
changeset 4159 9e3aae7675f1
parent 4158 0b4d21bc8b5c
parent 4156 acaa49a2768a
child 4160 bda0a85afcb7
equal deleted inserted replaced
4158:0b4d21bc8b5c 4159:9e3aae7675f1
     1 /*
       
     2  * Copyright 2008 Sun Microsystems, Inc.  All Rights Reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Sun designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Sun in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
       
    22  * CA 95054 USA or visit www.sun.com if you need additional information or
       
    23  * have any questions.
       
    24  */
       
    25 
       
    26 package javax.management.event;
       
    27 
       
    28 import com.sun.jmx.remote.util.ClassLogger;
       
    29 import java.io.IOException;
       
    30 import java.lang.ref.WeakReference;
       
    31 import java.lang.reflect.Method;
       
    32 import java.security.AccessController;
       
    33 import java.security.PrivilegedActionException;
       
    34 import java.security.PrivilegedExceptionAction;
       
    35 import java.util.ArrayList;
       
    36 import java.util.Collections;
       
    37 import java.util.HashMap;
       
    38 import java.util.List;
       
    39 import java.util.Map;
       
    40 import java.util.Set;
       
    41 import java.util.WeakHashMap;
       
    42 import javax.management.InstanceNotFoundException;
       
    43 import javax.management.ListenerNotFoundException;
       
    44 import javax.management.MBeanServer;
       
    45 import javax.management.MBeanServerConnection;
       
    46 import javax.management.MBeanServerDelegate;
       
    47 import javax.management.MBeanServerNotification;
       
    48 import javax.management.Notification;
       
    49 import javax.management.NotificationBroadcaster;
       
    50 import javax.management.NotificationFilter;
       
    51 import javax.management.NotificationListener;
       
    52 import javax.management.ObjectName;
       
    53 import javax.management.Query;
       
    54 import javax.management.QueryEval;
       
    55 import javax.management.QueryExp;
       
    56 
       
    57 /**
       
    58  * <p>An object that can be used to subscribe for notifications from all MBeans
       
    59  * in an MBeanServer that match a pattern.  For example, to listen for
       
    60  * notifications from all MBeans in the MBeanServer {@code mbs} that match
       
    61  * {@code com.example:type=Controller,name=*} you could write:</p>
       
    62  *
       
    63  * <pre>
       
    64  * EventSubscriber subscriber = EventSubscriber.getEventSubscriber(mbs);
       
    65  * ObjectName pattern = new ObjectName("com.example:type=Controller,name=*");
       
    66  * NotificationListener myListener = ...;
       
    67  * NotificationFilter myFilter = null;  // or whatever
       
    68  * Object handback = null;              // or whatever
       
    69  * subscriber.subscribe(pattern, myListener, myFilter, myHandback);
       
    70  * </pre>
       
    71  */
       
    72 public class EventSubscriber implements EventConsumer {
       
    73     /**
       
    74      * Returns an {@code EventSubscriber} object to subscribe for notifications
       
    75      * from the given {@code MBeanServer}.  Calling this method more
       
    76      * than once with the same parameter may or may not return the same object.
       
    77      *
       
    78      * @param mbs the {@code MBeanServer} containing MBeans to be subscribed to.
       
    79      * @return An {@code EventSubscriber} object.
       
    80      *
       
    81      * @throws NullPointerException if mbs is null.
       
    82      */
       
    83     public static EventSubscriber getEventSubscriber(MBeanServer mbs) {
       
    84         if (mbs == null)
       
    85             throw new NullPointerException("Null MBeanServer");
       
    86 
       
    87         EventSubscriber eventSubscriber = null;
       
    88         synchronized (subscriberMap) {
       
    89             final WeakReference<EventSubscriber> wrf = subscriberMap.get(mbs);
       
    90             eventSubscriber = (wrf == null) ? null : wrf.get();
       
    91 
       
    92             if (eventSubscriber == null) {
       
    93                 eventSubscriber = new EventSubscriber(mbs);
       
    94 
       
    95                 subscriberMap.put(mbs,
       
    96                         new WeakReference<EventSubscriber>(eventSubscriber));
       
    97             }
       
    98         }
       
    99 
       
   100         return eventSubscriber;
       
   101     }
       
   102 
       
   103     private EventSubscriber(final MBeanServer mbs) {
       
   104         logger.trace("EventSubscriber", "create a new one");
       
   105         this.mbeanServer = mbs;
       
   106 
       
   107         Exception x = null;
       
   108         try {
       
   109             AccessController.doPrivileged(
       
   110                     new PrivilegedExceptionAction<Void>() {
       
   111                 public Void run() throws Exception {
       
   112                     mbs.addNotificationListener(
       
   113                             MBeanServerDelegate.DELEGATE_NAME,
       
   114                             myMBeanServerListener, null, null);
       
   115                     return null;
       
   116                 }
       
   117             });
       
   118         } catch (PrivilegedActionException ex) {
       
   119             x = ex.getException();
       
   120         }
       
   121 
       
   122         // handle possible exceptions.
       
   123         //
       
   124         // Fail unless x is null or x is instance of InstanceNotFoundException
       
   125         // The logic here is that if the MBeanServerDelegate is not present,
       
   126         // we will assume that the connection will not emit any
       
   127         // MBeanServerNotifications.
       
   128         //
       
   129         if (x != null && !(x instanceof InstanceNotFoundException)) {
       
   130             if (x instanceof RuntimeException)
       
   131                 throw (RuntimeException) x;
       
   132             throw new RuntimeException(
       
   133                     "Can't add listener to MBean server delegate: " + x, x);
       
   134         }
       
   135     }
       
   136 
       
   137     public void subscribe(ObjectName name,
       
   138             NotificationListener listener,
       
   139             NotificationFilter filter,
       
   140             Object handback)
       
   141             throws IOException {
       
   142 
       
   143         if (logger.traceOn())
       
   144             logger.trace("subscribe", "" + name);
       
   145 
       
   146         if (name == null)
       
   147             throw new IllegalArgumentException("Null MBean name");
       
   148 
       
   149         if (listener == null)
       
   150             throw new IllegalArgumentException("Null listener");
       
   151 
       
   152         final MyListenerInfo li = new MyListenerInfo(listener, filter, handback);
       
   153         List<MyListenerInfo> list;
       
   154 
       
   155         Map<ObjectName, List<MyListenerInfo>> map;
       
   156         Set<ObjectName> names;
       
   157         if (name.isPattern()) {
       
   158             map = patternSubscriptionMap;
       
   159             names = mbeanServer.queryNames(name, notificationBroadcasterExp);
       
   160         } else {
       
   161             map = exactSubscriptionMap;
       
   162             names = Collections.singleton(name);
       
   163         }
       
   164 
       
   165         synchronized (map) {
       
   166             list = map.get(name);
       
   167             if (list == null) {
       
   168                 list = new ArrayList<MyListenerInfo>();
       
   169                 map.put(name, list);
       
   170             }
       
   171             list.add(li);
       
   172         }
       
   173 
       
   174         for (ObjectName mbeanName : names) {
       
   175             try {
       
   176                 mbeanServer.addNotificationListener(mbeanName,
       
   177                                                     listener,
       
   178                                                     filter,
       
   179                                                     handback);
       
   180             } catch (Exception e) {
       
   181                 logger.fine("subscribe", "addNotificationListener", e);
       
   182             }
       
   183         }
       
   184     }
       
   185 
       
   186     public void unsubscribe(ObjectName name,
       
   187             NotificationListener listener)
       
   188             throws ListenerNotFoundException, IOException {
       
   189         if (logger.traceOn())
       
   190             logger.trace("unsubscribe", "" + name);
       
   191 
       
   192         if (name == null)
       
   193             throw new IllegalArgumentException("Null MBean name");
       
   194 
       
   195         if (listener == null)
       
   196             throw new ListenerNotFoundException();
       
   197 
       
   198         Map<ObjectName, List<MyListenerInfo>> map;
       
   199         Set<ObjectName> names;
       
   200 
       
   201         if (name.isPattern()) {
       
   202             map = patternSubscriptionMap;
       
   203             names = mbeanServer.queryNames(name, notificationBroadcasterExp);
       
   204         } else {
       
   205             map = exactSubscriptionMap;
       
   206             names = Collections.singleton(name);
       
   207         }
       
   208 
       
   209         List<MyListenerInfo> toRemove = new ArrayList<MyListenerInfo>();
       
   210         synchronized (map) {
       
   211             List<MyListenerInfo> list = map.get(name);
       
   212             if (list == null) {
       
   213                 throw new ListenerNotFoundException();
       
   214             }
       
   215 
       
   216             for (MyListenerInfo info : list) {
       
   217                 if (info.listener == listener) {
       
   218                     toRemove.add(info);
       
   219                 }
       
   220             }
       
   221 
       
   222             if (toRemove.isEmpty()) {
       
   223                 throw new ListenerNotFoundException();
       
   224             }
       
   225 
       
   226             for (MyListenerInfo info : toRemove) {
       
   227                 list.remove(info);
       
   228             }
       
   229 
       
   230             if (list.isEmpty())
       
   231                 map.remove(name);
       
   232         }
       
   233 
       
   234         for (ObjectName mbeanName : names) {
       
   235             for (MyListenerInfo i : toRemove) {
       
   236                 try {
       
   237                     mbeanServer.removeNotificationListener(mbeanName,
       
   238                         i.listener, i.filter, i.handback);
       
   239                 } catch (Exception e) {
       
   240                     logger.fine("unsubscribe", "removeNotificationListener", e);
       
   241                 }
       
   242             }
       
   243         }
       
   244     }
       
   245 
       
   246     // ---------------------------------
       
   247     // private stuff
       
   248     // ---------------------------------
       
   249     // used to receive MBeanServerNotification
       
   250     private NotificationListener myMBeanServerListener =
       
   251             new NotificationListener() {
       
   252         public void handleNotification(Notification n, Object hb) {
       
   253             if (!(n instanceof MBeanServerNotification) ||
       
   254                     !MBeanServerNotification.
       
   255                     REGISTRATION_NOTIFICATION.equals(n.getType())) {
       
   256                 return;
       
   257             }
       
   258 
       
   259             final ObjectName name =
       
   260                     ((MBeanServerNotification)n).getMBeanName();
       
   261             try {
       
   262                 if (!mbeanServer.isInstanceOf(name,
       
   263                         NotificationBroadcaster.class.getName())) {
       
   264                     return;
       
   265                 }
       
   266             } catch (Exception e) {
       
   267                 // The only documented exception is InstanceNotFoundException,
       
   268                 // which could conceivably happen if the MBean is unregistered
       
   269                 // immediately after being registered.
       
   270                 logger.fine("myMBeanServerListener.handleNotification",
       
   271                         "isInstanceOf", e);
       
   272                 return;
       
   273             }
       
   274 
       
   275             final List<MyListenerInfo> listeners = new ArrayList<MyListenerInfo>();
       
   276 
       
   277             // If there are subscribers for the exact name that has just arrived
       
   278             // then add their listeners to the list.
       
   279             synchronized (exactSubscriptionMap) {
       
   280                 List<MyListenerInfo> exactListeners = exactSubscriptionMap.get(name);
       
   281                 if (exactListeners != null)
       
   282                     listeners.addAll(exactListeners);
       
   283             }
       
   284 
       
   285             // For every subscription pattern that matches the new name,
       
   286             // add all the listeners for that pattern to "listeners".
       
   287             synchronized (patternSubscriptionMap) {
       
   288                 for (ObjectName on : patternSubscriptionMap.keySet()) {
       
   289                     if (on.apply(name)) {
       
   290                         listeners.addAll(patternSubscriptionMap.get(on));
       
   291                     }
       
   292                 }
       
   293             }
       
   294 
       
   295             // Add all the listeners just found to the new MBean.
       
   296             for (MyListenerInfo li : listeners) {
       
   297                 try {
       
   298                     mbeanServer.addNotificationListener(
       
   299                             name,
       
   300                             li.listener,
       
   301                             li.filter,
       
   302                             li.handback);
       
   303                 } catch (Exception e) {
       
   304                     logger.fine("myMBeanServerListener.handleNotification",
       
   305                             "addNotificationListener", e);
       
   306                 }
       
   307             }
       
   308         }
       
   309     };
       
   310 
       
   311     private static class MyListenerInfo {
       
   312         public final NotificationListener listener;
       
   313         public final NotificationFilter filter;
       
   314         public final Object handback;
       
   315 
       
   316         public MyListenerInfo(NotificationListener listener,
       
   317                 NotificationFilter filter,
       
   318                 Object handback) {
       
   319 
       
   320             if (listener == null)
       
   321                 throw new IllegalArgumentException("Null listener");
       
   322 
       
   323             this.listener = listener;
       
   324             this.filter = filter;
       
   325             this.handback = handback;
       
   326         }
       
   327     }
       
   328 
       
   329     // ---------------------------------
       
   330     // private methods
       
   331     // ---------------------------------
       
   332     // ---------------------------------
       
   333     // private variables
       
   334     // ---------------------------------
       
   335     private final MBeanServer mbeanServer;
       
   336 
       
   337     private final Map<ObjectName, List<MyListenerInfo>> exactSubscriptionMap =
       
   338             new HashMap<ObjectName, List<MyListenerInfo>>();
       
   339     private final Map<ObjectName, List<MyListenerInfo>> patternSubscriptionMap =
       
   340             new HashMap<ObjectName, List<MyListenerInfo>>();
       
   341 
       
   342 
       
   343 
       
   344     // trace issues
       
   345     private static final ClassLogger logger =
       
   346             new ClassLogger("javax.management.event", "EventSubscriber");
       
   347 
       
   348     // Compatibility code, so we can run on Tiger:
       
   349     private static final QueryExp notificationBroadcasterExp;
       
   350     static {
       
   351         QueryExp broadcasterExp;
       
   352         try {
       
   353             final Method m = Query.class.getMethod("isInstanceOf", String.class);
       
   354             broadcasterExp = (QueryExp)m.invoke(Query.class,
       
   355                     new Object[] {NotificationBroadcaster.class.getName()});
       
   356         } catch (Exception e) {
       
   357             broadcasterExp = new BroadcasterQueryExp();
       
   358         }
       
   359         notificationBroadcasterExp = broadcasterExp;
       
   360     }
       
   361     private static class BroadcasterQueryExp extends QueryEval implements QueryExp {
       
   362         private static final long serialVersionUID = 1234L;
       
   363         public boolean apply(ObjectName name) {
       
   364             try {
       
   365                 return getMBeanServer().isInstanceOf(
       
   366                         name, NotificationBroadcaster.class.getName());
       
   367             } catch (Exception e) {
       
   368                 return false;
       
   369             }
       
   370         }
       
   371     }
       
   372 
       
   373     private static final
       
   374             Map<MBeanServerConnection, WeakReference<EventSubscriber>> subscriberMap =
       
   375             new WeakHashMap<MBeanServerConnection, WeakReference<EventSubscriber>>();
       
   376 }