changeset 147:2965459a8ee7

6610917: Define a generic NotificationFilter Summary: Adds javax.management.QueryNotificationFilter Reviewed-by: dfuchs
author emcmanus
date Tue, 01 Apr 2008 14:45:23 +0200
parents 52c76fc0a3a9
children f4205a7bdfd4
files src/share/classes/com/sun/jmx/interceptor/DefaultMBeanServerInterceptor.java src/share/classes/com/sun/jmx/mbeanserver/MBeanAnalyzer.java src/share/classes/com/sun/jmx/mbeanserver/MBeanIntrospector.java src/share/classes/com/sun/jmx/mbeanserver/NotificationMBeanSupport.java src/share/classes/com/sun/jmx/mbeanserver/OpenConverter.java src/share/classes/com/sun/jmx/mbeanserver/Repository.java src/share/classes/com/sun/jmx/mbeanserver/Util.java src/share/classes/javax/management/ObjectName.java src/share/classes/javax/management/QueryNotificationFilter.java test/javax/management/query/QueryNotifFilterTest.java
diffstat 10 files changed, 885 insertions(+), 53 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/com/sun/jmx/interceptor/DefaultMBeanServerInterceptor.java	Thu Mar 27 14:15:59 2008 -0700
+++ b/src/share/classes/com/sun/jmx/interceptor/DefaultMBeanServerInterceptor.java	Tue Apr 01 14:45:23 2008 +0200
@@ -34,8 +34,6 @@
 import java.util.HashSet;
 import java.util.WeakHashMap;
 import java.lang.ref.WeakReference;
-import java.io.PrintWriter;
-import java.io.StringWriter;
 import java.security.AccessControlContext;
 import java.security.Permission;
 import java.security.ProtectionDomain;
@@ -51,7 +49,6 @@
 import javax.management.InstanceNotFoundException;
 import javax.management.IntrospectionException;
 import javax.management.InvalidAttributeValueException;
-import javax.management.JMException;
 import javax.management.JMRuntimeException;
 import javax.management.ListenerNotFoundException;
 import javax.management.MalformedObjectNameException;
@@ -84,11 +81,10 @@
 import com.sun.jmx.mbeanserver.DynamicMBean2;
 import com.sun.jmx.mbeanserver.ModifiableClassLoaderRepository;
 import com.sun.jmx.mbeanserver.MBeanInstantiator;
-import com.sun.jmx.mbeanserver.MXBeanSupport;
 import com.sun.jmx.mbeanserver.Repository;
 import com.sun.jmx.mbeanserver.NamedObject;
-import com.sun.jmx.defaults.ServiceName;
 import com.sun.jmx.mbeanserver.Introspector;
+import com.sun.jmx.mbeanserver.Util;
 import com.sun.jmx.remote.util.EnvHelp;
 
 /**
@@ -623,18 +619,9 @@
             List<String> result = new ArrayList<String>(domains.length);
             for (int i = 0; i < domains.length; i++) {
                 try {
-                    ObjectName domain = new ObjectName(domains[i] + ":x=x");
+                    ObjectName domain = Util.newObjectName(domains[i] + ":x=x");
                     checkMBeanPermission((String) null, null, domain, "getDomains");
                     result.add(domains[i]);
-                } catch (MalformedObjectNameException e) {
-                    // Should never occur... But let's log it just in case.
-                    if (MBEANSERVER_LOGGER.isLoggable(Level.SEVERE)) {
-                        MBEANSERVER_LOGGER.logp(Level.SEVERE,
-                                DefaultMBeanServerInterceptor.class.getName(),
-                                "getDomains",
-                                "Failed to check permission for domain = " +
-                                domains[i], e);
-                    }
                 } catch (SecurityException e) {
                     // OK: Do not add this domain to the list
                 }
--- a/src/share/classes/com/sun/jmx/mbeanserver/MBeanAnalyzer.java	Thu Mar 27 14:15:59 2008 -0700
+++ b/src/share/classes/com/sun/jmx/mbeanserver/MBeanAnalyzer.java	Tue Apr 01 14:45:23 2008 +0200
@@ -107,10 +107,7 @@
     private MBeanAnalyzer(Class<?> mbeanInterface,
             MBeanIntrospector<M> introspector)
             throws NotCompliantMBeanException {
-        if (!mbeanInterface.isInterface()) {
-            throw new NotCompliantMBeanException("Not an interface: " +
-                    mbeanInterface.getName());
-        }
+        introspector.checkCompliance(mbeanInterface);
 
         try {
             initMaps(mbeanInterface, introspector);
@@ -121,11 +118,10 @@
 
     // Introspect the mbeanInterface and initialize this object's maps.
     //
-    private void initMaps(Class<?> mbeanInterface,
+    private void initMaps(Class<?> mbeanType,
             MBeanIntrospector<M> introspector) throws Exception {
-        final Method[] methodArray = mbeanInterface.getMethods();
-
-        final List<Method> methods = eliminateCovariantMethods(methodArray);
+        final List<Method> methods1 = introspector.getMethods(mbeanType);
+        final List<Method> methods = eliminateCovariantMethods(methods1);
 
         /* Run through the methods to detect inconsistencies and to enable
            us to give getter and setter together to visitAttribute. */
@@ -234,13 +230,13 @@
        but existing code may depend on it and users may be used to seeing
        operations or attributes appear in a particular order.  */
     static List<Method>
-            eliminateCovariantMethods(Method[] methodArray) {
+            eliminateCovariantMethods(List<Method> startMethods) {
         // We are assuming that you never have very many methods with the
         // same name, so it is OK to use algorithms that are quadratic
         // in the number of methods with the same name.
 
-        final int len = methodArray.length;
-        final Method[] sorted = methodArray.clone();
+        final int len = startMethods.size();
+        final Method[] sorted = startMethods.toArray(new Method[len]);
         Arrays.sort(sorted,MethodOrder.instance);
         final Set<Method> overridden = newSet();
         for (int i=1;i<len;i++) {
@@ -259,7 +255,7 @@
             }
         }
 
-        final List<Method> methods = newList(Arrays.asList(methodArray));
+        final List<Method> methods = newList(startMethods);
         methods.removeAll(overridden);
         return methods;
     }
--- a/src/share/classes/com/sun/jmx/mbeanserver/MBeanIntrospector.java	Thu Mar 27 14:15:59 2008 -0700
+++ b/src/share/classes/com/sun/jmx/mbeanserver/MBeanIntrospector.java	Tue Apr 01 14:45:23 2008 +0200
@@ -34,6 +34,7 @@
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Type;
+import java.util.Arrays;
 import java.util.List;
 import java.util.WeakHashMap;
 
@@ -169,6 +170,19 @@
      */
     abstract Descriptor getMBeanDescriptor(Class<?> resourceClass);
 
+    void checkCompliance(Class<?> mbeanType) throws NotCompliantMBeanException {
+        if (!mbeanType.isInterface()) {
+            throw new NotCompliantMBeanException("Not an interface: " +
+                    mbeanType.getName());
+        }
+    }
+
+    /**
+     * Get the methods to be analyzed to build the MBean interface.
+     */
+    List<Method> getMethods(final Class<?> mbeanType) throws Exception {
+        return Arrays.asList(mbeanType.getMethods());
+    }
 
     final PerInterface<M> getPerInterface(Class<?> mbeanInterface)
     throws NotCompliantMBeanException {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/com/sun/jmx/mbeanserver/NotificationMBeanSupport.java	Tue Apr 01 14:45:23 2008 +0200
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2007 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 com.sun.jmx.mbeanserver;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import javax.management.NotCompliantMBeanException;
+import javax.management.Notification;
+
+/**
+ * <p>A variant of {@code StandardMBeanSupport} where the only
+ * methods included are public getters.  This is used by
+ * {@code QueryNotificationFilter} to pretend that a Notification is
+ * an MBean so it can have a query evaluated on it.  Standard queries
+ * never set attributes or invoke methods but custom queries could and
+ * we don't want to allow that.  Also we don't want to fail if a
+ * Notification happens to have inconsistent types in a pair of getX and
+ * setX methods, and we want to include the Object.getClass() method.
+ */
+public class NotificationMBeanSupport extends StandardMBeanSupport {
+    public <T extends Notification> NotificationMBeanSupport(T n)
+            throws NotCompliantMBeanException {
+        super(n, Util.<Class<T>>cast(n.getClass()));
+    }
+
+    @Override
+    MBeanIntrospector<Method> getMBeanIntrospector() {
+        return introspector;
+    }
+
+    private static class Introspector extends StandardMBeanIntrospector {
+        @Override
+        void checkCompliance(Class<?> mbeanType) {}
+
+        @Override
+        List<Method> getMethods(final Class<?> mbeanType)
+                throws Exception {
+            List<Method> methods = new ArrayList<Method>();
+            for (Method m : mbeanType.getMethods()) {
+                String name = m.getName();
+                Class<?> ret = m.getReturnType();
+                if (m.getParameterTypes().length == 0) {
+                    if ((name.startsWith("is") && name.length() > 2 &&
+                            ret == boolean.class) ||
+                        (name.startsWith("get") && name.length() > 3 &&
+                            ret != void.class)) {
+                        methods.add(m);
+                    }
+                }
+            }
+            return methods;
+        }
+
+    }
+    private static final MBeanIntrospector<Method> introspector =
+            new Introspector();
+}
--- a/src/share/classes/com/sun/jmx/mbeanserver/OpenConverter.java	Thu Mar 27 14:15:59 2008 -0700
+++ b/src/share/classes/com/sun/jmx/mbeanserver/OpenConverter.java	Tue Apr 01 14:45:23 2008 +0200
@@ -438,7 +438,7 @@
                 c.getClassLoader() == null);
 
         final List<Method> methods =
-                MBeanAnalyzer.eliminateCovariantMethods(c.getMethods());
+                MBeanAnalyzer.eliminateCovariantMethods(Arrays.asList(c.getMethods()));
         final SortedMap<String,Method> getterMap = newSortedMap();
 
         /* Select public methods that look like "T getX()" or "boolean
--- a/src/share/classes/com/sun/jmx/mbeanserver/Repository.java	Thu Mar 27 14:15:59 2008 -0700
+++ b/src/share/classes/com/sun/jmx/mbeanserver/Repository.java	Tue Apr 01 14:45:23 2008 +0200
@@ -415,17 +415,8 @@
         boolean to_default_domain = false;
 
         // Set domain to default if domain is empty and not already set
-        if (dom.length() == 0) {
-             try {
-                name = new ObjectName(domain + name.toString());
-            } catch (MalformedObjectNameException e) {
-                if (MBEANSERVER_LOGGER.isLoggable(Level.FINEST)) {
-                    MBEANSERVER_LOGGER.logp(Level.FINEST,
-                            Repository.class.getName(), "addMBean",
-                            "Unexpected MalformedObjectNameException", e);
-                }
-            }
-        }
+        if (dom.length() == 0)
+            name = Util.newObjectName(domain + name.toString());
 
         // Do we have default domain ?
         if (dom == domain) {
--- a/src/share/classes/com/sun/jmx/mbeanserver/Util.java	Thu Mar 27 14:15:59 2008 -0700
+++ b/src/share/classes/com/sun/jmx/mbeanserver/Util.java	Tue Apr 01 14:45:23 2008 +0200
@@ -38,6 +38,8 @@
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
 
 public class Util {
     static <K, V> Map<K, V> newMap() {
@@ -85,6 +87,14 @@
         return new ArrayList<E>(c);
     }
 
+    public static ObjectName newObjectName(String s) {
+        try {
+            return new ObjectName(s);
+        } catch (MalformedObjectNameException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
     /* This method can be used by code that is deliberately violating the
      * allowed checked casts.  Rather than marking the whole method containing
      * the code with @SuppressWarnings, you can use a call to this method for
--- a/src/share/classes/javax/management/ObjectName.java	Thu Mar 27 14:15:59 2008 -0700
+++ b/src/share/classes/javax/management/ObjectName.java	Tue Apr 01 14:45:23 2008 +0200
@@ -26,6 +26,7 @@
 package javax.management;
 
 import com.sun.jmx.mbeanserver.GetPropertyAction;
+import com.sun.jmx.mbeanserver.Util;
 import java.io.IOException;
 import java.io.InvalidObjectException;
 import java.io.ObjectInputStream;
@@ -1386,12 +1387,7 @@
             throws NullPointerException {
         if (name.getClass().equals(ObjectName.class))
             return name;
-        try {
-            return new ObjectName(name.getSerializedNameString());
-        } catch (MalformedObjectNameException e) {
-            throw new IllegalArgumentException("Unexpected: " + e);
-            // can't happen
-        }
+        return Util.newObjectName(name.getSerializedNameString());
     }
 
     /**
@@ -1950,14 +1946,7 @@
      *
      * @since 1.6
      */
-    public static final ObjectName WILDCARD;
-    static {
-        try {
-            WILDCARD = new ObjectName("*:*");
-        } catch (MalformedObjectNameException e) {
-            throw new Error("Can't initialize wildcard name", e);
-        }
-    }
+    public static final ObjectName WILDCARD = Util.newObjectName("*:*");
 
     // Category : Utilities <===================================
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/javax/management/QueryNotificationFilter.java	Tue Apr 01 14:45:23 2008 +0200
@@ -0,0 +1,417 @@
+/*
+ * Copyright 2007 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;
+
+import com.sun.jmx.mbeanserver.NotificationMBeanSupport;
+import com.sun.jmx.mbeanserver.Util;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * <p>General-purpose notification filter.  This filter can be used to
+ * filter notifications from a possibly-remote MBean.  Most filtering
+ * decisions can be coded using this filter, which avoids having to
+ * write a custom implementation of the {@link NotificationFilter}
+ * class.  Writing a custom implementation requires you to deploy it
+ * on both the client and the server in the remote case, so using this class
+ * instead is recommended where possible.</p>
+ *
+ * <!-- <p>Because this class was introduced in version 2.0 of the JMX API,
+ * it may not be present on a remote JMX agent that is running an earlier
+ * version.  The method {@link JMX#addListenerWithFilter JMX.addListenerWithFilter}
+ * can be used when you cannot be sure whether this class is present in the
+ * agent you are connecting to.</p> -->
+ *
+ * <p>This class uses the {@linkplain Query Query API} to specify the
+ * filtering logic.  For example, to select only notifications where the
+ * {@linkplain Notification#getType() type} is {@code "com.example.mytype"},
+ * you could use</p>
+ *
+ * <pre>
+ * NotificationFilter filter =
+ *     new QueryNotificationFilter("Type = 'com.example.mytype'");
+ * </pre>
+ *
+ * <p>or equivalently</p>
+ *
+ * <pre>
+ * NotificationFilter filter =
+ *     new QueryNotificationFilter(
+ *             Query.eq(Query.attr("Type"), Query.value("com.example.mytype")));
+ * </pre>
+ *
+ * <p>(This particular example could also use
+ * {@link NotificationFilterSupport}.)</p>
+ *
+ * <p>Here are some other examples of filters you can specify with this class.</p>
+ *
+ * <dl>
+ *
+ * <dt>{@code QueryNotificationFilter("Type = 'com.example.type1' or
+ * Type = 'com.example.type2'")}
+ * <dd>Notifications where the type is either of the given strings.
+ *
+ * <dt>{@code QueryNotificationFilter("Type in ('com.example.type1',
+ * 'com.example.type2')")}
+ * <dd>Another way to write the previous example.
+ *
+ * <dt>{@code QueryNotificationFilter("SequenceNumber > 1000")}
+ * <dd>Notifications where the {@linkplain Notification#getSequenceNumber()
+ * sequence number} is greater than 1000.
+ *
+ * <dt>{@code QueryNotificationFilter(AttributeChangeNotification.class, null)}
+ * <dd>Notifications where the notification class is
+ * {@link AttributeChangeNotification} or a subclass of it.
+ *
+ * <dt>{@code QueryNotificationFilter(AttributeChangeNotification.class,
+ * "AttributeName = 'Size'")}
+ * <dd>Notifications where the notification class is
+ * {@link AttributeChangeNotification} or a subclass, and where the
+ * {@linkplain AttributeChangeNotification#getAttributeName() name of the
+ * changed attribute} is {@code Size}.
+ *
+ * <dt>{@code QueryNotificationFilter(AttributeChangeNotification.class,
+ * "AttributeName = 'Size' and NewValue - OldValue > 100")}
+ * <dd>As above, but the difference between the
+ * {@linkplain AttributeChangeNotification#getNewValue() new value} and the
+ * {@linkplain AttributeChangeNotification#getOldValue() old value} must be
+ * greater than 100.
+ *
+ * <dt>{@code QueryNotificationFilter("like 'com.example.mydomain:*'")}
+ * <dd>Notifications where the {@linkplain Notification#getSource() source}
+ * is an ObjectName that matches the pattern.
+ *
+ * <dt>{@code QueryNotificationFilter("Source.canonicalName like
+ * 'com.example.mydomain:%'")}
+ * <dd>Another way to write the previous example.
+ *
+ * <dt>{@code QueryNotificationFilter(MBeanServerNotification.class,
+ * "Type = 'JMX.mbean.registered' and MBeanName.canonicalName like
+ * 'com.example.mydomain:%'")}
+ * <dd>Notifications of class {@link MBeanServerNotification} representing
+ * an object registered in the domain {@code com.example.mydomain}.
+ *
+ * </dl>
+ *
+ * <h4>How it works</h4>
+ *
+ * <p>Although the examples above are clear, looking closely at the
+ * Query API reveals a subtlety.  A {@link QueryExp} is evaluated on
+ * an {@link ObjectName}, not a {@code Notification}.</p>
+ *
+ * <p>Every time a {@code Notification} is to be filtered by a
+ * {@code QueryNotificationFilter}, a special {@link MBeanServer} is created.
+ * This {@code MBeanServer} contains exactly one MBean, which represents the
+ * {@code Notification}.  If the {@linkplain Notification#getSource()
+ * source} of the notification is an {@code ObjectName}, which is
+ * recommended practice, then the name of the MBean representing the
+ * {@code Notification} will be this {@code ObjectName}.  Otherwise the
+ * name is unspecified.</p>
+ *
+ * <p>The query specified in the {@code QueryNotificationFilter} constructor
+ * is evaluated against this {@code MBeanServer} and {@code ObjectName},
+ * and the filter returns true if and only if the query does.  If the
+ * query throws an exception, then the filter will return false.</p>
+ *
+ * <p>The MBean representing the {@code Notification} has one attribute for
+ * every property of the {@code Notification}. Specifically, for every public
+ * method {@code T getX()} in the {@code NotificationClass}, the MBean will
+ * have an attribute called {@code X} of type {@code T}. For example, if the
+ * {@code Notification} is an {@code AttributeChangeNotification}, then the
+ * MBean will have an attribute called {@code AttributeName} of type
+ * {@code "java.lang.String"}, corresponding to the method {@link
+ * AttributeChangeNotification#getAttributeName}.</p>
+ *
+ * <p>Query evaluation usually involves calls to the methods of {@code
+ * MBeanServer}.  The methods have the following behavior:</p>
+ *
+ * <ul>
+ * <li>The {@link MBeanServer#getAttribute getAttribute} method returns the
+ * value of the corresponding property.
+ * <li>The {@link MBeanServer#getObjectInstance getObjectInstance}
+ * method returns an {@link ObjectInstance} where the {@link
+ * ObjectInstance#getObjectName ObjectName} is the name of the MBean and the
+ * {@link ObjectInstance#getClassName ClassName} is the class name of the
+ * {@code Notification}.
+ * <li>The {@link MBeanServer#isInstanceOf isInstanceOf} method returns true
+ * if and only if the {@code Notification}'s {@code ClassLoader} can load the
+ * named class, and the {@code Notification} is an {@linkplain Class#isInstance
+ * instance} of that class.
+ * </ul>
+ *
+ * <p>These are the only {@code MBeanServer} methods that are needed to
+ * evaluate standard queries. The behavior of the other {@code MBeanServer}
+ * methods is unspecified.</p>
+ *
+ * @since 1.7
+ */
+public class QueryNotificationFilter implements NotificationFilter {
+    private static final long serialVersionUID = -8408613922660635231L;
+
+    private static final ObjectName DEFAULT_NAME =
+            Util.newObjectName(":type=Notification");
+    private static final QueryExp trueQuery;
+    static {
+        ValueExp zero = Query.value(0);
+        trueQuery = Query.eq(zero, zero);
+    }
+
+    private final QueryExp query;
+
+    /**
+     * Construct a {@code QueryNotificationFilter} that evaluates the given
+     * {@code QueryExp} to determine whether to accept a notification.
+     *
+     * @param query the {@code QueryExp} to evaluate.  Can be null,
+     * in which case all notifications are accepted.
+     */
+    public QueryNotificationFilter(QueryExp query) {
+        if (query == null)
+            this.query = trueQuery;
+        else
+            this.query = query;
+    }
+
+    /**
+     * Construct a {@code QueryNotificationFilter} that evaluates the query
+     * in the given string to determine whether to accept a notification.
+     * The string is converted into a {@code QueryExp} using
+     * {@link Query#fromString Query.fromString}.
+     *
+     * @param query the string specifying the query to evaluate.  Can be null,
+     * in which case all notifications are accepted.
+     *
+      * @throws IllegalArgumentException if the string is not a valid
+      * query string.
+     */
+    public QueryNotificationFilter(String query) {
+        this(Query.fromString(query));
+    }
+
+    /**
+     * <p>Construct a {@code QueryNotificationFilter} that evaluates the query
+     * in the given string to determine whether to accept a notification,
+     * and where the notification must also be an instance of the given class.
+     * The string is converted into a {@code QueryExp} using
+     * {@link Query#fromString Query.fromString}.</p>
+     *
+     * @param notifClass the class that the notification must be an instance of.
+     * Cannot be null.
+     *
+     * @param query the string specifying the query to evaluate.  Can be null,
+     * in which case all notifications are accepted.
+     *
+     * @throws IllegalArgumentException if the string is not a valid
+     * query string, or if {@code notifClass} is null.
+     */
+    public QueryNotificationFilter(
+            Class<? extends Notification> notifClass, String query) {
+        this(Query.and(Query.isInstanceOf(Query.value(notNull(notifClass).getName())),
+                       Query.fromString(query)));
+    }
+
+    private static <T> T notNull(T x) {
+        if (x == null)
+            throw new IllegalArgumentException("Null argument");
+        return x;
+    }
+
+    /**
+     * Retrieve the query that this notification filter will evaluate for
+     * each notification.
+     *
+     * @return the query.
+     */
+    public QueryExp getQuery() {
+        return query;
+    }
+
+    public boolean isNotificationEnabled(Notification notification) {
+        ObjectName name;
+
+        Object source = notification.getSource();
+        if (source instanceof ObjectName)
+            name = (ObjectName) source;
+        else
+            name = DEFAULT_NAME;
+
+        MBS mbsImpl = new MBS(notification, name);
+        MBeanServer mbs = (MBeanServer) Proxy.newProxyInstance(
+                MBeanServer.class.getClassLoader(),
+                new Class<?>[] {MBeanServer.class},
+                new ForwardIH(mbsImpl));
+        return evalQuery(query, mbs, name);
+    }
+
+    private static boolean evalQuery(
+            QueryExp query, MBeanServer mbs, ObjectName name) {
+        MBeanServer oldMBS = QueryEval.getMBeanServer();
+        try {
+            if (mbs != null)
+                query.setMBeanServer(mbs);
+            return query.apply(name);
+        } catch (Exception e) {
+            return false;
+        } finally {
+            query.setMBeanServer(oldMBS);
+        }
+    }
+
+    private static class ForwardIH implements InvocationHandler {
+        private final MBS mbs;
+
+        ForwardIH(MBS mbs) {
+            this.mbs = mbs;
+        }
+
+        public Object invoke(Object proxy, Method method, Object[] args)
+                throws Throwable {
+            Method forward;
+            try {
+                forward = MBS.class.getMethod(
+                        method.getName(), method.getParameterTypes());
+            } catch (NoSuchMethodException e) {
+                throw new UnsupportedOperationException(method.getName());
+            }
+            try {
+                return forward.invoke(mbs, args);
+            } catch (InvocationTargetException e) {
+                throw e.getCause();
+            }
+        }
+    }
+
+    private static class MBS {
+        private final Notification notification;
+        private final ObjectName objectName;
+        private final ObjectInstance objectInstance;
+        private volatile DynamicMBean mbean;
+
+        MBS(Notification n, ObjectName name) {
+            this.notification = n;
+            this.objectName = name;
+            this.objectInstance = new ObjectInstance(name, n.getClass().getName());
+        }
+
+        private void checkName(ObjectName name) throws InstanceNotFoundException {
+            if (!objectName.equals(name))
+                throw new InstanceNotFoundException(String.valueOf(name));
+        }
+
+        private DynamicMBean mbean(ObjectName name)
+                throws InstanceNotFoundException, ReflectionException {
+            if (mbean == null) {
+                try {
+                    mbean = new NotificationMBeanSupport(notification);
+                } catch (NotCompliantMBeanException e) {
+                    throw new ReflectionException(e);
+                }
+            }
+            return mbean;
+        }
+
+        public ObjectInstance getObjectInstance(ObjectName name)
+                throws InstanceNotFoundException {
+            checkName(name);
+            return objectInstance;
+        }
+
+        public Set<ObjectInstance> queryMBeans(ObjectName name, QueryExp query) {
+            Set<ObjectName> names = queryNames(name, query);
+            switch (names.size()) {
+            case 0:
+                return Collections.emptySet();
+            case 1:
+                return Collections.singleton(objectInstance);
+            default:
+                throw new UnsupportedOperationException("Internal error");
+            }
+        }
+
+        public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
+            if ((name != null && !name.apply(objectName)) ||
+                    (query != null && !evalQuery(query, null, name)))
+                return Collections.emptySet();
+            return Collections.singleton(objectName);
+        }
+
+        public boolean isRegistered(ObjectName name) {
+            return objectName.equals(name);
+        }
+
+        public Integer getMBeanCount() {
+            return 1;
+        }
+
+        public Object getAttribute(ObjectName name, String attribute)
+                throws MBeanException, AttributeNotFoundException,
+                       InstanceNotFoundException, ReflectionException {
+            return mbean(name).getAttribute(attribute);
+        }
+
+        public AttributeList getAttributes(ObjectName name, String[] attributes)
+                throws InstanceNotFoundException, ReflectionException {
+            return mbean(name).getAttributes(attributes);
+        }
+
+        public String getDefaultDomain() {
+            return objectName.getDomain();
+        }
+
+        public String[] getDomains() {
+            return new String[] {objectName.getDomain()};
+        }
+
+        public MBeanInfo getMBeanInfo(ObjectName name)
+                throws InstanceNotFoundException, ReflectionException {
+            return mbean(name).getMBeanInfo();
+        }
+
+        public boolean isInstanceOf(ObjectName name, String className)
+                throws InstanceNotFoundException {
+            try {
+                mbean(name);
+                ClassLoader loader = notification.getClass().getClassLoader();
+                Class<?> c = Class.forName(className, false, loader);
+                return c.isInstance(notification);
+            } catch (ReflectionException e) {
+                return false;
+            } catch (ClassNotFoundException e) {
+                return false;
+            }
+        }
+
+        public ClassLoader getClassLoaderFor(ObjectName mbeanName)
+                throws InstanceNotFoundException {
+            checkName(mbeanName);
+            return notification.getClass().getClassLoader();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/management/query/QueryNotifFilterTest.java	Tue Apr 01 14:45:23 2008 +0200
@@ -0,0 +1,347 @@
+/*
+ * Copyright 2007 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test QueryNotifFilterTest
+ * @bug 6610917
+ * @summary Test the QueryNotificationFilter class
+ * @author Eamonn McManus
+ */
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import javax.management.Attribute;
+import javax.management.AttributeChangeNotification;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanInfo;
+import javax.management.MBeanServer;
+import javax.management.MalformedObjectNameException;
+import javax.management.Notification;
+import javax.management.NotificationFilter;
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
+import javax.management.Query;
+import javax.management.QueryEval;
+import javax.management.QueryExp;
+import javax.management.QueryNotificationFilter;
+
+public class QueryNotifFilterTest {
+    private static class Case {
+        final Notification notif;
+        final QueryExp query;
+        final boolean expect;
+        final Class<? extends Notification> notifClass;
+        Case(Notification notif, String query, boolean expect) {
+            this(notif, query, notif.getClass(), expect);
+        }
+        Case(Notification notif, String query,
+                Class<? extends Notification> notifClass, boolean expect) {
+            this(notif, Query.fromString(query), notifClass, expect);
+        }
+        Case(Notification notif, QueryExp query, boolean expect) {
+            this(notif, query, notif.getClass(), expect);
+        }
+        Case(Notification notif, QueryExp query,
+                Class<? extends Notification> notifClass, boolean expect) {
+            this.notif = notif;
+            this.query = query;
+            this.expect = expect;
+            this.notifClass = notifClass;
+        }
+    }
+
+    /* In principle users can create their own implementations of QueryExp
+     * and use them with QueryNotificationFilter.  If they do so, then
+     * they can call any MBeanServer method.  Not all of those methods
+     * will work with the special MBeanServer we concoct to analyze a
+     * Notification, but some will, including some that are not called
+     * by the standard queries.  So we check each of those cases too.
+     */
+    private static class ExoticCase {
+        final Notification trueNotif;
+        final Notification falseNotif;
+        final QueryExp query;
+        ExoticCase(Notification trueNotif, Notification falseNotif, QueryExp query) {
+            this.trueNotif = trueNotif;
+            this.falseNotif = falseNotif;
+            this.query = query;
+        }
+    }
+
+    private static abstract class ExoticQuery
+            extends QueryEval implements QueryExp {
+        private final String queryString;
+        ExoticQuery(String queryString) {
+            this.queryString = queryString;
+        }
+        abstract boolean apply(MBeanServer mbs, ObjectName name) throws Exception;
+        @Override
+        public boolean apply(ObjectName name) {
+            try {
+                return apply(getMBeanServer(), name);
+            } catch (Exception e) {
+                e.printStackTrace(System.out);
+                return false;
+            }
+        }
+        @Override
+        public String toString() {
+            return queryString;
+        }
+    }
+
+    private static ObjectName makeObjectName(String s) {
+        try {
+            return new ObjectName(s);
+        } catch (MalformedObjectNameException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static class CustomNotification extends Notification {
+        public CustomNotification(String type, Object source, long seqNo) {
+            super(type, source, seqNo);
+        }
+
+        public String getName() {
+            return "claude";
+        }
+
+        public boolean isInteresting() {
+            return true;
+        }
+    }
+
+    private static final Notification simpleNotif =
+            new Notification("mytype", "source", 0L);
+    private static final Notification attrChangeNotif =
+            new AttributeChangeNotification(
+                    "x", 0L, 0L, "msg", "AttrName", "int", 2, 3);
+    private static final ObjectName testObjectName = makeObjectName("a:b=c");
+    private static final Notification sourcedNotif =
+            new Notification("mytype", testObjectName, 0L);
+    private static final Notification customNotif =
+            new CustomNotification("mytype", testObjectName, 0L);
+
+    private static final Case[] testCases = {
+        new Case(simpleNotif, "Type = 'mytype'", true),
+        new Case(simpleNotif, "Type = 'mytype'",
+                Notification.class, true),
+        new Case(simpleNotif, "Type = 'mytype'",
+                AttributeChangeNotification.class, false),
+        new Case(simpleNotif, "Type != 'mytype'", false),
+        new Case(simpleNotif, "Type = 'somethingelse'", false),
+        new Case(attrChangeNotif, "AttributeName = 'AttrName'", true),
+        new Case(attrChangeNotif,
+                "instanceof 'javax.management.AttributeChangeNotification'",
+                true),
+        new Case(attrChangeNotif,
+                "instanceof 'javax.management.Notification'",
+                true),
+        new Case(attrChangeNotif,
+                "instanceof 'javax.management.relation.MBeanServerNotification'",
+                false),
+        new Case(attrChangeNotif,
+                "class = 'javax.management.AttributeChangeNotification'",
+                true),
+        new Case(attrChangeNotif,
+                "javax.management.AttributeChangeNotification#AttributeName = 'AttrName'",
+                true),
+        new Case(sourcedNotif,
+                testObjectName,
+                true),
+        new Case(sourcedNotif,
+                makeObjectName("a*:b=*"),
+                true),
+        new Case(sourcedNotif,
+                makeObjectName("a*:c=*"),
+                false),
+        new Case(customNotif, "Name = 'claude'", true),
+        new Case(customNotif, "Name = 'tiddly'", false),
+        new Case(customNotif, "Interesting = true", true),
+        new Case(customNotif, "Interesting = false", false),
+    };
+
+    private static final ExoticCase[] exoticTestCases = {
+        new ExoticCase(
+                simpleNotif, new Notification("notmytype", "source", 0L),
+                new ExoticQuery("getAttributes") {
+                    boolean apply(MBeanServer mbs, ObjectName name)
+                            throws Exception {
+                        List<Attribute> attrs = mbs.getAttributes(
+                                name, new String[] {"Type", "Source"}).asList();
+                        return (attrs.get(0).equals(new Attribute("Type", "mytype")) &&
+                                attrs.get(1).equals(new Attribute("Source", "source")));
+                    }
+                }),
+        new ExoticCase(
+                new Notification("mytype", "source", 0L) {},
+                simpleNotif,
+                new ExoticQuery("getClassLoaderFor") {
+                    boolean apply(MBeanServer mbs, ObjectName name)
+                            throws Exception {
+                        return (mbs.getClassLoaderFor(name) ==
+                                this.getClass().getClassLoader());
+                    }
+                }),
+        new ExoticCase(
+                sourcedNotif, simpleNotif,
+                new ExoticQuery("getDomains") {
+                    boolean apply(MBeanServer mbs, ObjectName name)
+                            throws Exception {
+                        return Arrays.equals(mbs.getDomains(),
+                                new String[] {testObjectName.getDomain()});
+                    }
+                }),
+        new ExoticCase(
+                simpleNotif, attrChangeNotif,
+                new ExoticQuery("getMBeanInfo") {
+                    boolean apply(MBeanServer mbs, ObjectName name)
+                            throws Exception {
+                        MBeanInfo mbi = mbs.getMBeanInfo(name);
+                        // If we ever add a constructor to Notification then
+                        // we will have to change the 4 below.
+                        if (mbi.getOperations().length > 0 ||
+                                mbi.getConstructors().length != 4 ||
+                                mbi.getNotifications().length > 0)
+                            return false;
+                        Set<String> expect = new HashSet<String>(
+                            Arrays.asList(
+                                "Class", "Message", "SequenceNumber", "Source",
+                                "TimeStamp", "Type", "UserData"));
+                        Set<String> actual = new HashSet<String>();
+                        for (MBeanAttributeInfo mbai : mbi.getAttributes())
+                            actual.add(mbai.getName());
+                        return actual.equals(expect);
+                    }
+                }),
+        new ExoticCase(
+                simpleNotif, attrChangeNotif,
+                new ExoticQuery("getObjectInstance") {
+                    boolean apply(MBeanServer mbs, ObjectName name)
+                            throws Exception {
+                        ObjectInstance oi = mbs.getObjectInstance(name);
+                        return oi.getClassName().equals(Notification.class.getName());
+                    }
+                }),
+        new ExoticCase(
+                sourcedNotif, simpleNotif,
+                new ExoticQuery("queryNames") {
+                    boolean apply(MBeanServer mbs, ObjectName name)
+                            throws Exception {
+                        Set<ObjectName> names = mbs.queryNames(null,
+                                Query.eq(Query.attr("Type"), Query.value("mytype")));
+                        return names.equals(Collections.singleton(testObjectName));
+                    }
+                }),
+        new ExoticCase(
+                sourcedNotif, simpleNotif,
+                new ExoticQuery("queryMBeans") {
+                    boolean apply(MBeanServer mbs, ObjectName name)
+                            throws Exception {
+                        Set<ObjectInstance> insts = mbs.queryMBeans(null,
+                                Query.eq(Query.attr("Type"), Query.value("mytype")));
+                        if (insts.size() != 1)
+                            return false;
+                        ObjectInstance inst = insts.iterator().next();
+                        return (inst.getObjectName().equals(testObjectName) &&
+                                inst.getClassName().equals(Notification.class.getName()));
+                    }
+                }),
+    };
+
+    private static enum Test {
+        QUERY_EXP("query"), STRING("string"), STRING_PLUS_CLASS("string with class");
+        private final String name;
+        Test(String name) {
+            this.name = name;
+        }
+        @Override
+        public String toString() {
+            return name;
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        boolean allok = true;
+        for (Case testCase : testCases) {
+            for (Test test : Test.values()) {
+                QueryNotificationFilter nf;
+                String queryString;
+                switch (test) {
+                case QUERY_EXP: {
+                    QueryExp inst = Query.isInstanceOf(
+                            Query.value(testCase.notifClass.getName()));
+                    QueryExp and = Query.and(inst, testCase.query);
+                    queryString = Query.toString(and);
+                    nf = new QueryNotificationFilter(and);
+                    break;
+                }
+                case STRING: {
+                    String s = "instanceof '" + testCase.notifClass.getName() + "'";
+                    queryString = s + " and " + Query.toString(testCase.query);
+                    nf = new QueryNotificationFilter(queryString);
+                    break;
+                }
+                case STRING_PLUS_CLASS:
+                    queryString = null;
+                    nf = new QueryNotificationFilter(
+                            testCase.notifClass, Query.toString(testCase.query));
+                    break;
+                default:
+                    throw new AssertionError();
+                }
+                boolean accept = nf.isNotificationEnabled(testCase.notif);
+                if (queryString != null) {
+                    queryString = Query.toString(Query.fromString(queryString));
+                    if (!queryString.equals(Query.toString(nf.getQuery()))) {
+                        System.out.println("FAIL: query string mismatch: expected " +
+                                "\"" + queryString + "\", got \"" +
+                                Query.toString(nf.getQuery()));
+                        allok = false;
+                    }
+                }
+                boolean ok = (accept == testCase.expect);
+                System.out.println((ok ? "pass" : "FAIL") + ": " +
+                        testCase.query + " (" + test + ")");
+                allok &= ok;
+            }
+        }
+        for (ExoticCase testCase : exoticTestCases) {
+            NotificationFilter nf = new QueryNotificationFilter(testCase.query);
+            for (boolean expect : new boolean[] {true, false}) {
+                Notification n = expect ? testCase.trueNotif : testCase.falseNotif;
+                boolean accept = nf.isNotificationEnabled(n);
+                boolean ok = (accept == expect);
+                System.out.println((ok ? "pass" : "FAIL") + ": " +
+                        testCase.query + ": " + n);
+                allok &= ok;
+            }
+        }
+        if (!allok)
+            throw new Exception("TEST FAILED");
+    }
+}