blob: 9c92fcafd9725905632d8a5f528fa09e96280753 [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 java.beans.IntrospectionException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.List;
import javax.management.JMX;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanNotificationInfo;
import javax.management.Notification;
import javax.management.NotificationBroadcaster;
import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationEmitter;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import org.apache.logging.log4j.Logger;
import org.apache.geode.SystemFailure;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.execute.FunctionService;
import org.apache.geode.cache.execute.ResultCollector;
import org.apache.geode.distributed.DistributedMember;
import org.apache.geode.internal.logging.LogService;
/**
* This class is the proxy handler for all the proxies created for federated MBeans. Its designed
* with Java proxy mechanism. All data calls are delegated to the federation components. All method
* calls are routed to specified members via Function service
*
*
*/
public class MBeanProxyInvocationHandler implements InvocationHandler {
private static final Logger logger = LogService.getLogger();
/**
* Name of the MBean
*/
private ObjectName objectName;
/**
* The monitoring region where this Object resides.
*/
private Region<String, Object> monitoringRegion;
/**
* The member to which this proxy belongs
*/
private DistributedMember member;
/**
* emitter is a helper class for sending notifications on behalf of the proxy
*/
private final NotificationBroadcasterSupport emitter;
private final ProxyInterface proxyImpl;
private boolean isMXBean;
private MXBeanProxyInvocationHandler mxbeanInvocationRef;
/**
*
* @param member member to which this MBean belongs
* @param monitoringRegion corresponding MonitoringRegion
* @param objectName ObjectName of the MBean
* @param interfaceClass on which interface the proxy to be exposed
*/
public static Object newProxyInstance(DistributedMember member,
Region<String, Object> monitoringRegion, ObjectName objectName,
FederationComponent federationComponent, Class interfaceClass)
throws ClassNotFoundException, IntrospectionException {
boolean isMXBean = JMX.isMXBeanInterface(interfaceClass);
boolean notificationBroadcaster = federationComponent.isNotificationEmitter();
InvocationHandler handler =
new MBeanProxyInvocationHandler(member, objectName, monitoringRegion, isMXBean);
Class[] interfaces;
if (notificationBroadcaster) {
interfaces =
new Class[] {interfaceClass, ProxyInterface.class, NotificationBroadCasterProxy.class};
} else {
interfaces = new Class[] {interfaceClass, ProxyInterface.class};
}
Object proxy = Proxy.newProxyInstance(MBeanProxyInvocationHandler.class.getClassLoader(),
interfaces, handler);
return interfaceClass.cast(proxy);
}
/**
*
* @param member member to which this MBean belongs
* @param objectName ObjectName of the MBean
* @param monitoringRegion corresponding MonitoringRegion
*/
private MBeanProxyInvocationHandler(DistributedMember member, ObjectName objectName,
Region<String, Object> monitoringRegion, boolean isMXBean)
throws IntrospectionException, ClassNotFoundException {
this.member = member;
this.objectName = objectName;
this.monitoringRegion = monitoringRegion;
this.emitter = new NotificationBroadcasterSupport();
this.proxyImpl = new ProxyInterfaceImpl();
this.isMXBean = isMXBean;
}
/**
* Inherited method from Invocation handler All object state requests are delegated to the
* federated component.
*
* All setters and operations() are delegated to the function service.
*
* Notification emmitter methods are also delegated to the function service
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (logger.isTraceEnabled()) {
logger.trace("Invoking Method {}", method.getName());
}
final Class methodClass = method.getDeclaringClass();
if (methodClass.equals(NotificationBroadcaster.class)
|| methodClass.equals(NotificationEmitter.class))
return invokeBroadcasterMethod(proxy, method, args);
final String methodName = method.getName();
final Class[] paramTypes = method.getParameterTypes();
final Class returnType = method.getReturnType();
final int nargs = (args == null) ? 0 : args.length;
if (methodName.equals("setLastRefreshedTime")) {
proxyImpl.setLastRefreshedTime((Long) args[0]);
return null;
}
if (methodName.equals("getLastRefreshedTime")) {
return proxyImpl.getLastRefreshedTime();
}
if (methodName.equals("sendNotification")) {
sendNotification(args[0]);
return null;
}
// local or not: equals, toString, hashCode
if (shouldDoLocally(proxy, method)) {
return doLocally(proxy, method, args);
}
// Support For MXBean open types
if (isMXBean) {
MXBeanProxyInvocationHandler p = findMXBeanProxy(objectName, methodClass, this);
return p.invoke(proxy, method, args);
}
if (methodName.startsWith("get") && methodName.length() > 3 && nargs == 0
&& !returnType.equals(Void.TYPE)) {
return delegateToObjectState(methodName.substring(3));
}
if (methodName.startsWith("is") && methodName.length() > 2 && nargs == 0
&& (returnType.equals(Boolean.TYPE) || returnType.equals(Boolean.class))) {
return delegateToObjectState(methodName.substring(2));
}
final String[] signature = new String[paramTypes.length];
for (int i = 0; i < paramTypes.length; i++)
signature[i] = paramTypes[i].getName();
if (methodName.startsWith("set") && methodName.length() > 3 && nargs == 1
&& returnType.equals(Void.TYPE)) {
return delegateToFucntionService(objectName, methodName, args, signature);
}
return delegateToFucntionService(objectName, methodName, args, signature);
}
/**
* As this proxy may behave as an notification emitter it delegates to the member
* NotificationBroadcasterSupport object
*
*/
private void sendNotification(Object notification) {
emitter.sendNotification((Notification) notification);
}
/**
* This will get the data from Object state which is replicated across the hidden region
* FederataionComponent being the carrier.
*
*/
protected Object delegateToObjectState(String attributeName) throws Throwable {
Object returnObj;
try {
FederationComponent fedComp =
(FederationComponent) monitoringRegion.get(objectName.toString());
returnObj = fedComp.getValue(attributeName);
} catch (IllegalArgumentException e) {
throw new MBeanException(e);
} catch (Exception e) {
throw new MBeanException(e);
} catch (VirtualMachineError e) {
SystemFailure.initiateFailure(e);
throw e;
} catch (Throwable th) {
SystemFailure.checkFailure();
throw new MBeanException(new Exception(th.getLocalizedMessage()));
}
return returnObj;
}
/**
* It will call the Generic function to execute the method on the remote VM
*
* @param objectName ObjectName of the MBean
* @param methodName method name
* @param args arguments to the methods
* @param signature signature of the method
* @return result Object
*/
protected Object delegateToFucntionService(ObjectName objectName, String methodName,
Object[] args, String[] signature) throws Throwable {
Object[] functionArgs = new Object[5];
functionArgs[0] = objectName;
functionArgs[1] = methodName;
functionArgs[2] = signature;
functionArgs[3] = args;
functionArgs[4] = member.getName();
List<Object> result = null;
try {
ResultCollector rc = FunctionService.onMember(member).setArguments(functionArgs)
.execute(ManagementConstants.MGMT_FUNCTION_ID);
result = (List<Object>) rc.getResult();
// Exceptions of ManagementFunctions
} catch (Exception e) {
if (logger.isDebugEnabled()) {
logger.debug(" Exception while Executing Funtion {}", e.getMessage(), e);
}
// Only in case of Exception caused for Function framework.
return null;
} catch (VirtualMachineError e) {
SystemFailure.initiateFailure(e);
throw e;
} catch (Throwable th) {
SystemFailure.checkFailure();
if (logger.isDebugEnabled()) {
logger.debug(" Exception while Executing Funtion {}", th.getMessage(), th);
}
return null;
}
return checkErrors(result.get(ManagementConstants.RESULT_INDEX));
}
private Object checkErrors(Object lastResult) throws Throwable {
if (lastResult instanceof MBeanException) {
// Convert all MBean public API exceptions to MBeanException
throw (Exception) lastResult;
}
if (lastResult instanceof Exception) {
return null;
}
if (lastResult instanceof Throwable) {
return null;
}
return lastResult;
}
/**
* The call will delegate to Managed Node for NotificationHub to register a local listener to
* listen for notification from the MBean
*
* Moreover it will also add the client to local listener list by adding to the contained emitter.
*
* @param proxy the proxy object
* @param method method to be invoked
* @param args method arguments
* @return result value if any
*/
private Object invokeBroadcasterMethod(Object proxy, Method method, Object[] args)
throws Throwable {
final String methodName = method.getName();
final int nargs = (args == null) ? 0 : args.length;
final Class[] paramTypes = method.getParameterTypes();
final String[] signature = new String[paramTypes.length];
if (methodName.equals("addNotificationListener")) {
/*
* The various throws of IllegalArgumentException here should not happen, since we know what
* the methods in NotificationBroadcaster and NotificationEmitter are.
*/
if (nargs != 3) {
final String msg = "Bad arg count to addNotificationListener: " + nargs;
throw new IllegalArgumentException(msg);
}
/*
* Other inconsistencies will produce ClassCastException below.
*/
NotificationListener listener = (NotificationListener) args[0];
NotificationFilter filter = (NotificationFilter) args[1];
Object handback = args[2];
emitter.addNotificationListener(listener, filter, handback);
delegateToFucntionService(objectName, methodName, null, signature);
return null;
} else if (methodName.equals("removeNotificationListener")) {
/*
* NullPointerException if method with no args, but that shouldn't happen because removeNL
* does have args.
*/
NotificationListener listener = (NotificationListener) args[0];
switch (nargs) {
case 1:
emitter.removeNotificationListener(listener);
/**
* No need to send listener and filter details to other members. We only need to send a
* message saying remove the listner registered for this object on your side. Fixes Bug[
* #47075 ]
*/
delegateToFucntionService(objectName, methodName, null, signature);
return null;
case 3:
NotificationFilter filter = (NotificationFilter) args[1];
Object handback = args[2];
emitter.removeNotificationListener(listener, filter, handback);
delegateToFucntionService(objectName, methodName, null, signature);
return null;
default:
final String msg = "Bad arg count to removeNotificationListener: " + nargs;
throw new IllegalArgumentException(msg);
}
} else if (methodName.equals("getNotificationInfo")) {
if (args != null) {
throw new IllegalArgumentException("getNotificationInfo has " + "args");
}
if (!MBeanJMXAdapter.mbeanServer.isRegistered(objectName)) {
return new MBeanNotificationInfo[0];
}
/**
* MBean info is delegated to function service as intention is to get the info of the actual
* mbean rather than the proxy
*/
Object obj = delegateToFucntionService(objectName, methodName, args, signature);
if (obj instanceof String) {
return new MBeanNotificationInfo[0];
}
MBeanInfo info = (MBeanInfo) obj;
return info.getNotifications();
} else {
throw new IllegalArgumentException("Bad method name: " + methodName);
}
}
/**
* Internal implementation of all the generic proxy methods
*
*
*/
private class ProxyInterfaceImpl implements ProxyInterface {
/**
* last refreshed time of the proxy
*/
private long lastRefreshedTime;
/**
* Constructore
*/
public ProxyInterfaceImpl() {
this.lastRefreshedTime = System.currentTimeMillis();
}
/**
* Last refreshed time
*/
@Override
public long getLastRefreshedTime() {
return lastRefreshedTime;
}
/**
* sets the proxy refresh time
*/
@Override
public void setLastRefreshedTime(long lastRefreshedTime) {
this.lastRefreshedTime = lastRefreshedTime;
}
}
private boolean shouldDoLocally(Object proxy, Method method) {
final String methodName = method.getName();
if ((methodName.equals("hashCode") || methodName.equals("toString"))
&& method.getParameterTypes().length == 0)
return true;
if (methodName.equals("equals")
&& Arrays.equals(method.getParameterTypes(), new Class[] {Object.class}))
return true;
return false;
}
private Object doLocally(Object proxy, Method method, Object[] args) {
final String methodName = method.getName();
FederationComponent fedComp = (FederationComponent) monitoringRegion.get(objectName.toString());
if (methodName.equals("equals")) {
return fedComp.equals(args[0]);
} else if (methodName.equals("toString")) {
return fedComp.toString();
} else if (methodName.equals("hashCode")) {
return fedComp.hashCode();
}
throw new RuntimeException("Unexpected method name: " + methodName);
}
private MXBeanProxyInvocationHandler findMXBeanProxy(ObjectName objectName,
Class<?> mxbeanInterface, MBeanProxyInvocationHandler handler) throws Throwable {
MXBeanProxyInvocationHandler proxyRef = mxbeanInvocationRef;
if (mxbeanInvocationRef == null) {
synchronized (this) {
try {
mxbeanInvocationRef =
new MXBeanProxyInvocationHandler(objectName, mxbeanInterface, handler);
} catch (IllegalArgumentException e) {
String msg =
"Cannot make MXBean proxy for " + mxbeanInterface.getName() + ": " + e.getMessage();
throw new IllegalArgumentException(msg, e.getCause());
}
}
}
return mxbeanInvocationRef;
}
}