6610917: Define a generic NotificationFilter
Summary: Adds javax.management.QueryNotificationFilter
Reviewed-by: dfuchs
--- a/jdk/src/share/classes/com/sun/jmx/interceptor/DefaultMBeanServerInterceptor.java Thu Mar 27 14:15:59 2008 -0700
+++ b/jdk/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/jdk/src/share/classes/com/sun/jmx/mbeanserver/MBeanAnalyzer.java Thu Mar 27 14:15:59 2008 -0700
+++ b/jdk/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/jdk/src/share/classes/com/sun/jmx/mbeanserver/MBeanIntrospector.java Thu Mar 27 14:15:59 2008 -0700
+++ b/jdk/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/jdk/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/jdk/src/share/classes/com/sun/jmx/mbeanserver/OpenConverter.java Thu Mar 27 14:15:59 2008 -0700
+++ b/jdk/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/jdk/src/share/classes/com/sun/jmx/mbeanserver/Repository.java Thu Mar 27 14:15:59 2008 -0700
+++ b/jdk/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/jdk/src/share/classes/com/sun/jmx/mbeanserver/Util.java Thu Mar 27 14:15:59 2008 -0700
+++ b/jdk/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/jdk/src/share/classes/javax/management/ObjectName.java Thu Mar 27 14:15:59 2008 -0700
+++ b/jdk/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/jdk/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/jdk/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");
+ }
+}