| /* |
| * 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; |
| } |
| |
| } |