/* * Copyright (c) 2008-2012, Hazel Bilisim Ltd. All Rights Reserved. * * Licensed 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 com.hazelcast.jmx; import javax.management.*; import java.lang.reflect.Method; import java.util.HashMap; import java.util.logging.Level; import java.util.logging.Logger; /** * A generic class to instrument object with dynamic MBeans. * <p/> * Implements the method instrument(), in which add attributes and * operation exposed to JMX. * <p/> * If the managed object is an immutable state of some other object that requires * a special refresh strategy, override method {@link #refresh} * <p/> * http://docs.sun.com/app/docs/doc/816-4178/6madjde4n?a=view * * @author Marco Ferrante, DISI - University of Genoa */ public abstract class AbstractMBean<E> implements DynamicMBean, MBeanRegistration { protected final static Logger logger = Logger.getLogger(AbstractMBean.class.getName()); protected transient MBeanServer mbeanServer; private volatile ObjectNameSpec parentName = new ObjectNameSpec(); private volatile ObjectName objectName; // Use a weak reference? http://weblogs.java.net/blog/emcmanus/archive/2005/07/cleaning_up_an_1.html private E managedObject; private String description; protected final ManagementService managementService; /** * Attribute infos */ private class AttributeInfo { protected String name; protected String description = null; protected Method getter = null; protected Method setter = null; public AttributeInfo(String name) { this.name = name; } public MBeanAttributeInfo getInfo() throws IntrospectionException { return new MBeanAttributeInfo(name, description, getter, setter); } } /** * Attribute infos */ private class OperationInfo { protected transient String name; protected transient String description; protected transient Method method; protected MBeanOperationInfo info; public OperationInfo(String name) { this.name = name; } public OperationInfo(String description, Method method) throws IntrospectionException { info = new MBeanOperationInfo(description, method); this.method = method; } public MBeanOperationInfo getInfo() throws IntrospectionException { if (info == null) { info = new MBeanOperationInfo(description, method); } return info; } } private HashMap<String, AttributeInfo> attributeInfos = new HashMap<String, AttributeInfo>(); /** * Operation infos */ private HashMap<String, OperationInfo> operationInfos = new HashMap<String, OperationInfo>(); public AbstractMBean(E managedObject, ManagementService service) { this.managedObject = managedObject; this.managementService = service; } public final E getManagedObject() { return managedObject; } /** * Override this method if the managed object requires some refresh * before reading. Return the new state/object. */ protected E refresh() { return getManagedObject(); } /** * Prepare MBean information, invoked by preRegister(). * * @throws Exception */ private final void instrument() throws Exception { // Class description JMXDescription dscr = this.getClass().getAnnotation(JMXDescription.class); if (dscr != null) { this.description = dscr.value(); } // Search for annotations for (Method method : getClass().getMethods()) { // Attributes if (method.isAnnotationPresent(JMXAttribute.class)) { JMXAttribute annotation = method.getAnnotation(JMXAttribute.class); if (logger.isLoggable(Level.FINEST)) { logger.finest("Found annotation " + annotation + " in method " + method.getName() + " in object " + hashCode() + " of class " + getClass().getName()); } String name = annotation.value(); if (name.length() == 0) { String methodName = method.getName(); if (methodName.startsWith("get")) { name = methodName.substring(3); } else if (methodName.startsWith("set")) { name = methodName.substring(3); } else if (methodName.startsWith("is")) { name = methodName.substring(2); } else { logger.warning("Uncomplaint method name " + method.getName() + " for attribute"); name = method.getName(); } } AttributeInfo info = attributeInfos.get(name); if (info == null) { info = new AttributeInfo(name); attributeInfos.put(name, info); } // Attribute description dscr = method.getAnnotation(JMXDescription.class); if (dscr != null) { if (info.description != null) { logger.warning("Duplicate description for attribute " + name + ", overwrite"); } info.description = dscr.value(); } // getter if (method.getReturnType() != Void.class && method.getParameterTypes().length == 0) { if (info.getter != null) { throw new IllegalArgumentException("Duplicate getter for attribute " + name + " in class " + getClass().getName()); } else { info.getter = method; } } // setter else if (method.getReturnType() == Void.class && method.getParameterTypes().length == 1) { if (info.setter != null) { throw new IllegalArgumentException("Duplicate setter for attribute " + name + " in class " + getClass().getName()); } else { info.setter = method; } } else { logger.warning("Method " + method.getName() + " is neither a setter or a getter"); } } // Operations if (method.isAnnotationPresent(JMXOperation.class)) { JMXOperation annotation = method.getAnnotation(JMXOperation.class); if (logger.isLoggable(Level.FINEST)) { logger.finest("Found operation annotation " + annotation); } String name = annotation.value(); if (name.length() == 0) { throw new IllegalArgumentException("Empty operation name in " + method.getName() + " in class " + getClass().getName()); } if (operationInfos.containsKey(name)) { throw new IllegalArgumentException("Duplicate operation " + name + " in class " + getClass().getName()); } OperationInfo info = new OperationInfo(name); operationInfos.put(name, info); // Attribute description dscr = method.getAnnotation(JMXDescription.class); if (dscr != null) { info.description = dscr.value(); } info.method = method; } } } /** * From DynamicMBean interface */ public MBeanInfo getMBeanInfo() { final MBeanInfo mbeanInfo = new MBeanInfo( managedObject.getClass().getName(), description, buildAttributeInfos(), null, buildOperationInfos(), null ); return mbeanInfo; } private Object getValue(String attribute, boolean refresh) throws AttributeNotFoundException, MBeanException, ReflectionException { if (attribute == null || attribute.length() == 0) throw new NullPointerException("Invalid null attribute requested"); AttributeInfo info = attributeInfos.get(attribute); if (info == null) { logger.log(Level.WARNING, "Managed attribute " + attribute + " not registered in MBean"); throw new AttributeNotFoundException("Attribute " + attribute + " not registered in MBean"); } if (refresh) { managedObject = refresh(); } Object result; try { Method getter = info.getter; if (getter.getDeclaringClass() == this.getClass()) { if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "Attribute '" + attribute + "' belonging to MBean"); } result = getter.invoke(this); } else { if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "Attribute '" + attribute + "' belonging to managed object"); } result = getter.invoke(managedObject); } } catch (Exception e) { logger.log(Level.FINE, "Error accessing attribute " + attribute, e); throw new ReflectionException(e); } return result; } /** * Local attribute override managed object attribute */ public final Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException { return getValue(attribute, true); } /** * getAttributes() in interface DynamicMBean * * @param attributes A String array of names of the attributes to be retrieved. * @return The array of the retrieved attributes. * @throws RuntimeOperationsException Wraps an * {@link IllegalArgumentException}: The object name in parameter is * null or attributes in parameter is null. */ public final AttributeList getAttributes(String[] attributes) { managedObject = refresh(); AttributeList result = new AttributeList(attributes.length); try { for (String name : attributes) { Object value = getValue(name, false); Attribute attribute = new Attribute(name, value); result.add(attribute); } } catch (Exception e) { throw new IllegalArgumentException(e); } return result; } /** * invoke() in interface DynamicMBean */ public final Object invoke(String actionName, Object[] params, String[] signature) throws MBeanException, ReflectionException { if (actionName == null || actionName.length() == 0) throw new NullPointerException("Invalid null operation invoked"); OperationInfo info = operationInfos.get(actionName); if (info == null) { logger.log(Level.WARNING, "Managed operation " + actionName + " not registered in MBean"); throw new UnsupportedOperationException("Operation " + actionName + " not registered in MBean"); } Object result; try { Method method = info.method; if (method.getDeclaringClass() == this.getClass()) { if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "Operation '" + actionName + "' belonging to MBean"); } result = method.invoke(this, params); } else { if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "Operation '" + actionName + "' belonging to managed object", actionName); } result = method.invoke(managedObject, params); } } catch (Exception e) { logger.log(Level.FINE, "Error invoking operation " + actionName, e); throw new ReflectionException(e); } return result; } public final void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException { // Required by DynamicMBean interface, nothing to do throw new UnsupportedOperationException(); } public final AttributeList setAttributes(AttributeList attributes) { // Required by DynamicMBean interface, nothing to do throw new UnsupportedOperationException(); } private MBeanAttributeInfo[] buildAttributeInfos() { if (attributeInfos == null || attributeInfos.size() == 0) { return null; } MBeanAttributeInfo[] result = new MBeanAttributeInfo[attributeInfos.size()]; int i = 0; for (AttributeInfo info : attributeInfos.values()) { try { result[i++] = info.getInfo(); } catch (IntrospectionException e) { logger.log(Level.WARNING, "Error building attribute list", e); throw new IllegalArgumentException(e); } } return result; } private MBeanOperationInfo[] buildOperationInfos() { if (operationInfos == null || operationInfos.size() == 0) { return null; } MBeanOperationInfo[] result = new MBeanOperationInfo[operationInfos.size()]; int i = 0; for (OperationInfo info : operationInfos.values()) { try { result[i++] = info.getInfo(); } catch (IntrospectionException e) { logger.log(Level.WARNING, "Error building operation list", e); throw new IllegalArgumentException(e); } } return result; } public void setParentName(ObjectNameSpec spec) { parentName = spec; } public ObjectNameSpec getParentName() { return parentName; } /** * Override to provide a JMX name */ protected ObjectNameSpec getNameSpec() { return parentName.getNested("unknown", "@" + hashCode()); } /** * The current objectName. * To provide a default, override this method and pass null as name * to the method MBeanServer.registerMBean() * * @return The current objectName */ public final ObjectName getObjectName() throws Exception { if (objectName == null) { objectName = getNameSpec().buildObjectName(); } return objectName; } /** * Build the current objectName from the spec. * * @return The new objectName */ // public ObjectName getObjectName(ObjectNameSpec spec) throws Exception { // objectName = spec.buildObjectName(); // return objectName; // } /** * From interface {@link javax.management.MBeanRegistration} */ public ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception { try { instrument(); } catch (Exception e) { logger.log(Level.FINE, "Error generating MBeanInfo", e); throw e; } if (name != null) { objectName = name; } mbeanServer = server; return getObjectName(); } /** * From interface {@link javax.management.MBeanRegistration} */ public void postRegister(Boolean registrationDone) { ; // Nothing to do } /** * From interface {@link javax.management.MBeanRegistration} */ public void preDeregister() throws Exception { ; // Nothing to do } /** * From interface {@link javax.management.MBeanRegistration} */ public void postDeregister() { ; // Nothing to do } }