blob: b78287bc71c872efb7c0946d21b757ed11ae57d7 [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.geode.management.internal;
import static java.util.Arrays.asList;
import java.io.InvalidObjectException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.management.MBeanException;
import javax.management.ObjectName;
import javax.management.openmbean.OpenDataException;
import org.apache.geode.annotations.Immutable;
/**
* This proxy handler handles all the method call invoked on an MXBean It follows same route as
* MBeanProxyInvocationHandler Only difference is after obtaining the result it transforms the open
* type to the actual java type
*/
public class MXBeanProxyInvocationHandler {
@Immutable
private static final Comparator<Method> METHOD_ORDER_COMPARATOR = new MethodOrder();
private final ObjectName objectName;
private final MBeanProxyInvocationHandler proxyHandler;
private final Map<Method, MethodHandler> methodHandlerMap;
MXBeanProxyInvocationHandler(ObjectName objectName, Class<?> mxbeanInterface,
MBeanProxyInvocationHandler proxyHandler) throws IllegalArgumentException {
this(objectName, mxbeanInterface, proxyHandler, OpenTypeUtil.newMap());
}
private MXBeanProxyInvocationHandler(ObjectName objectName, Class<?> mxbeanInterface,
MBeanProxyInvocationHandler proxyHandler, Map<Method, MethodHandler> methodHandlerMap)
throws IllegalArgumentException {
if (mxbeanInterface == null) {
throw new IllegalArgumentException("mxbeanInterface must not be null");
}
this.objectName = objectName;
this.proxyHandler = proxyHandler;
this.methodHandlerMap = methodHandlerMap;
initHandlers(mxbeanInterface);
}
public Object invoke(Object proxy, Method method, Object[] arguments)
throws InvalidObjectException, OpenDataException, MBeanException {
MethodHandler handler = methodHandlerMap.get(method);
OpenMethod convertingMethod = handler.getConvertingMethod();
Object[] openArgs = convertingMethod.toOpenParameters(arguments);
Object result = handler.invoke(proxy, method, openArgs);
return convertingMethod.fromOpenReturnValue(result);
}
private void initHandlers(Class<?> mxbeanInterface) {
Method[] methodArray = mxbeanInterface.getMethods();
List<Method> methods = eliminateCovariantMethods(methodArray);
for (Method method : methods) {
String name = method.getName();
String attributeName = "";
if (name.startsWith("get")) {
attributeName = name.substring(3);
} else if (name.startsWith("is") && method.getReturnType() == boolean.class) {
attributeName = name.substring(2);
}
if (!attributeName.isEmpty() && method.getParameterTypes().length == 0
&& method.getReturnType() != void.class) {
// For Getters
methodHandlerMap.put(method, new GetterHandler(attributeName, OpenMethod.from(method)));
} else if (name.startsWith("set") && name.length() > 3
&& method.getParameterTypes().length == 1 && method.getReturnType() == void.class) {
// For Setters
methodHandlerMap.put(method, new SetterHandler(attributeName, OpenMethod.from(method)));
} else {
methodHandlerMap.put(method, new OpHandler(attributeName, OpenMethod.from(method)));
}
}
}
/**
* Eliminate methods that are overridden with a covariant return type. Reflection will return both
* the original and the overriding method but we need only the overriding one is of interest
*
* @return the method after eliminating covariant methods
*/
private static List<Method> eliminateCovariantMethods(Method[] methodArray) {
int methodCount = methodArray.length;
Method[] sorted = methodArray.clone();
Arrays.sort(sorted, METHOD_ORDER_COMPARATOR);
Set<Method> overridden = OpenTypeUtil.newSet();
for (int i = 1; i < methodCount; i++) {
Method m0 = sorted[i - 1];
Method m1 = sorted[i];
if (!m0.getName().equals(m1.getName())) {
continue;
}
if (Arrays.equals(m0.getParameterTypes(), m1.getParameterTypes())) {
overridden.add(m0);
}
}
List<Method> methods = OpenTypeUtil.newList(asList(methodArray));
methods.removeAll(overridden);
return methods;
}
/**
* Handler for MXBean Proxy
*/
private abstract static class MethodHandler {
private final String name;
private final OpenMethod convertingMethod;
MethodHandler(String name, OpenMethod convertingMethod) {
this.name = name;
this.convertingMethod = convertingMethod;
}
String getName() {
return name;
}
abstract Object invoke(Object proxy, Method method, Object[] arguments) throws MBeanException;
private OpenMethod getConvertingMethod() {
return convertingMethod;
}
}
private class GetterHandler extends MethodHandler {
private GetterHandler(String attributeName, OpenMethod convertingMethod) {
super(attributeName, convertingMethod);
}
@Override
Object invoke(Object proxy, Method method, Object[] arguments) throws MBeanException {
String methodName = method.getName();
String attributeName = "";
if (methodName.startsWith("get")) {
attributeName = methodName.substring(3);
} else if (methodName.startsWith("is") && method.getReturnType() == boolean.class) {
attributeName = methodName.substring(2);
}
return proxyHandler.delegateToObjectState(attributeName);
}
}
private class SetterHandler extends MethodHandler {
private SetterHandler(String attributeName, OpenMethod convertingMethod) {
super(attributeName, convertingMethod);
}
@Override
Object invoke(Object proxy, Method method, Object[] arguments) throws MBeanException {
String methodName = method.getName();
Class[] parameterTypes = method.getParameterTypes();
String[] signature = new String[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
signature[i] = parameterTypes[i].getName();
}
return proxyHandler.delegateToFunctionService(objectName, methodName, arguments, signature);
}
}
private class OpHandler extends MethodHandler {
private OpHandler(String operationName, OpenMethod convertingMethod) {
super(operationName, convertingMethod);
}
@Override
Object invoke(Object proxy, Method method, Object[] arguments) throws MBeanException {
String methodName = method.getName();
Class[] parameterTypes = method.getParameterTypes();
String[] signature = new String[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
signature[i] = parameterTypes[i].getName();
}
return proxyHandler.delegateToFunctionService(objectName, methodName, arguments, signature);
}
}
/**
* A comparator that defines a total order so that methods have the same name and identical
* signatures appear next to each others. The methods are sorted in such a way that methods which
* override each other will sit next to each other, with the overridden method first - e.g. Object
* getFoo() is placed before Integer getFoo(). This makes it possible to determine whether a
* method overrides another one simply by looking at the method(s) that precedes it in the list.
* (see eliminateCovariantMethods).
**/
private static class MethodOrder implements Comparator<Method> {
@Override
public int compare(Method a, Method b) {
int cmp = a.getName().compareTo(b.getName());
if (cmp != 0) {
return cmp;
}
Class<?>[] aparams = a.getParameterTypes();
Class<?>[] bparams = b.getParameterTypes();
if (aparams.length != bparams.length) {
return aparams.length - bparams.length;
}
if (!Arrays.equals(aparams, bparams)) {
return Arrays.toString(aparams).compareTo(Arrays.toString(bparams));
}
Class<?> aret = a.getReturnType();
Class<?> bret = b.getReturnType();
if (aret == bret) {
return 0;
}
if (aret.isAssignableFrom(bret)) {
return -1;
}
return +1;
}
}
}