/* * Copyright (C) MX4J. All rights reserved. * * This software is distributed under the terms of the MX4J License version 1.0. See the terms of * the MX4J License in the documentation provided with this software. */ package org.apache.geode.admin.jmx.internal; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Date; import java.util.Iterator; import javax.management.Attribute; import javax.management.AttributeChangeNotification; import javax.management.AttributeChangeNotificationFilter; import javax.management.AttributeList; import javax.management.AttributeNotFoundException; import javax.management.Descriptor; import javax.management.InstanceNotFoundException; import javax.management.InvalidAttributeValueException; import javax.management.ListenerNotFoundException; import javax.management.MBeanAttributeInfo; import javax.management.MBeanException; import javax.management.MBeanInfo; import javax.management.MBeanNotificationInfo; import javax.management.MBeanOperationInfo; import javax.management.MBeanRegistration; import javax.management.MBeanRegistrationException; import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.Notification; import javax.management.NotificationBroadcasterSupport; import javax.management.NotificationEmitter; import javax.management.NotificationFilter; import javax.management.NotificationListener; import javax.management.ObjectName; import javax.management.ReflectionException; import javax.management.RuntimeErrorException; import javax.management.RuntimeOperationsException; import javax.management.ServiceNotFoundException; import javax.management.loading.ClassLoaderRepository; import javax.management.modelmbean.InvalidTargetObjectTypeException; import javax.management.modelmbean.ModelMBean; import javax.management.modelmbean.ModelMBeanAttributeInfo; import javax.management.modelmbean.ModelMBeanInfo; import javax.management.modelmbean.ModelMBeanOperationInfo; import mx4j.ImplementationException; import mx4j.log.FileLogger; import mx4j.log.Log; import mx4j.log.Logger; import mx4j.log.MBeanLogger; import mx4j.persist.FilePersister; import mx4j.persist.MBeanPersister; import mx4j.persist.PersisterMBean; import mx4j.util.Utils; import org.apache.geode.internal.i18n.LocalizedStrings; /** * @author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a> * @version $Revision: 1.14 $ */ public class MX4JModelMBean implements ModelMBean, MBeanRegistration, NotificationEmitter { private static final String OBJECT_RESOURCE_TYPE = "ObjectReference"; private static final int ALWAYS_STALE = 1; private static final int NEVER_STALE = 2; private static final int STALE = 3; private static final int NOT_STALE = 4; private static final int PERSIST_NEVER = -1; private static final int PERSIST_ON_TIMER = -2; private static final int PERSIST_ON_UPDATE = -3; private static final int PERSIST_NO_MORE_OFTEN_THAN = -4; private MBeanServer m_mbeanServer; private Object m_managedResource; private boolean m_canBeRegistered; private ModelMBeanInfo m_modelMBeanInfo; private NotificationBroadcasterSupport m_attributeChangeBroadcaster = new NotificationBroadcasterSupport(); private NotificationBroadcasterSupport m_generalBroadcaster = new NotificationBroadcasterSupport(); public MX4JModelMBean() throws MBeanException, RuntimeOperationsException { try { load(); } catch (Exception x) { Logger logger = getLogger(); logger.warn(LocalizedStrings.MX4JModelMBean_CANNOT_RESTORE_PREVIOUSLY_SAVED_STATUS .toLocalizedString(), x); } } public MX4JModelMBean(ModelMBeanInfo info) throws MBeanException, RuntimeOperationsException { if (info == null) throw new RuntimeOperationsException(new IllegalArgumentException( LocalizedStrings.MX4JModelMBean_MODELMBEANINFO_PARAMETER_CANT_BE_NULL .toLocalizedString())); else setModelMBeanInfo(info); } private Logger getLogger() { return Log.getLogger(getClass().getName()); } public ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception { if (m_canBeRegistered) { m_mbeanServer = server; return name; } else { throw new MBeanRegistrationException(new IllegalStateException( LocalizedStrings.MX4JModelMBean_MODELMBEAN_CANNOT_BE_REGISTERED_UNTIL_SETMODELMBEANINFO_HAS_BEEN_CALLED .toLocalizedString())); } } public void postRegister(Boolean registrationDone) { if (!registrationDone.booleanValue()) clear(); } public void preDeregister() throws Exception {} public void postDeregister() { clear(); } private void clear() { m_mbeanServer = null; m_managedResource = null; m_modelMBeanInfo = null; m_generalBroadcaster = null; m_attributeChangeBroadcaster = null; // PENDING: also remove generic listeners, attribute change listeners, log4j appenders... } public void setModelMBeanInfo(ModelMBeanInfo modelMBeanInfo) throws MBeanException, RuntimeOperationsException { if (modelMBeanInfo == null) throw new RuntimeOperationsException(new IllegalArgumentException( LocalizedStrings.MX4JModelMBean_MODELMBEANINFO_CANNOT_BE_NULL.toLocalizedString())); if (!isModelMBeanInfoValid(modelMBeanInfo)) throw new RuntimeOperationsException(new IllegalArgumentException( LocalizedStrings.MX4JModelMBean_MODELMBEANINFO_IS_INVALID.toLocalizedString())); m_modelMBeanInfo = (ModelMBeanInfo) modelMBeanInfo.clone(); Logger logger = getLogger(); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("ModelMBeanInfo successfully set to: " + m_modelMBeanInfo); // Only now the MBean can be registered in the MBeanServer m_canBeRegistered = true; } private boolean isModelMBeanInfoValid(ModelMBeanInfo info) { if (info == null || info.getClassName() == null) return false; // PENDING: maybe more checks are needed return true; } public void setManagedResource(Object resource, String resourceType) throws MBeanException, RuntimeOperationsException, InstanceNotFoundException, InvalidTargetObjectTypeException { if (resource == null) throw new RuntimeOperationsException(new IllegalArgumentException( LocalizedStrings.MX4JModelMBean_MANAGED_RESOURCE_CANNOT_BE_NULL.toLocalizedString())); if (!isResourceTypeSupported(resourceType)) throw new InvalidTargetObjectTypeException(resourceType); Logger logger = getLogger(); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Setting managed resource to be: " + resource); m_managedResource = resource; } private boolean isResourceTypeSupported(String resourceType) { // For now only object reference is supported return OBJECT_RESOURCE_TYPE.equals(resourceType); } private Object getManagedResource() { return m_managedResource; } public MBeanInfo getMBeanInfo() { return m_modelMBeanInfo == null ? null : (MBeanInfo) m_modelMBeanInfo.clone(); } public void addAttributeChangeNotificationListener(NotificationListener listener, String attributeName, Object handback) throws MBeanException, RuntimeOperationsException, IllegalArgumentException { if (listener == null) throw new RuntimeOperationsException(new IllegalArgumentException( LocalizedStrings.MX4JModelMBean_LISTENER_CANNOT_BE_NULL.toLocalizedString())); AttributeChangeNotificationFilter filter = new AttributeChangeNotificationFilter(); if (attributeName != null) { filter.enableAttribute(attributeName); } else { MBeanAttributeInfo[] ai = m_modelMBeanInfo.getAttributes(); for (int i = 0; i < ai.length; i++) { Descriptor d = ((ModelMBeanAttributeInfo) ai[i]).getDescriptor(); filter.enableAttribute((String) d.getFieldValue("name")); } } getAttributeChangeBroadcaster().addNotificationListener(listener, filter, handback); Logger logger = getLogger(); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Listener " + listener + " for attribute " + attributeName + " added successfully, handback is " + handback); } public void addNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) throws IllegalArgumentException { m_generalBroadcaster.addNotificationListener(listener, filter, handback); } public MBeanNotificationInfo[] getNotificationInfo() { return m_modelMBeanInfo.getNotifications(); } public void removeAttributeChangeNotificationListener(NotificationListener listener, String attributeName) throws RuntimeOperationsException, ListenerNotFoundException { try { removeAttributeChangeNotificationListener(listener, attributeName, null); } catch (MBeanException e) { throw new RuntimeOperationsException(new RuntimeException(e.getMessage())); } } // Not in the spec but needed private void removeAttributeChangeNotificationListener(NotificationListener listener, String attributeName, Object handback) throws MBeanException, RuntimeOperationsException, ListenerNotFoundException { if (listener == null) throw new RuntimeOperationsException(new IllegalArgumentException( LocalizedStrings.MX4JModelMBean_LISTENER_CANNOT_BE_NULL.toLocalizedString())); AttributeChangeNotificationFilter filter = new AttributeChangeNotificationFilter(); if (attributeName != null) { filter.enableAttribute(attributeName); } else { MBeanAttributeInfo[] ai = m_modelMBeanInfo.getAttributes(); for (int i = 0; i < ai.length; i++) { Descriptor d = ((ModelMBeanAttributeInfo) ai[i]).getDescriptor(); filter.enableAttribute((String) d.getFieldValue("name")); } } getAttributeChangeBroadcaster().removeNotificationListener(listener, filter, handback); Logger logger = getLogger(); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Listener " + listener + " for attribute " + attributeName + " removed successfully, handback is " + handback); } public void removeNotificationListener(NotificationListener listener) throws RuntimeOperationsException, ListenerNotFoundException { m_generalBroadcaster.removeNotificationListener(listener); } public void removeNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) throws RuntimeOperationsException, ListenerNotFoundException { m_generalBroadcaster.removeNotificationListener(listener, filter, handback); } public void sendAttributeChangeNotification(Attribute oldAttribute, Attribute newAttribute) throws MBeanException, RuntimeOperationsException { if (oldAttribute == null || newAttribute == null) throw new RuntimeOperationsException(new IllegalArgumentException( LocalizedStrings.MX4JModelMBean_ATTRIBUTE_CANNOT_BE_NULL.toLocalizedString())); if (!oldAttribute.getName().equals(newAttribute.getName())) throw new RuntimeOperationsException(new IllegalArgumentException( LocalizedStrings.MX4JModelMBean_ATTRIBUTE_NAMES_CANNOT_BE_DIFFERENT.toLocalizedString())); // TODO: the source must be the object name of the MBean if the listener was registered through // MBeanServer Object oldValue = oldAttribute.getValue(); AttributeChangeNotification n = new AttributeChangeNotification(this, 1, System.currentTimeMillis(), "Attribute value changed", oldAttribute.getName(), oldValue == null ? null : oldValue.getClass().getName(), oldValue, newAttribute.getValue()); sendAttributeChangeNotification(n); } public void sendAttributeChangeNotification(AttributeChangeNotification notification) throws MBeanException, RuntimeOperationsException { if (notification == null) throw new RuntimeOperationsException(new IllegalArgumentException( LocalizedStrings.MX4JModelMBean_NOTIFICATION_CANNOT_BE_NULL.toLocalizedString())); getAttributeChangeBroadcaster().sendNotification(notification); Logger modelMBeanLogger = getModelMBeanLogger(notification.getType()); if (modelMBeanLogger != null) if (modelMBeanLogger.isEnabledFor(Logger.DEBUG)) modelMBeanLogger.debug("ModelMBean log: " + new Date() + " - " + notification); Logger logger = getLogger(); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Attribute change notification " + notification + " sent"); } public void sendNotification(String message) throws MBeanException, RuntimeOperationsException { Notification notification = new Notification("jmx.modelmbean.general", this, 1, message); sendNotification(notification); } public void sendNotification(Notification notification) throws MBeanException, RuntimeOperationsException { if (m_generalBroadcaster != null) { m_generalBroadcaster.sendNotification(notification); } } public AttributeList getAttributes(String[] attributes) { if (attributes == null) throw new RuntimeOperationsException(new IllegalArgumentException( LocalizedStrings.MX4JModelMBean_ATTRIBUTE_NAMES_CANNOT_BE_NULL.toLocalizedString())); Logger logger = getLogger(); AttributeList list = new AttributeList(); for (int i = 0; i < attributes.length; ++i) { String attrName = attributes[i]; Attribute attribute = null; try { Object value = getAttribute(attrName); attribute = new Attribute(attrName, value); list.add(attribute); } catch (Exception x) { if (logger.isEnabledFor(Logger.TRACE)) logger.trace("getAttribute for attribute " + attrName + " failed", x); // And go on with the next attribute } } return list; } public Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException { if (attribute == null) throw new RuntimeOperationsException(new IllegalArgumentException( LocalizedStrings.MX4JModelMBean_ATTRIBUTE_NAME_CANNOT_BE_NULL.toLocalizedString())); Logger logger = getLogger(); // I want the real info, not its clone ModelMBeanInfo info = getModelMBeanInfo(); if (info == null) throw new AttributeNotFoundException( LocalizedStrings.MX4JModelMBean_MODELMBEANINFO_IS_NULL.toLocalizedString()); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("ModelMBeanInfo is: " + info); // This is a clone, we use it read only ModelMBeanAttributeInfo attrInfo = info.getAttribute(attribute); if (attrInfo == null) throw new AttributeNotFoundException( LocalizedStrings.MX4JModelMBean_CANNOT_FIND_MODELMBEANATTRIBUTEINFO_FOR_ATTRIBUTE_0 .toLocalizedString(attribute)); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Attribute info is: " + attrInfo); if (!attrInfo.isReadable()) throw new AttributeNotFoundException( LocalizedStrings.MX4JModelMBean_ATTRIBUTE_0_IS_NOT_READABLE.toLocalizedString(attribute)); // This returns a clone of the mbean descriptor, we use it read only Descriptor mbeanDescriptor = info.getMBeanDescriptor(); if (mbeanDescriptor == null) throw new AttributeNotFoundException( LocalizedStrings.MX4JModelMBean_MBEAN_DESCRIPTOR_CANNOT_BE_NULL.toLocalizedString()); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("MBean descriptor is: " + mbeanDescriptor); // This descriptor is a clone Descriptor attributeDescriptor = attrInfo.getDescriptor(); if (attributeDescriptor == null) throw new AttributeNotFoundException( LocalizedStrings.MX4JModelMBean_ATTRIBUTE_DESCRIPTOR_FOR_ATTRIBUTE_0_CANNOT_BE_NULL .toLocalizedString(attribute)); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Attribute descriptor is: " + attributeDescriptor); Object returnValue = null; String lastUpdateField = "lastUpdatedTimeStamp"; int staleness = getStaleness(attributeDescriptor, mbeanDescriptor, lastUpdateField); if (staleness == ALWAYS_STALE || staleness == STALE) { if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Value is stale"); String getter = (String) attributeDescriptor.getFieldValue("getMethod"); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("getMethod field is: " + getter); if (getter == null) { // No getter, use default value returnValue = attributeDescriptor.getFieldValue("default"); if (returnValue != null) { // Check if the return type is of the same type // As an extension allow covariant return type Class returned = returnValue.getClass(); Class declared = loadClassWithContextClassLoader(attrInfo.getType()); checkAssignability(returned, declared); } if (logger.isEnabledFor(Logger.DEBUG)) logger.debug( "getAttribute for attribute " + attribute + " returns default value: " + returnValue); } else { if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Invoking attribute getter..."); // As an extension, allow attributes to be called on target objects also Object target = resolveTargetObject(attributeDescriptor); returnValue = invokeMethod(target, getter, new Class[0], new Object[0]); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Returned value is: " + returnValue); if (returnValue != null) { // Check if the return type is of the same type // As an extension allow covariant return type Class returned = returnValue.getClass(); Class declared = loadClassWithContextClassLoader(attrInfo.getType()); checkAssignability(returned, declared); } // Cache the new value only if caching is needed if (staleness != ALWAYS_STALE) { attributeDescriptor.setField("value", returnValue); attributeDescriptor.setField(lastUpdateField, Long.valueOf(System.currentTimeMillis())); if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Returned value has been cached"); // And now replace the descriptor with the updated clone info.setDescriptor(attributeDescriptor, "attribute"); } if (logger.isEnabledFor(Logger.DEBUG)) logger.debug( "getAttribute for attribute " + attribute + " returns invoked value: " + returnValue); } } else { // Return cached value returnValue = attributeDescriptor.getFieldValue("value"); if (returnValue != null) { // Check if the return type is of the same type // As an extension allow covariant return type Class returned = returnValue.getClass(); Class declared = loadClassWithContextClassLoader(attrInfo.getType()); checkAssignability(returned, declared); } if (logger.isEnabledFor(Logger.DEBUG)) logger.debug( "getAttribute for attribute " + attribute + " returns cached value: " + returnValue); } // Puff, everything went ok return returnValue; } public AttributeList setAttributes(AttributeList attributes) { if (attributes == null) throw new RuntimeOperationsException(new IllegalArgumentException( LocalizedStrings.MX4JModelMBean_ATTRIBUTE_LIST_CANNOT_BE_NULL.toLocalizedString())); Logger logger = getLogger(); AttributeList list = new AttributeList(); for (Iterator i = attributes.iterator(); i.hasNext();) { Attribute attribute = (Attribute) i.next(); String name = attribute.getName(); try { setAttribute(attribute); list.add(attribute); } catch (Exception x) { if (logger.isEnabledFor(Logger.TRACE)) logger.trace("setAttribute for attribute " + name + " failed", x); // And go on with the next one } } return list; } public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException { if (attribute == null) throw new RuntimeOperationsException(new IllegalArgumentException( LocalizedStrings.MX4JModelMBean_ATTRIBUTE_CANNOT_BE_NULL.toLocalizedString())); Logger logger = getLogger(); // No need to synchronize: I work mostly on clones // I want the real info, not its clone ModelMBeanInfo info = getModelMBeanInfo(); if (info == null) throw new AttributeNotFoundException( LocalizedStrings.MX4JModelMBean_MODELMBEANINFO_IS_NULL.toLocalizedString()); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("ModelMBeanInfo is: " + info); String attrName = attribute.getName(); Object attrValue = attribute.getValue(); // This is a clone, we use it read only ModelMBeanAttributeInfo attrInfo = info.getAttribute(attrName); if (attrInfo == null) throw new AttributeNotFoundException( LocalizedStrings.MX4JModelMBean_CANNOT_FIND_MODELMBEANATTRIBUTEINFO_FOR_ATTRIBUTE_0 .toLocalizedString(attrName)); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Attribute info is: " + attrInfo); if (!attrInfo.isWritable()) throw new AttributeNotFoundException( LocalizedStrings.MX4JModelMBean_ATTRIBUTE_0_IS_NOT_WRITABLE.toLocalizedString(attrName)); // This returns a clone of the mbean descriptor, we use it read only Descriptor mbeanDescriptor = info.getMBeanDescriptor(); if (mbeanDescriptor == null) throw new AttributeNotFoundException( LocalizedStrings.MX4JModelMBean_MBEAN_DESCRIPTOR_CANNOT_BE_NULL.toLocalizedString()); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("MBean descriptor is: " + mbeanDescriptor); // This descriptor is a clone Descriptor attributeDescriptor = attrInfo.getDescriptor(); if (attributeDescriptor == null) throw new AttributeNotFoundException( LocalizedStrings.MX4JModelMBean_ATTRIBUTE_DESCRIPTOR_FOR_ATTRIBUTE_0_CANNOT_BE_NULL .toLocalizedString(attrName)); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Attribute descriptor is: " + attributeDescriptor); String lastUpdateField = "lastUpdatedTimeStamp"; Object oldValue = null; try { oldValue = getAttribute(attrName); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Previous value of attribute " + attrName + ": " + oldValue); } catch (Exception x) { if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Cannot get previous value of attribute " + attrName, x); } // Check if setMethod is present String method = (String) attributeDescriptor.getFieldValue("setMethod"); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("setMethod field is: " + method); if (method != null) { Class declared = loadClassWithContextClassLoader(attrInfo.getType()); if (attrValue != null) { Class parameter = attrValue.getClass(); checkAssignability(parameter, declared); } // As an extension, allow attributes to be called on target objects also Object target = resolveTargetObject(attributeDescriptor); invokeMethod(target, method, new Class[] {declared}, new Object[] {attrValue}); // Cache the value only if currencyTimeLimit is not 0, ie it is not always stale int staleness = getStaleness(attributeDescriptor, mbeanDescriptor, lastUpdateField); if (staleness != ALWAYS_STALE) { attributeDescriptor.setField("value", attrValue); attributeDescriptor.setField(lastUpdateField, Long.valueOf(System.currentTimeMillis())); if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Attribute's value has been cached"); } else { if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Always stale, avoiding to cache attribute's value"); } } else { if (attrValue != null) { Class parameter = attrValue.getClass(); Class declared = loadClassWithContextClassLoader(attrInfo.getType()); checkAssignability(parameter, declared); } // Always store the value in the descriptor: no setMethod attributeDescriptor.setField("value", attrValue); } // And now replace the descriptor with the updated clone info.setDescriptor(attributeDescriptor, "attribute"); // Send notifications to listeners if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Sending attribute change notifications"); sendAttributeChangeNotification(new Attribute(attrName, oldValue), attribute); // Persist this ModelMBean boolean persistNow = shouldPersistNow(attributeDescriptor, mbeanDescriptor, lastUpdateField); if (persistNow) { if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Persisting this ModelMBean..."); try { store(); if (logger.isEnabledFor(Logger.TRACE)) logger.trace("ModelMBean persisted successfully"); } catch (Exception x) { logger.error(LocalizedStrings.MX4JModelMBean_CANNOT_STORE_MODELMBEAN_AFTER_SETATTRIBUTE, x); if (x instanceof MBeanException) throw (MBeanException) x; else throw new MBeanException(x); } } } public Object invoke(String method, Object[] arguments, String[] params) throws MBeanException, ReflectionException { if (method == null) throw new RuntimeOperationsException(new IllegalArgumentException( LocalizedStrings.MX4JModelMBean_METHOD_NAME_CANNOT_BE_NULL.toLocalizedString())); if (arguments == null) arguments = new Object[0]; if (params == null) params = new String[0]; Logger logger = getLogger(); // Find operation descriptor ModelMBeanInfo info = getModelMBeanInfo(); if (info == null) throw new MBeanException(new ServiceNotFoundException( LocalizedStrings.MX4JModelMBean_MODELMBEANINFO_IS_NULL.toLocalizedString())); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("ModelMBeanInfo is: " + info); // This is a clone, we use it read only ModelMBeanOperationInfo operInfo = info.getOperation(method); if (operInfo == null) throw new MBeanException(new ServiceNotFoundException( LocalizedStrings.MX4JModelMBean_CANNOT_FIND_MODELMBEANOPERATIONINFO_FOR_OPERATION_0 .toLocalizedString(method))); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Operation info is: " + operInfo); // This descriptor is a clone Descriptor operationDescriptor = operInfo.getDescriptor(); if (operationDescriptor == null) throw new MBeanException(new ServiceNotFoundException( LocalizedStrings.MX4JModelMBean_OPERATION_DESCRIPTOR_FOR_OPERATION_0_CANNOT_BE_NULL .toLocalizedString(method))); String role = (String) operationDescriptor.getFieldValue("role"); if (role == null || !role.equals("operation")) throw new MBeanException(new ServiceNotFoundException( LocalizedStrings.MX4JModelMBean_OPERATION_DESCRIPTOR_FIELD_ROLE_MUST_BE_OPERATION_NOT_0 .toLocalizedString(role))); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Operation descriptor is: " + operationDescriptor); // This returns a clone of the mbean descriptor, we use it read only Descriptor mbeanDescriptor = info.getMBeanDescriptor(); if (mbeanDescriptor == null) throw new MBeanException(new ServiceNotFoundException( LocalizedStrings.MX4JModelMBean_MBEAN_DESCRIPTOR_CANNOT_BE_NULL.toLocalizedString())); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("MBean descriptor is: " + mbeanDescriptor); Object returnValue = null; String lastUpdateField = "lastReturnedTimeStamp"; // Check if the method should be invoked given the cache settings int staleness = getStaleness(operationDescriptor, mbeanDescriptor, lastUpdateField); if (staleness == ALWAYS_STALE || staleness == STALE) { if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Value is stale"); // Find parameters classes Class[] parameters = null; try { parameters = Utils.loadClasses(Thread.currentThread().getContextClassLoader(), params); } catch (ClassNotFoundException x) { logger.error(LocalizedStrings.MX4JModelMBean_CANNOT_FIND_OPERATIONS_PARAMETER_CLASSES, x); throw new ReflectionException(x); } if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Invoking operation..."); // Find target object Object target = resolveTargetObject(operationDescriptor); returnValue = invokeMethod(target, method, parameters, arguments); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Returned value is: " + returnValue); if (returnValue != null) { Class parameter = returnValue.getClass(); Class declared = loadClassWithContextClassLoader(operInfo.getReturnType()); checkAssignability(parameter, declared); } // Cache the new value only if caching is needed if (staleness != ALWAYS_STALE) { operationDescriptor.setField("lastReturnedValue", returnValue); operationDescriptor.setField(lastUpdateField, Long.valueOf(System.currentTimeMillis())); if (logger.isEnabledFor(Logger.TRACE)) { logger.trace("Returned value has been cached"); } // And now replace the descriptor with the updated clone info.setDescriptor(operationDescriptor, "operation"); } if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("invoke for operation " + method + " returns invoked value: " + returnValue); } else { // Return cached value returnValue = operationDescriptor.getFieldValue("lastReturnedValue"); if (returnValue != null) { Class parameter = returnValue.getClass(); Class declared = loadClassWithContextClassLoader(operInfo.getReturnType()); checkAssignability(parameter, declared); } if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("invoke for operation " + method + " returns cached value: " + returnValue); } // As an extension, persist this model mbean also after operation invocation, but using only // settings provided in the operation descriptor, without falling back to defaults set in // the MBean descriptor boolean persistNow = shouldPersistNow(operationDescriptor, null, lastUpdateField); int impact = operInfo.getImpact(); if (persistNow && impact != MBeanOperationInfo.INFO) { if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Persisting this ModelMBean..."); try { store(); if (logger.isEnabledFor(Logger.TRACE)) logger.trace("ModelMBean persisted successfully"); } catch (Exception x) { logger.error( LocalizedStrings.MX4JModelMBean_CANNOT_STORE_MODELMBEAN_AFTER_OPERATION_INVOCATION, x); if (x instanceof MBeanException) throw (MBeanException) x; else throw new MBeanException(x); } } return returnValue; } private Object resolveTargetObject(Descriptor descriptor) throws MBeanException { Logger logger = getLogger(); Object target = descriptor.getFieldValue("targetObject"); if (logger.isEnabledFor(Logger.TRACE)) logger.trace("targetObject is: " + target); if (target == null) { target = getManagedResource(); } else { String targetObjectType = (String) descriptor.getFieldValue("targetObjectType"); if (logger.isEnabledFor(Logger.TRACE)) logger.trace("targetObjectType is: " + targetObjectType); if (targetObjectType == null) { // Not defined, assume object reference targetObjectType = OBJECT_RESOURCE_TYPE; } if (!isResourceTypeSupported(targetObjectType)) throw new MBeanException(new InvalidTargetObjectTypeException(targetObjectType)); } return target; } public void load() throws MBeanException, RuntimeOperationsException, InstanceNotFoundException { PersisterMBean persister = findPersister(); if (persister != null) { ModelMBeanInfo info = (ModelMBeanInfo) persister.load(); setModelMBeanInfo(info); } } public void store() throws MBeanException, RuntimeOperationsException, InstanceNotFoundException { PersisterMBean persister = findPersister(); if (persister != null) { // Take a clone to avoid synchronization problems ModelMBeanInfo info = (ModelMBeanInfo) getMBeanInfo(); persister.store(info); } } protected ClassLoaderRepository getClassLoaderRepository() { if (m_mbeanServer != null) return m_mbeanServer.getClassLoaderRepository(); else return null; } private boolean shouldPersistNow(Descriptor attribute, Descriptor mbean, String lastUpdateField) { int persist = getPersistPolicy(attribute, mbean); if (persist == PERSIST_NO_MORE_OFTEN_THAN) { Long period = getFieldTimeValue(attribute, mbean, "persistPeriod"); long now = System.currentTimeMillis(); Long lastUpdate = (Long) attribute.getFieldValue(lastUpdateField); if (now - lastUpdate.longValue() < period.longValue()) return false; else return true; } else if (persist == PERSIST_NEVER) { return false; } else if (persist == PERSIST_ON_TIMER) { return false; } else if (persist == PERSIST_ON_UPDATE) { return true; } else { throw new ImplementationException( LocalizedStrings.MX4JModelMBean_INVALID_PERSIST_VALUE.toLocalizedString()); } } private int getPersistPolicy(Descriptor descriptor, Descriptor mbean) { Logger logger = getLogger(); String persist = (String) descriptor.getFieldValue("persistPolicy"); if (persist == null && mbean != null) persist = (String) mbean.getFieldValue("persistPolicy"); if (persist == null) { if (logger.isEnabledFor(Logger.TRACE)) logger.trace("No persist policy defined, assuming Never"); return PERSIST_NEVER; } else { if (persist.equals("Never")) { if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Persist never"); return PERSIST_NEVER; } else if (persist.equals("OnUpdate")) { if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Persist on update"); return PERSIST_ON_UPDATE; } else if (persist.equals("OnTimer")) { if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Persist on update"); return PERSIST_ON_TIMER; } else if (persist.equals("NoMoreOftenThan")) { if (logger.isEnabledFor(Logger.TRACE)) { Long period = getFieldTimeValue(descriptor, mbean, "persistPeriod"); logger.trace("Persist no more often than " + period); } return PERSIST_NO_MORE_OFTEN_THAN; } else { // Garbage, assuming Never if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Invalid persist policy, assuming persist never"); return PERSIST_NEVER; } } } private int getStaleness(Descriptor attribute, Descriptor mbean, String lastUpdateField) { Logger logger = getLogger(); Long currencyTimeLimit = getFieldTimeValue(attribute, mbean, "currencyTimeLimit"); if (currencyTimeLimit == null) { // No time limit defined if (logger.isEnabledFor(Logger.TRACE)) logger.trace("No currencyTimeLimit defined, assuming always stale"); return ALWAYS_STALE; } else { long ctl = currencyTimeLimit.longValue() * 1000; if (logger.isEnabledFor(Logger.TRACE)) logger.trace("currencyTimeLimit is (ms): " + ctl); if (ctl == 0) { // Never stale if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Never stale"); return NEVER_STALE; } else if (ctl < 0) // this should be == -1 but the other cases are in the air { // Always stale if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Always stale"); return ALWAYS_STALE; } else { Long timestamp = (Long) attribute.getFieldValue(lastUpdateField); long luts = 0; if (timestamp != null) luts = timestamp.longValue(); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug(lastUpdateField + " is: " + luts); long now = System.currentTimeMillis(); if (now < luts + ctl) { // Seems to be not stale, but has been set at least once ? if (timestamp == null) { // Return stale to call it the first time if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Stale since was never set"); return STALE; } else { if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Not stale"); return NOT_STALE; } } else { // Stale if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Stale"); return STALE; } } } } private Long getFieldTimeValue(Descriptor descriptor, Descriptor mbean, String field) { Logger logger = getLogger(); Object value = descriptor.getFieldValue(field); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Descriptor's " + field + " field: " + value); if (value == null && mbean != null) { value = mbean.getFieldValue(field); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("MBean's " + field + " field: " + value); if (value == null) return null; } if (value instanceof Number) return Long.valueOf(((Number) value).longValue()); if (value instanceof String) { try { long ctl = Long.parseLong((String) value); return Long.valueOf(ctl); } catch (NumberFormatException x) { return Long.valueOf(0); } } return Long.valueOf(0); } private Object invokeMethod(Object target, String methodName, Class[] params, Object[] args) throws MBeanException, ReflectionException { // First try on this instance, then on the target Object realTarget = null; Method method = null; try { realTarget = this; method = realTarget.getClass().getMethod(methodName, params); } catch (NoSuchMethodException x) { realTarget = target; } if (realTarget == null) throw new MBeanException(new ServiceNotFoundException( LocalizedStrings.MX4JModelMBean_COULD_NOT_FIND_TARGET.toLocalizedString())); if (method == null) { try { method = realTarget.getClass().getMethod(methodName, params); } catch (NoSuchMethodException x) { throw new ReflectionException(x); } } try { Object value = method.invoke(realTarget, args); Logger logger = getLogger(); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Method invocation returned value: " + value); return value; } catch (IllegalAccessException x) { throw new ReflectionException(x); } catch (IllegalArgumentException x) { throw new MBeanException(x); } catch (InvocationTargetException x) { Throwable t = x.getTargetException(); if (t instanceof Error) throw new MBeanException(new RuntimeErrorException((Error) t)); else throw new MBeanException((Exception) t); } } private Logger getModelMBeanLogger(String notificationType) throws MBeanException { // Get a copy to avoid synchronization ModelMBeanInfo info = getModelMBeanInfo(); // First look if there is a suitable notification descriptor, otherwise use MBean descriptor Descriptor descriptor = null; Logger modelMBeanLogger = null; if (notificationType != null) { descriptor = info.getDescriptor(notificationType, "notification"); modelMBeanLogger = findLogger(descriptor); } if (modelMBeanLogger == null) { descriptor = info.getMBeanDescriptor(); modelMBeanLogger = findLogger(descriptor); if (modelMBeanLogger != null) return modelMBeanLogger; } return null; } private Logger findLogger(Descriptor descriptor) { Logger logger = getLogger(); if (descriptor == null) { if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Can't find MBean logger, descriptor is null"); return null; } String log = (String) descriptor.getFieldValue("log"); String location = (String) descriptor.getFieldValue("logFile"); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Log fields: log=" + log + ", file=" + location); if (log == null || !Boolean.valueOf(log).booleanValue()) { if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Logging is not supported by this ModelMBean"); return null; } // Logger is supported, where log to ? if (location == null) { // As an extension, see if the field logMBean has been defined location = (String) descriptor.getFieldValue("logMBean"); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Log fields: mbean=" + location); if (location == null) { if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Logging is not supported by this ModelMBean"); return null; } // It seems that the user wants to delegate a registered mbean to log try { ObjectName objectName = new ObjectName(location); MBeanServer server = getMBeanServer(); if (server == null) throw new MBeanException(new IllegalStateException( LocalizedStrings.MX4JModelMBean_MX4JMODELMBEAN_IS_NOT_REGISTERED .toLocalizedString())); if (server.isRegistered(objectName)) { MBeanLogger l = new MBeanLogger(server, objectName); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("ModelMBean log supported by delegating to this MBean: " + objectName); return l; } return null; } catch (MalformedObjectNameException x) { // Ah, was not a correct object name if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Specified logMBean field does not contain a valid ObjectName: " + location); return null; } catch (MBeanException x) { if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("logMBean field does not specify an MBean that supports logging delegation", x); return null; } } else { // User decided to log to a file if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("ModelMBean log supported on file system"); return new FileLogger(location); } } private NotificationBroadcasterSupport getAttributeChangeBroadcaster() { return m_attributeChangeBroadcaster; } private MBeanServer getMBeanServer() { return m_mbeanServer; } private ModelMBeanInfo getModelMBeanInfo() { // No cloning performed return m_modelMBeanInfo; } private PersisterMBean findPersister() throws MBeanException, InstanceNotFoundException { Logger logger = getLogger(); ModelMBeanInfo info = getModelMBeanInfo(); if (info == null) { // Not yet initialized if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Can't find persister, ModelMBeanInfo is null"); return null; } Descriptor mbeanDescriptor = info.getMBeanDescriptor(); if (mbeanDescriptor == null) { // This is normally should not happen if ModelMBeanInfoSupport is used if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Can't find persister, MBean descriptor is null"); return null; } String location = (String) mbeanDescriptor.getFieldValue("persistLocation"); String name = (String) mbeanDescriptor.getFieldValue("persistName"); String mbeanName = (String) mbeanDescriptor.getFieldValue("name"); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Persistence fields: location=" + location + ", name=" + name); if (mbeanName == null && name == null) { if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Persistence is not supported by this ModelMBean"); return null; } // Try to see if this mbean should delegate to another mbean if (name != null) { try { ObjectName objectName = new ObjectName(name.trim()); // OK, a valid object name MBeanServer server = getMBeanServer(); if (server == null) throw new MBeanException(new IllegalStateException( LocalizedStrings.MX4JModelMBean_MX4JMODELMBEAN_IS_NOT_REGISTERED .toLocalizedString())); if (server.isRegistered(objectName) && server.isInstanceOf(objectName, PersisterMBean.class.getName())) { // OK, the given mbean is registered with this mbean server PersisterMBean persister = new MBeanPersister(server, objectName); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Persistence is delegated to this MBean: " + objectName); return persister; } else { throw new InstanceNotFoundException(objectName.toString()); } } catch (MalformedObjectNameException ignored) { // It does not delegates to another mbean, use default if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Persistence is not delegated to another MBean"); } // Default is serialization to file FilePersister persister = new FilePersister(location, name); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Persistence is realized through file system in " + persister.getFileName()); return persister; } else { // Only location given, use MBean name FilePersister persister = new FilePersister(location, mbeanName); if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Persistence is realized through file system in " + persister.getFileName()); return persister; } } private Class loadClassWithContextClassLoader(String name) { try { return Utils.loadClass(Thread.currentThread().getContextClassLoader(), name); } catch (ClassNotFoundException x) { Logger logger = getLogger(); if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Cannot find attribute's declared return class", x); return null; } } private void checkAssignability(Class parameter, Class declared) throws MBeanException { Logger logger = getLogger(); if (logger.isEnabledFor(Logger.DEBUG)) { logger.debug("The class of the parameter is: " + parameter); if (parameter != null) logger.debug("The classloder of the parameter's class is: " + parameter.getClassLoader()); logger.debug("The class declared as type of the attribute is: " + declared); if (declared != null) logger.debug( "The classloader of the declared parameter's class is: " + declared.getClassLoader()); } boolean assignable = false; if (declared == null || parameter == null) assignable = false; else if (declared == boolean.class && parameter == Boolean.class) assignable = true; else if (declared == byte.class && parameter == Byte.class) assignable = true; else if (declared == char.class && parameter == Character.class) assignable = true; else if (declared == short.class && parameter == Short.class) assignable = true; else if (declared == int.class && parameter == Integer.class) assignable = true; else if (declared == long.class && parameter == Long.class) assignable = true; else if (declared == float.class && parameter == Float.class) assignable = true; else if (declared == double.class && parameter == Double.class) assignable = true; else assignable = declared.isAssignableFrom(parameter); if (!assignable) { if (logger.isEnabledFor(Logger.TRACE)) logger.trace( "Parameter value's class and attribute's declared return class are not assignable"); throw new MBeanException(new InvalidAttributeValueException( LocalizedStrings.MX4JModelMBean_RETURNED_TYPE_AND_DECLARED_TYPE_ARE_NOT_ASSIGNABLE .toLocalizedString())); } } }