/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 1997-2012 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. */ package org.glassfish.admin.amx.util.jmx; import java.io.IOException; import java.util.Collections; import java.util.HashSet; import java.util.Set; import javax.management.InstanceNotFoundException; import javax.management.JMException; import javax.management.MBeanServer; import javax.management.MBeanServerConnection; import javax.management.MBeanServerNotification; import javax.management.Notification; import javax.management.NotificationFilter; import javax.management.NotificationListener; import javax.management.ObjectName; import org.glassfish.admin.amx.util.SetUtil; /** Convenience base class for listening for Notifications from one or more MBeans, which may be specified as a specific MBean ObjectName, or an ObjectName pattern. If the ObjectName is a pattern, the list of listenees is dynamically maintained. <p> Caller should call {@link #cleanup} when done, because a listener is maintained on the MBeanServer delegate. */ public abstract class NotificationListenerBase implements NotificationListener { private final MBeanServerConnection mConn; /** actual MBean ObjectNames, not patterns */ private final Set<ObjectName> mListenees; /** targets as specified by caller, may be a pattern or fixed ObjectName */ private final ObjectName mPattern; private final NotificationFilter mFilter; private RegistrationListener mDelegateListener; private volatile boolean mSetupListening; /** Calls this( conn, listenTo, null, null ). <p><b>Instantiating code must call setupListening() in order to initiate listening</b> */ protected NotificationListenerBase( final String name, final MBeanServerConnection conn, final ObjectName pattern) throws IOException { this(name, conn, pattern, null); } /** Listen to all MBean(s) which match the pattern 'listenTo'. <p><b>Instantiating code must call setupListening() in order to initiate listening</b> @param name arbitrary name of this listener @param conn the MBeanServerConnection or MBeanServer @param pattern an MBean ObjectName, or an ObjectName pattern @param filter optional NotificationFilter */ protected NotificationListenerBase( final String name, final MBeanServerConnection conn, final ObjectName pattern, final NotificationFilter filter) throws IOException { mConn = conn; mPattern = pattern; mFilter = filter; mDelegateListener = null; mSetupListening = false; mListenees = Collections.synchronizedSet(new HashSet<ObjectName>()); // test connection for validity if (!conn.isRegistered(JMXUtil.getMBeanServerDelegateObjectName())) { throw new IllegalArgumentException(); } } /** Subclass should implement this routine. */ @Override public abstract void handleNotification(final Notification notif, final Object handback); protected synchronized void listenToMBean(final ObjectName objectName) throws InstanceNotFoundException, IOException { if (!mListenees.contains(objectName)) { mListenees.add(objectName); getMBeanServerConnection().addNotificationListener( objectName, this, mFilter, null); } } public synchronized void startListening() throws InstanceNotFoundException, IOException { if (mSetupListening) { throw new IllegalStateException("setupListening() must be called exactly once"); } if (mPattern.isPattern()) { // it's crucial we listen for registration/unregistration events // so that any patterns are maintained. // do this BEFORE the code below, of we could // miss a registration. mDelegateListener = new RegistrationListener(); JMXUtil.listenToMBeanServerDelegate(mConn, mDelegateListener, null, null); } Set<ObjectName> s; if (mPattern.isPattern()) { s = JMXUtil.queryNames(getConn(), mPattern, null); } else { s = SetUtil.newSet(mPattern); } for (final ObjectName objectName : s) { listenToMBean(objectName); } mSetupListening = true; } /** Get the filter originally specified when constructing this object. */ public final NotificationFilter getNotificationFilter(final ObjectName objectName) { return mFilter; } protected synchronized void listenToIfMatch(final ObjectName objectName) throws IOException, InstanceNotFoundException { if (!mListenees.contains(objectName)) { final String defaultDomain = getConn().getDefaultDomain(); if (JMXUtil.matchesPattern(defaultDomain, mPattern, objectName)) { listenToMBean(objectName); } } } /** tracks coming and going of MBeans being listened to which match our patterns. */ private final class RegistrationListener implements NotificationListener { public RegistrationListener() { } @Override public void handleNotification( final Notification notifIn, final Object handback) { if (notifIn instanceof MBeanServerNotification) { final MBeanServerNotification notif = (MBeanServerNotification) notifIn; final ObjectName objectName = notif.getMBeanName(); final String type = notif.getType(); try { if (type.equals(MBeanServerNotification.REGISTRATION_NOTIFICATION)) { listenToIfMatch(objectName); } else if (type.equals(MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) { mListenees.remove(objectName); } } catch (Exception e) { // nothing can be done... } } } } /** Reset everything so that no listening is occuring and all lists are empty. */ public synchronized void cleanup() { try { if (mDelegateListener != null) { // it's crucial we listen for registration/unregistration events // so that any patterns are maintained. getConn().removeNotificationListener( JMXUtil.getMBeanServerDelegateObjectName(), mDelegateListener, null, null); mDelegateListener = null; } for (final ObjectName objectName : mListenees) { getConn().removeNotificationListener( objectName, this, mFilter, null); } } catch (JMException e) { } catch (IOException e) { } mListenees.clear(); } /** @return a copy of the MBean currently being listened to. */ public synchronized Set<ObjectName> getListenees() { final Set<ObjectName> objectNames = new HashSet<ObjectName>(); synchronized (mListenees) { objectNames.addAll(mListenees); } return (objectNames); } /** @return the MBeanServerConnection in use. @throws an Exception if no longer alive ( isAlive() returns false). */ public final MBeanServerConnection getMBeanServerConnection() { return getConn(); } protected final MBeanServerConnection getConn() { return mConn; } protected final void checkAlive() throws IOException { if (!isAlive()) { throw new IOException("MBeanServerConnection failed"); } } /** @return true if still listening and the connection is still alive */ public boolean isAlive() { boolean isAlive = true; if (!(mConn instanceof MBeanServer)) { // remote, check if it is alive try { mConn.isRegistered(JMXUtil.getMBeanServerDelegateObjectName()); } catch (Exception e) { isAlive = false; } } return isAlive; } }