提交 f799712d 编写于 作者: E emcmanus

6610917: Define a generic NotificationFilter

Summary: Adds javax.management.QueryNotificationFilter
Reviewed-by: dfuchs
上级 1cb28fec
...@@ -34,8 +34,6 @@ import java.util.Set; ...@@ -34,8 +34,6 @@ import java.util.Set;
import java.util.HashSet; import java.util.HashSet;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.security.AccessControlContext; import java.security.AccessControlContext;
import java.security.Permission; import java.security.Permission;
import java.security.ProtectionDomain; import java.security.ProtectionDomain;
...@@ -51,7 +49,6 @@ import javax.management.InstanceAlreadyExistsException; ...@@ -51,7 +49,6 @@ import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException; import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException; import javax.management.IntrospectionException;
import javax.management.InvalidAttributeValueException; import javax.management.InvalidAttributeValueException;
import javax.management.JMException;
import javax.management.JMRuntimeException; import javax.management.JMRuntimeException;
import javax.management.ListenerNotFoundException; import javax.management.ListenerNotFoundException;
import javax.management.MalformedObjectNameException; import javax.management.MalformedObjectNameException;
...@@ -84,11 +81,10 @@ import static com.sun.jmx.defaults.JmxProperties.MBEANSERVER_LOGGER; ...@@ -84,11 +81,10 @@ import static com.sun.jmx.defaults.JmxProperties.MBEANSERVER_LOGGER;
import com.sun.jmx.mbeanserver.DynamicMBean2; import com.sun.jmx.mbeanserver.DynamicMBean2;
import com.sun.jmx.mbeanserver.ModifiableClassLoaderRepository; import com.sun.jmx.mbeanserver.ModifiableClassLoaderRepository;
import com.sun.jmx.mbeanserver.MBeanInstantiator; import com.sun.jmx.mbeanserver.MBeanInstantiator;
import com.sun.jmx.mbeanserver.MXBeanSupport;
import com.sun.jmx.mbeanserver.Repository; import com.sun.jmx.mbeanserver.Repository;
import com.sun.jmx.mbeanserver.NamedObject; import com.sun.jmx.mbeanserver.NamedObject;
import com.sun.jmx.defaults.ServiceName;
import com.sun.jmx.mbeanserver.Introspector; import com.sun.jmx.mbeanserver.Introspector;
import com.sun.jmx.mbeanserver.Util;
import com.sun.jmx.remote.util.EnvHelp; import com.sun.jmx.remote.util.EnvHelp;
/** /**
...@@ -623,18 +619,9 @@ public class DefaultMBeanServerInterceptor implements MBeanServerInterceptor { ...@@ -623,18 +619,9 @@ public class DefaultMBeanServerInterceptor implements MBeanServerInterceptor {
List<String> result = new ArrayList<String>(domains.length); List<String> result = new ArrayList<String>(domains.length);
for (int i = 0; i < domains.length; i++) { for (int i = 0; i < domains.length; i++) {
try { try {
ObjectName domain = new ObjectName(domains[i] + ":x=x"); ObjectName domain = Util.newObjectName(domains[i] + ":x=x");
checkMBeanPermission((String) null, null, domain, "getDomains"); checkMBeanPermission((String) null, null, domain, "getDomains");
result.add(domains[i]); 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) { } catch (SecurityException e) {
// OK: Do not add this domain to the list // OK: Do not add this domain to the list
} }
......
...@@ -107,10 +107,7 @@ class MBeanAnalyzer<M> { ...@@ -107,10 +107,7 @@ class MBeanAnalyzer<M> {
private MBeanAnalyzer(Class<?> mbeanInterface, private MBeanAnalyzer(Class<?> mbeanInterface,
MBeanIntrospector<M> introspector) MBeanIntrospector<M> introspector)
throws NotCompliantMBeanException { throws NotCompliantMBeanException {
if (!mbeanInterface.isInterface()) { introspector.checkCompliance(mbeanInterface);
throw new NotCompliantMBeanException("Not an interface: " +
mbeanInterface.getName());
}
try { try {
initMaps(mbeanInterface, introspector); initMaps(mbeanInterface, introspector);
...@@ -121,11 +118,10 @@ class MBeanAnalyzer<M> { ...@@ -121,11 +118,10 @@ class MBeanAnalyzer<M> {
// Introspect the mbeanInterface and initialize this object's maps. // Introspect the mbeanInterface and initialize this object's maps.
// //
private void initMaps(Class<?> mbeanInterface, private void initMaps(Class<?> mbeanType,
MBeanIntrospector<M> introspector) throws Exception { MBeanIntrospector<M> introspector) throws Exception {
final Method[] methodArray = mbeanInterface.getMethods(); final List<Method> methods1 = introspector.getMethods(mbeanType);
final List<Method> methods = eliminateCovariantMethods(methods1);
final List<Method> methods = eliminateCovariantMethods(methodArray);
/* Run through the methods to detect inconsistencies and to enable /* Run through the methods to detect inconsistencies and to enable
us to give getter and setter together to visitAttribute. */ us to give getter and setter together to visitAttribute. */
...@@ -234,13 +230,13 @@ class MBeanAnalyzer<M> { ...@@ -234,13 +230,13 @@ class MBeanAnalyzer<M> {
but existing code may depend on it and users may be used to seeing but existing code may depend on it and users may be used to seeing
operations or attributes appear in a particular order. */ operations or attributes appear in a particular order. */
static List<Method> static List<Method>
eliminateCovariantMethods(Method[] methodArray) { eliminateCovariantMethods(List<Method> startMethods) {
// We are assuming that you never have very many methods with the // We are assuming that you never have very many methods with the
// same name, so it is OK to use algorithms that are quadratic // same name, so it is OK to use algorithms that are quadratic
// in the number of methods with the same name. // in the number of methods with the same name.
final int len = methodArray.length; final int len = startMethods.size();
final Method[] sorted = methodArray.clone(); final Method[] sorted = startMethods.toArray(new Method[len]);
Arrays.sort(sorted,MethodOrder.instance); Arrays.sort(sorted,MethodOrder.instance);
final Set<Method> overridden = newSet(); final Set<Method> overridden = newSet();
for (int i=1;i<len;i++) { for (int i=1;i<len;i++) {
...@@ -259,7 +255,7 @@ class MBeanAnalyzer<M> { ...@@ -259,7 +255,7 @@ class MBeanAnalyzer<M> {
} }
} }
final List<Method> methods = newList(Arrays.asList(methodArray)); final List<Method> methods = newList(startMethods);
methods.removeAll(overridden); methods.removeAll(overridden);
return methods; return methods;
} }
......
...@@ -34,6 +34,7 @@ import java.lang.reflect.Constructor; ...@@ -34,6 +34,7 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.WeakHashMap; import java.util.WeakHashMap;
...@@ -169,6 +170,19 @@ abstract class MBeanIntrospector<M> { ...@@ -169,6 +170,19 @@ abstract class MBeanIntrospector<M> {
*/ */
abstract Descriptor getMBeanDescriptor(Class<?> resourceClass); 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) final PerInterface<M> getPerInterface(Class<?> mbeanInterface)
throws NotCompliantMBeanException { throws NotCompliantMBeanException {
......
/*
* 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 { ...@@ -438,7 +438,7 @@ public abstract class OpenConverter {
c.getClassLoader() == null); c.getClassLoader() == null);
final List<Method> methods = final List<Method> methods =
MBeanAnalyzer.eliminateCovariantMethods(c.getMethods()); MBeanAnalyzer.eliminateCovariantMethods(Arrays.asList(c.getMethods()));
final SortedMap<String,Method> getterMap = newSortedMap(); final SortedMap<String,Method> getterMap = newSortedMap();
/* Select public methods that look like "T getX()" or "boolean /* Select public methods that look like "T getX()" or "boolean
......
...@@ -415,17 +415,8 @@ public class Repository { ...@@ -415,17 +415,8 @@ public class Repository {
boolean to_default_domain = false; boolean to_default_domain = false;
// Set domain to default if domain is empty and not already set // Set domain to default if domain is empty and not already set
if (dom.length() == 0) { if (dom.length() == 0)
try { name = Util.newObjectName(domain + name.toString());
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);
}
}
}
// Do we have default domain ? // Do we have default domain ?
if (dom == domain) { if (dom == domain) {
......
...@@ -38,6 +38,8 @@ import java.util.Map; ...@@ -38,6 +38,8 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.SortedMap; import java.util.SortedMap;
import java.util.TreeMap; import java.util.TreeMap;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
public class Util { public class Util {
static <K, V> Map<K, V> newMap() { static <K, V> Map<K, V> newMap() {
...@@ -85,6 +87,14 @@ public class Util { ...@@ -85,6 +87,14 @@ public class Util {
return new ArrayList<E>(c); 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 /* This method can be used by code that is deliberately violating the
* allowed checked casts. Rather than marking the whole method containing * allowed checked casts. Rather than marking the whole method containing
* the code with @SuppressWarnings, you can use a call to this method for * the code with @SuppressWarnings, you can use a call to this method for
......
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
package javax.management; package javax.management;
import com.sun.jmx.mbeanserver.GetPropertyAction; import com.sun.jmx.mbeanserver.GetPropertyAction;
import com.sun.jmx.mbeanserver.Util;
import java.io.IOException; import java.io.IOException;
import java.io.InvalidObjectException; import java.io.InvalidObjectException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
...@@ -1386,12 +1387,7 @@ public class ObjectName extends ToQueryString ...@@ -1386,12 +1387,7 @@ public class ObjectName extends ToQueryString
throws NullPointerException { throws NullPointerException {
if (name.getClass().equals(ObjectName.class)) if (name.getClass().equals(ObjectName.class))
return name; return name;
try { return Util.newObjectName(name.getSerializedNameString());
return new ObjectName(name.getSerializedNameString());
} catch (MalformedObjectNameException e) {
throw new IllegalArgumentException("Unexpected: " + e);
// can't happen
}
} }
/** /**
...@@ -1950,14 +1946,7 @@ public class ObjectName extends ToQueryString ...@@ -1950,14 +1946,7 @@ public class ObjectName extends ToQueryString
* *
* @since 1.6 * @since 1.6
*/ */
public static final ObjectName WILDCARD; public static final ObjectName WILDCARD = Util.newObjectName("*:*");
static {
try {
WILDCARD = new ObjectName("*:*");
} catch (MalformedObjectNameException e) {
throw new Error("Can't initialize wildcard name", e);
}
}
// Category : Utilities <=================================== // Category : Utilities <===================================
......
/*
* 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();
}
}
}
/*
* 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");
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册