1 /*
   2  * Copyright (c) 2009, 2010, Oracle and/or its affiliates. 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 
  27 package com.sun.org.glassfish.external.amx;
  28 
  29 import java.util.Set;
  30 import javax.management.MBeanServer;
  31 import javax.management.MBeanServerConnection;
  32 import javax.management.MBeanServerNotification;
  33 import javax.management.Notification;
  34 import javax.management.NotificationListener;
  35 import javax.management.ObjectName;
  36 import java.util.concurrent.CountDownLatch;
  37 
  38 import static com.sun.org.glassfish.external.amx.AMX.*;
  39 
  40 /**
  41  * Listens for registration of MBeans of various types.
  42  * Intended usage is for subsystems to lazy-load only when the Parent
  43  * MBean is registered.
  44  */
  45 @com.sun.org.glassfish.external.arc.Taxonomy(stability = com.sun.org.glassfish.external.arc.Stability.UNCOMMITTED)
  46 public class MBeanListener<T extends MBeanListener.Callback> implements NotificationListener
  47 {
  48     private static void debug(final Object o) { System.out.println( "" + o ); }
  49 
  50     /** listen for MBeans in a given domain of a given type[name]
  51         OR an ObjectName (below) */
  52     private final String mJMXDomain;
  53     private final String mType;
  54     private final String mName;
  55 
  56     /** mType and mName should be null if mObjectName is non-null, and vice versa */
  57     private final ObjectName mObjectName;
  58 
  59     private final MBeanServerConnection mMBeanServer;
  60 
  61     private final T mCallback;
  62 
  63     public String toString()
  64     {
  65         return "MBeanListener: ObjectName=" + mObjectName + ", type=" + mType + ", name=" + mName;
  66     }
  67 
  68     public String getType()
  69     {
  70         return mType;
  71     }
  72 
  73     public String getName()
  74     {
  75         return mName;
  76     }
  77 
  78     public MBeanServerConnection getMBeanServer()
  79     {
  80         return mMBeanServer;
  81     }
  82 
  83     /** Callback interface.  */
  84     public interface Callback
  85     {
  86         public void mbeanRegistered(final ObjectName objectName, final MBeanListener listener);
  87         public void mbeanUnregistered(final ObjectName objectName, final MBeanListener listener);
  88     }
  89 
  90     /**
  91         Default callback implementation, can be subclassed if needed
  92         Remembers only the last MBean that was seen.
  93      */
  94     public static class CallbackImpl implements MBeanListener.Callback
  95     {
  96         private volatile ObjectName mRegistered = null;
  97         private volatile ObjectName mUnregistered = null;
  98         private final boolean mStopAtFirst;
  99 
 100         public CallbackImpl() {
 101             this(true);
 102         }
 103 
 104         public CallbackImpl(final boolean stopAtFirst)
 105         {
 106             mStopAtFirst = stopAtFirst;
 107         }
 108 
 109         public ObjectName getRegistered()   { return mRegistered; }
 110         public ObjectName getUnregistered() { return mUnregistered; }
 111 
 112         protected final CountDownLatch mLatch = new CountDownLatch(1);
 113 
 114         /** Optional: wait for the CountDownLatch to fire
 115             If used, the subclass should countDown() the latch when the
 116             appropriate event happens
 117         */
 118         public void await()
 119         {
 120             try
 121             {
 122                 mLatch.await(); // wait until BootAMXMBean is ready
 123             }
 124             catch (InterruptedException e)
 125             {
 126                 throw new RuntimeException(e);
 127             }
 128         }
 129 
 130         public void mbeanRegistered(final ObjectName objectName, final MBeanListener listener)
 131         {
 132             mRegistered = objectName;
 133             if ( mStopAtFirst )
 134             {
 135                 listener.stopListening();
 136             }
 137         }
 138         public void mbeanUnregistered(final ObjectName objectName, final MBeanListener listener)
 139         {
 140             mUnregistered = objectName;
 141             if ( mStopAtFirst )
 142             {
 143                 listener.stopListening();
 144             }
 145         }
 146     }
 147 
 148     public T getCallback()
 149     {
 150         return mCallback;
 151     }
 152 
 153     /**
 154      * Listener for a specific MBean.
 155      * Caller must call {@link #start} to start listening.
 156      * @param server
 157      * @param objectName
 158      * @param callback
 159      */
 160     public MBeanListener(
 161             final MBeanServerConnection server,
 162             final ObjectName objectName,
 163             final T callback)
 164     {
 165         mMBeanServer = server;
 166         mObjectName = objectName;
 167         mJMXDomain = null;
 168         mType = null;
 169         mName = null;
 170         mCallback = callback;
 171     }
 172 
 173     /**
 174      * Listener for all MBeans of specified type, with or without a name.
 175      * Caller must call {@link #start} to start listening.
 176      * @param server
 177      * @param type type of the MBean (as found in the ObjectName)
 178      * @param callback
 179      */
 180     public MBeanListener(
 181             final MBeanServerConnection server,
 182             final String domain,
 183             final String type,
 184             final T callback)
 185     {
 186         this(server, domain, type, null, callback);
 187     }
 188 
 189     /**
 190      * Listener for MBeans of specified type, with specified name (or any name
 191      * if null is passed for the name).
 192      * Caller must call {@link #start} to start listening.
 193      * @param server
 194      * @param type type of the MBean (as found in the ObjectName)
 195      * @param name name of the MBean, or null if none
 196      * @param callback
 197      */
 198     public MBeanListener(
 199             final MBeanServerConnection server,
 200             final String domain,
 201             final String type,
 202             final String name,
 203             final T callback)
 204     {
 205         mMBeanServer = server;
 206         mJMXDomain = domain;
 207         mType = type;
 208         mName = name;
 209         mObjectName = null;
 210         mCallback = callback;
 211     }
 212 
 213 
 214     private boolean isRegistered( final MBeanServerConnection conn, final ObjectName objectName )
 215     {
 216         try
 217         {
 218             return conn.isRegistered(objectName);
 219         }
 220         catch (final Exception e)
 221         {
 222             throw new RuntimeException(e);
 223         }
 224     }
 225 
 226     /**
 227     Start listening.  If the required MBean(s) are already present, the callback
 228     will be synchronously made before returning.  It is also possible that the
 229     callback could happen twice for the same MBean.
 230      */
 231     public void startListening()
 232     {
 233         // race condition: must listen *before* looking for existing MBeans
 234         try
 235         {
 236             mMBeanServer.addNotificationListener( AMXUtil.getMBeanServerDelegateObjectName(), this, null, this);
 237         }
 238         catch (final Exception e)
 239         {
 240             throw new RuntimeException("Can't add NotificationListener", e);
 241         }
 242 
 243         if ( mObjectName != null )
 244         {
 245             if ( isRegistered(mMBeanServer, mObjectName) )
 246             {
 247                 mCallback.mbeanRegistered(mObjectName, this);
 248             }
 249         }
 250         else
 251         {
 252             // query for AMX MBeans of the requisite type
 253             String props = TYPE_KEY + "=" + mType;
 254             if (mName != null)
 255             {
 256                 props = props + "," + NAME_KEY + mName;
 257             }
 258 
 259             final ObjectName pattern = AMXUtil.newObjectName(mJMXDomain + ":" +props);
 260             try
 261             {
 262                 final Set<ObjectName> matched = mMBeanServer.queryNames(pattern, null);
 263                 for (final ObjectName objectName : matched)
 264                 {
 265                     mCallback.mbeanRegistered(objectName, this);
 266                 }
 267             }
 268             catch( final Exception e )
 269             {
 270                 throw new RuntimeException(e);
 271             }
 272         }
 273     }
 274 
 275 
 276     /** unregister the listener */
 277     public void stopListening()
 278     {
 279         try
 280         {
 281             mMBeanServer.removeNotificationListener( AMXUtil.getMBeanServerDelegateObjectName(), this);
 282         }
 283         catch (final Exception e)
 284         {
 285             throw new RuntimeException("Can't remove NotificationListener " + this, e);
 286         }
 287     }
 288 
 289     public void handleNotification(
 290             final Notification notifIn,
 291             final Object handback)
 292     {
 293         if (notifIn instanceof MBeanServerNotification)
 294         {
 295             final MBeanServerNotification notif = (MBeanServerNotification) notifIn;
 296             final ObjectName objectName = notif.getMBeanName();
 297 
 298             boolean match = false;
 299             if ( mObjectName != null && mObjectName.equals(objectName) )
 300             {
 301                 match = true;
 302             }
 303             else if ( objectName.getDomain().equals( mJMXDomain ) )
 304             {
 305                 if ( mType != null && mType.equals(objectName.getKeyProperty(TYPE_KEY)) )
 306                 {
 307                     final String mbeanName = objectName.getKeyProperty(NAME_KEY);
 308                     if (mName != null && mName.equals(mbeanName))
 309                     {
 310                         match = true;
 311                     }
 312                 }
 313             }
 314 
 315             if ( match )
 316             {
 317                 final String notifType = notif.getType();
 318                 if (MBeanServerNotification.REGISTRATION_NOTIFICATION.equals(notifType))
 319                 {
 320                     mCallback.mbeanRegistered(objectName, this);
 321                 }
 322                 else if (MBeanServerNotification.UNREGISTRATION_NOTIFICATION.equals(notifType))
 323                 {
 324                     mCallback.mbeanUnregistered(objectName, this);
 325                 }
 326             }
 327         }
 328     }
 329 
 330 }