1/*
2 * Copyright (c) 2009, 2013, 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
27package com.sun.org.glassfish.external.amx;
28
29import java.util.Set;
30import javax.management.MBeanServer;
31import javax.management.MBeanServerConnection;
32import javax.management.MBeanServerNotification;
33import javax.management.Notification;
34import javax.management.NotificationListener;
35import javax.management.ObjectName;
36import java.util.concurrent.CountDownLatch;
37
38import 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)
46public 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}
331