blob: 61abecbbc885d293361acbe21cda2b4c3b24bc35 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.openejb.monitoring;
import org.apache.openejb.util.Classes;
import org.apache.xbean.finder.ClassFinder;
import org.apache.xbean.propertyeditor.PropertyEditors;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.DynamicMBean;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanConstructorInfo;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.ReflectionException;
import javax.management.MBeanFeatureInfo;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
/**
* @version $Rev$ $Date$
*/
public class ManagedMBean implements DynamicMBean {
private final List<MBeanAttributeInfo> attributes = new ArrayList<MBeanAttributeInfo>();
private final List<MBeanOperationInfo> operations = new ArrayList<MBeanOperationInfo>();
private final Map<String, Member> attributesMap = new HashMap<String, Member>();
private final Map<String, MethodMember> operationsMap = new HashMap<String, MethodMember>();
private final List<Member> dynamic = new ArrayList<Member>();
private Pattern includes = Pattern.compile("");
private Pattern excludes = Pattern.compile("");
private boolean filterAttributes;
private MBeanParameterInfo excludeInfo;
private MBeanParameterInfo includeInfo;
public ManagedMBean(Object managed) {
this(managed, "");
try {
Method method = this.getClass().getMethod("setAttributesFilter", String.class, String.class);
String description = "Filters the attributes that show up in the MBeanInfo." +
" The exclude is applied first, then any attributes that match the " +
"include are re-added. It may be required to disconnect and reconnect " +
"the JMX console to force a refresh of the MBeanInfo";
excludeInfo = new MBeanParameterInfo("excludeRegex", "java.lang.String", "\"" + excludes.pattern() + "\"");
includeInfo = new MBeanParameterInfo("includeRegex", "java.lang.String", "\"" + includes.pattern() + "\"");
MBeanOperationInfo filterOperation = new MBeanOperationInfo("FilterAttributes", description, new MBeanParameterInfo[]{
excludeInfo,
includeInfo,
}, "void", 3);
operations.add(filterOperation);
operationsMap.put(filterOperation.getName(), new MethodMember(method, this, ""));
filterAttributes = true;
} catch (NoSuchMethodException e) {
throw new IllegalStateException(e);
}
}
ManagedMBean(Object managed, String prefix) {
scan(managed, prefix);
for (Member member : attributesMap.values()) {
attributes.add(new MBeanAttributeInfo(member.getName(), member.getType().getName(), "", true, false, false));
}
for (Member member : operationsMap.values()) {
MBeanOperationInfo op = new MBeanOperationInfo("", ((MethodMember) member).getter);
operations.add(new MBeanOperationInfo(member.getName(), "", op.getSignature(), op.getReturnType(), op.getImpact()));
}
filterAttributes = true;
excludeInfo = new MBeanParameterInfo("excludeRegex", "java.lang.String", "\"" + excludes.pattern() + "\"");
includeInfo = new MBeanParameterInfo("includeRegex", "java.lang.String", "\"" + includes.pattern() + "\"");
}
private void sortAttributes(List<MBeanAttributeInfo> attributes) {
Collections.sort(attributes, new Comparator<MBeanAttributeInfo>() {
public int compare(MBeanAttributeInfo o1, MBeanAttributeInfo o2) {
return o1.getName().compareTo(o2.getName());
}
});
}
private void sortOperations(List<MBeanOperationInfo> operations) {
Collections.sort(operations, new Comparator<MBeanOperationInfo>() {
public int compare(MBeanOperationInfo o1, MBeanOperationInfo o2) {
return o1.getName().compareTo(o2.getName());
}
});
}
private void scan(Object target, String prefix) {
ClassFinder finder = new ClassFinder(Classes.ancestors(target.getClass()));
List<Field> fields = finder.findAnnotatedFields(Managed.class);
for (Field field : fields) {
attribute(new FieldMember(field, target, prefix));
}
List<Method> managed = finder.findAnnotatedMethods(Managed.class);
for (Method method : managed) {
MethodMember member = new MethodMember(method, target, prefix);
if (!method.getName().matches("(get|is)([A-Z_].*|)")) {
operationsMap.put(member.getName(), member);
} else {
attribute(new MethodMember(method, target, prefix));
}
}
List<Method> collections = finder.findAnnotatedMethods(ManagedCollection.class);
for (Method method : collections) {
dynamic.add(new MethodMember(method, target, prefix));
}
}
private void attribute(Member member) {
Class<?> type = member.getType();
Managed managed = type.getAnnotation(Managed.class);
if (managed != null) {
try {
String s = "";
if (managed.append()) s = member.getName();
scan(member.get(), s);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} else {
attributesMap.put(member.getName(), member);
}
}
public Object getAttribute(String s) throws AttributeNotFoundException, MBeanException, ReflectionException {
try {
Member member = attributesMap.get(s);
if (member == null) throw new AttributeNotFoundException(s);
return member.get();
} catch (Exception e) {
e.printStackTrace();
throw new ReflectionException(e);
}
}
public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException {
}
public AttributeList getAttributes(String[] strings) {
AttributeList list = new AttributeList(strings.length);
for (String attribute : strings) {
try {
list.add(new Attribute(attribute, getAttribute(attribute)));
} catch (Exception e) {
e.printStackTrace();
}
}
return list;
}
public AttributeList setAttributes(AttributeList attributeList) {
return new AttributeList();
}
public Object invoke(String operation, Object[] args, String[] types) throws MBeanException, ReflectionException {
MethodMember member = operationsMap.get(operation);
Method method = member.getter;
for (int i = 0; i < method.getParameterTypes().length; i++) {
Object value = args[i];
Class<?> expectedType = method.getParameterTypes()[i];
if (value instanceof String && (expectedType != Object.class)) {
String stringValue = (String) value;
value = PropertyEditors.getValue(expectedType, stringValue);
}
args[i] = value;
}
try {
return method.invoke(member.target, args);
} catch (InvocationTargetException e) {
throw new ReflectionException((Exception)e.getCause());
} catch (Exception e) {
throw new ReflectionException(e);
}
}
public MBeanInfo getMBeanInfo() {
List<MBeanAttributeInfo> attributes = new ArrayList<MBeanAttributeInfo>(this.attributes);
List<MBeanOperationInfo> operations = new ArrayList<MBeanOperationInfo>(this.operations);
for (Member member : dynamic) {
try {
ManagedCollection managedCollection = member.getAnnotation(ManagedCollection.class);
Collection collection = (Collection) member.get();
for (Object o : collection) {
try {
Field field = o.getClass().getDeclaredField(managedCollection.key());
field.setAccessible(true);
Object key = field.get(o);
ManagedMBean bean = new ManagedMBean(o, key.toString());
for (MBeanAttributeInfo info : bean.getMBeanInfo().getAttributes()) {
attributes.add(info);
}
for (MBeanOperationInfo info : bean.getMBeanInfo().getOperations()) {
operations.add(info);
}
attributesMap.putAll(bean.attributesMap);
operationsMap.putAll(bean.operationsMap);
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
sortOperations(operations);
sortAttributes(attributes);
if (filterAttributes) {
Iterator<MBeanAttributeInfo> iterator = attributes.iterator();
while (iterator.hasNext()) {
MBeanAttributeInfo info = iterator.next();
if (includes.matcher(info.getName()).matches()) continue;
if (excludes.matcher(info.getName()).matches()) iterator.remove();
}
}
return new MBeanInfo(this.getClass().getName(), "", attributes.toArray(new MBeanAttributeInfo[0]), new MBeanConstructorInfo[0], operations.toArray(new MBeanOperationInfo[0]), new MBeanNotificationInfo[0]);
}
public void setAttributesFilter(String exclude, String include) {
if (include == null) include = "";
if (exclude == null) exclude = "";
includes = Pattern.compile(include);
excludes = Pattern.compile(exclude);
try {
// Set the current value as the description
Field field = MBeanFeatureInfo.class.getDeclaredField("description");
field.setAccessible(true);
field.set(includeInfo, "\"" + includes.pattern() + "\"");
field.set(excludeInfo, "\"" + excludes.pattern() + "\"");
} catch (Exception e) {
// Oh well, we tried
}
}
/**
* Small utility interface used to allow polymorphing
* of java.lang.reflect.Method and java.lang.reflect.Field
* so that each can be treated as injection targets using
* the same code.
*/
public static interface Member {
Object get() throws IllegalAccessException, InvocationTargetException;
String getName();
Class getType();
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
}
/**
* Implementation of Member for java.lang.reflect.Method
* Used for injection targets that are annotated methods
*/
public static class MethodMember implements Member {
private final Method getter;
private final Object target;
private final String prefix;
public MethodMember(Method getter, Object target, String prefix) {
getter.setAccessible(true);
this.getter = getter;
this.target = target;
this.prefix = prefix;
}
public Class getType() {
return getter.getReturnType();
}
public Class getDeclaringClass() {
return getter.getDeclaringClass();
}
public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
return getter.getAnnotation(annotationClass);
}
/**
* The method name needs to be changed from "getFoo" to "foo"
*
* @return
*/
public String getName() {
String method = getter.getName();
StringBuilder name = new StringBuilder(method);
// remove 'get'
if (method.matches("get([A-Z].*|)")) name.delete(0, 3);
if (method.matches("is([A-Z].*|)")) name.delete(0, 2);
if (!"".equals(prefix)) {
if (!"".equals(name.toString())) name.insert(0, ".");
name.insert(0, prefix);
}
return name.toString();
}
public String toString() {
return getter.toString();
}
public Object get() throws IllegalAccessException, InvocationTargetException {
return getter.invoke(target);
}
}
/**
* Implementation of Member for java.lang.reflect.Field
* Used for injection targets that are annotated fields
*/
public static class FieldMember implements Member {
private final Field field;
private final Object target;
private final String prefix;
public FieldMember(Field field, Object target, String prefix) {
field.setAccessible(true);
this.field = field;
this.target = target;
this.prefix = prefix;
}
public Class getType() {
return unwrap(field.getType());
}
public String toString() {
return field.toString();
}
public Class getDeclaringClass() {
return field.getDeclaringClass();
}
public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
return field.getAnnotation(annotationClass);
}
public String getName() {
StringBuilder name = new StringBuilder(field.getName());
name.setCharAt(0, Character.toUpperCase(name.charAt(0)));
if (!"".equals(prefix)) {
if (!"".equals(name.toString())) name.insert(0, ".");
name.insert(0, prefix);
}
return name.toString();
}
public Object get() throws IllegalAccessException {
return unwrap(field.get(target));
}
public Class<?> unwrap(Class<?> clazz) {
if (clazz == AtomicInteger.class) {
return int.class;
} else if (clazz == AtomicBoolean.class) {
return boolean.class;
} else if (clazz == AtomicLong.class) {
return long.class;
} else if (clazz == AtomicReference.class) {
return Object.class;
} else {
return clazz;
}
}
public Object unwrap(Object clazz) {
if (clazz instanceof AtomicInteger) {
return ((AtomicInteger) clazz).get();
} else if (clazz instanceof AtomicBoolean) {
return ((AtomicBoolean) clazz).get();
} else if (clazz instanceof AtomicLong) {
return ((AtomicLong) clazz).get();
} else if (clazz instanceof AtomicReference) {
return ((AtomicReference) clazz).get();
} else {
return clazz;
}
}
}
}