6610917: Define a generic NotificationFilter
Adds javax.management.QueryNotificationFilter Reviewed-by: dfuchs
This commit is contained in:
parent
be69c9cfa3
commit
332059a555
jdk
src/share/classes
com/sun/jmx
interceptor
mbeanserver
javax/management
test/javax/management/query
@ -34,8 +34,6 @@ import java.util.Set;
|
||||
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.InstanceAlreadyExistsException;
|
||||
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 static com.sun.jmx.defaults.JmxProperties.MBEANSERVER_LOGGER;
|
||||
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 @@ public class DefaultMBeanServerInterceptor implements MBeanServerInterceptor {
|
||||
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
|
||||
}
|
||||
|
@ -107,10 +107,7 @@ class MBeanAnalyzer<M> {
|
||||
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 @@ class MBeanAnalyzer<M> {
|
||||
|
||||
// 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 @@ class MBeanAnalyzer<M> {
|
||||
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 @@ class MBeanAnalyzer<M> {
|
||||
}
|
||||
}
|
||||
|
||||
final List<Method> methods = newList(Arrays.asList(methodArray));
|
||||
final List<Method> methods = newList(startMethods);
|
||||
methods.removeAll(overridden);
|
||||
return methods;
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ import java.lang.reflect.Constructor;
|
||||
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 class MBeanIntrospector<M> {
|
||||
*/
|
||||
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 {
|
||||
|
@ -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();
|
||||
}
|
@ -438,7 +438,7 @@ public abstract class OpenConverter {
|
||||
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
|
||||
|
@ -415,17 +415,8 @@ public class Repository {
|
||||
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) {
|
||||
|
@ -38,6 +38,8 @@ import java.util.Map;
|
||||
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 @@ public class Util {
|
||||
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
|
||||
|
@ -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 @@ public class ObjectName extends ToQueryString
|
||||
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 @@ public class ObjectName extends ToQueryString
|
||||
*
|
||||
* @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 <===================================
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
347
jdk/test/javax/management/query/QueryNotifFilterTest.java
Normal file
347
jdk/test/javax/management/query/QueryNotifFilterTest.java
Normal file
@ -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");
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user