blob: 4831134f6af356d80807e14763d0c25be4bc4458 [file] [log] [blame]
/*
* 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.ref.WeakReference;
import java.security.PrivilegedAction;
import java.util.Map;
import java.util.WeakHashMap;
import javax.annotation.Resource;
import javax.management.MBeanServer;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import static com.sun.jmx.mbeanserver.Util.newMap;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.management.SendNotification;
public class MBeanInjector {
private static Class<?>[] injectedClasses = {
MBeanServer.class, ObjectName.class, SendNotification.class,
};
public static void inject(Object mbean, MBeanServer mbs, ObjectName name)
throws Exception {
ClassInjector injector = injectorForClass(mbean.getClass());
injector.inject(mbean, MBeanServer.class, mbs);
injector.inject(mbean, ObjectName.class, name);
}
public static boolean injectsSendNotification(Object mbean)
throws NotCompliantMBeanException {
ClassInjector injector = injectorForClass(mbean.getClass());
return injector.injects(SendNotification.class);
}
public static void injectSendNotification(Object mbean, SendNotification sn)
throws Exception {
ClassInjector injector = injectorForClass(mbean.getClass());
injector.inject(mbean, SendNotification.class, sn);
}
public static void validate(Class<?> c) throws NotCompliantMBeanException {
injectorForClass(c);
}
private static class ClassInjector {
private Map<Class<?>, List<Field>> fields;
private Map<Class<?>, List<Method>> methods;
ClassInjector(Class<?> c) throws NotCompliantMBeanException {
fields = newMap();
methods = newMap();
Class<?> sup = c.getSuperclass();
ClassInjector supInjector;
if (sup == null) {
supInjector = null;
} else {
supInjector = injectorForClass(sup);
fields.putAll(supInjector.fields);
methods.putAll(supInjector.methods);
}
addMembers(c);
eliminateOverriddenMethods();
// If we haven't added any new fields or methods to what we
// inherited, then we can share the parent's maps.
if (supInjector != null) {
if (fields.equals(supInjector.fields))
fields = supInjector.fields;
if (methods.equals(supInjector.methods))
methods = supInjector.methods;
}
}
boolean injects(Class<?> c) {
return (fields.get(c) != null || methods.get(c) != null);
}
<T> void inject(Object instance, Class<T> type, T resource)
throws Exception {
List<Field> fs = fields.get(type);
if (fs != null) {
for (Field f : fs)
f.set(instance, resource);
}
List<Method> ms = methods.get(type);
if (ms != null) {
for (Method m : ms) {
try {
m.invoke(instance, resource);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof Error)
throw (Error) cause;
else
throw (Exception) cause;
}
}
}
}
private void eliminateOverriddenMethods() {
/* Covariant overriding is unlikely, but it is possible that the
* parent has a @Resource method that we override with another
* @Resource method. We don't want to invoke both methods,
* because polymorphism means we would actually invoke the same
* method twice.
*/
for (Map.Entry<Class<?>, List<Method>> entry : methods.entrySet()) {
List<Method> list = entry.getValue();
list = MBeanAnalyzer.eliminateCovariantMethods(list);
entry.setValue(list);
}
}
/*
* Find Fields or Methods within the given Class that we can inject
* resource references into. Suppose we want to know if a Field can get
* a reference to an ObjectName. We'll accept fields like this:
*
* @Resource
* private transient ObjectName name;
*
* or like this:
*
* @Resource(type = ObjectName.class)
* private transient Object name;
*
* but not like this:
*
* @Resource
* private transient Object name;
*
* (Plain @Resource is equivalent to @Resource(type = Object.class).)
*
* We don't want to inject into everything that might possibly accept
* an ObjectName reference, because examples like the last one above
* could also accept an MBeanServer reference or any other sort of
* reference.
*
* So we accept a Field if it has a @Resource annotation and either
* (a) its type is ObjectName or a subclass and its @Resource type is
* compatible with ObjectName (e.g. it is Object); or
* (b) its type is compatible with ObjectName and its @Resource type
* is exactly ObjectName. Fields that meet these criteria will not
* meet the same criteria with respect to other types such as MBeanServer.
*
* The same logic applies mutatis mutandis to Methods such as this:
*
* @Resource
* private void setObjectName1(ObjectName name)
* @Resource(type = Object.class)
* private void setObjectName2(Object name)
*/
private void addMembers(final Class<?> c)
throws NotCompliantMBeanException {
AccessibleObject[][] memberArrays =
AccessController.doPrivileged(
new PrivilegedAction<AccessibleObject[][]>() {
public AccessibleObject[][] run() {
return new AccessibleObject[][] {
c.getDeclaredFields(), c.getDeclaredMethods()
};
}
});
for (AccessibleObject[] members : memberArrays) {
for (final AccessibleObject member : members) {
Resource res = member.getAnnotation(Resource.class);
if (res == null)
continue;
final Field field;
final Method method;
final Class<?> memberType;
final int modifiers;
if (member instanceof Field) {
field = (Field) member;
memberType = field.getType();
modifiers = field.getModifiers();
method = null;
} else {
field = null;
method = (Method) member;
Class<?>[] paramTypes = method.getParameterTypes();
if (paramTypes.length != 1) {
throw new NotCompliantMBeanException(
"@Resource method must have exactly 1 " +
"parameter: " + method);
}
if (method.getReturnType() != void.class) {
throw new NotCompliantMBeanException(
"@Resource method must return void: " +
method);
}
memberType = paramTypes[0];
modifiers = method.getModifiers();
}
if (Modifier.isStatic(modifiers)) {
throw new NotCompliantMBeanException(
"@Resource method or field cannot be static: " +
member);
}
for (Class<?> injectedClass : injectedClasses) {
Class<?>[] types = {memberType, res.type()};
boolean accept = false;
for (int i = 0; i < 2; i++) {
if (types[i] == injectedClass &&
types[1 - i].isAssignableFrom(injectedClass)) {
accept = true;
break;
}
}
if (accept) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
member.setAccessible(true);
return null;
}
});
addToMap(fields, injectedClass, field);
addToMap(methods, injectedClass, method);
}
}
}
}
}
private static <K, V> void addToMap(Map<K, List<V>> map, K key, V value) {
if (value == null)
return;
List<V> list = map.get(key);
if (list == null)
list = Collections.singletonList(value);
else {
if (list.size() == 1)
list = new ArrayList<V>(list);
list.add(value);
}
map.put(key, list);
}
}
private static synchronized ClassInjector injectorForClass(Class<?> c)
throws NotCompliantMBeanException {
WeakReference<ClassInjector> wr = injectorMap.get(c);
ClassInjector ci = (wr == null) ? null : wr.get();
if (ci == null) {
ci = new ClassInjector(c);
injectorMap.put(c, new WeakReference<ClassInjector>(ci));
}
return ci;
}
private static Map<Class<?>, WeakReference<ClassInjector>> injectorMap =
new WeakHashMap<Class<?>, WeakReference<ClassInjector>>();
}