http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/c0221bed/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/MX4JModelMBean.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/MX4JModelMBean.java b/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/MX4JModelMBean.java new file mode 100755 index 0000000..cb2152a --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/MX4JModelMBean.java @@ -0,0 +1,1239 @@ +/* + * 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.internal.admin.api.jmx.impl; + +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_stee...@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())); + } + } +}
http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/c0221bed/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/MX4JServerSocketFactory.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/MX4JServerSocketFactory.java b/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/MX4JServerSocketFactory.java new file mode 100644 index 0000000..15a0c27 --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/MX4JServerSocketFactory.java @@ -0,0 +1,160 @@ +/* + * 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.internal.admin.api.jmx.impl; + +import org.apache.geode.internal.admin.api.DistributedSystemConfig; +import org.apache.geode.internal.admin.api.impl.InetAddressUtil; +import org.apache.geode.distributed.internal.DistributionConfig; +import org.apache.geode.internal.net.SocketCreator; +import org.apache.geode.internal.logging.LogService; +import org.apache.geode.internal.net.SocketCreatorFactory; + +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.net.ServerSocket; +import java.util.Properties; + +/** + * Creates <code>ServerSockets</code> for JMX adaptors. + * <p> + * The interface {@link mx4j.tools.adaptor.AdaptorServerSocketFactory} is + * implemented in order to support securing of {@link + * mx4j.tools.adaptor.http.HttpAdaptor}. + * <p> + * The interface {@link java.rmi.server.RMIServerSocketFactory} is implemented + * to support the securing of {@link + * javax.management.remote.JMXConnectorServer}. See {@link + * javax.management.remote.rmi.RMIConnectorServer} for the actual subclass that + * is used for the JMX RMI adaptor. + * <p> + * Complete info on JSSE, including debugging, can be found at + * <a href="http://java.sun.com/j2se/1.4.2/docs/guide/security/jsse/JSSERefGuide.html"> + * http://java.sun.com/j2se/1.4.2/docs/guide/security/jsse/JSSERefGuide.html</a> + * + * @since GemFire 3.5 (old name was SSLAdaptorServerSocketFactory) + */ +public class MX4JServerSocketFactory +implements mx4j.tools.adaptor.AdaptorServerSocketFactory, + java.rmi.server.RMIServerSocketFactory { + + private static final Logger logger = LogService.getLogger(); + + private static final int DEFAULT_BACKLOG = 50; + + private final SocketCreator socketCreator; + private String bindAddress = DistributedSystemConfig.DEFAULT_BIND_ADDRESS; + private int backlog = DEFAULT_BACKLOG; + + /** + * Constructs new instance of MX4JServerSocketFactory. + * + * @param useSSL + * true if ssl is to be enabled + * @param needClientAuth + * true if client authentication is required + * @param protocols + * space-delimited list of ssl protocols to use + * @param ciphers + * space-delimited list of ssl ciphers to use + * @param gfsecurityProps + * vendor properties passed in through gfsecurity.properties + */ + public MX4JServerSocketFactory(boolean useSSL, + boolean needClientAuth, + String protocols, + String ciphers, + Properties gfsecurityProps) { + if (protocols == null || protocols.length() == 0) { + protocols = DistributionConfig.DEFAULT_SSL_PROTOCOLS; + } + if (ciphers == null || ciphers.length() == 0) { + ciphers = DistributionConfig.DEFAULT_SSL_CIPHERS; + } + this.socketCreator = SocketCreatorFactory.createNonDefaultInstance( + useSSL, needClientAuth, protocols, ciphers, gfsecurityProps); + } + + /** + * Constructs new instance of MX4JServerSocketFactory. + * + * @param useSSL + * true if ssl is to be enabled + * @param needClientAuth + * true if client authentication is required + * @param protocols + * space-delimited list of ssl protocols to use + * @param ciphers + * space-delimited list of ssl ciphers to use + * @param bindAddress + * host or address to bind to (bind-address) + * @param backlog + * how many connections are queued + * @param gfsecurityProps + * vendor properties passed in through gfsecurity.properties + */ + public MX4JServerSocketFactory(boolean useSSL, + boolean needClientAuth, + String protocols, + String ciphers, + String bindAddress, // optional for RMI impl + int backlog, // optional for RMI impl + Properties gfsecurityProps) { + this(useSSL, needClientAuth, protocols, ciphers, gfsecurityProps); + this.bindAddress = bindAddress; + this.backlog = backlog; + } + + // ------------------------------------------------------------------------- + // mx4j.tools.adaptor.AdaptorServerSocketFactory impl... + // ------------------------------------------------------------------------- + + public ServerSocket createServerSocket(int port, + int backlog, + String bindAddress) throws IOException { + if ("".equals(bindAddress)) { + return socketCreator.createServerSocket( + port, backlog); + + } else { + return socketCreator.createServerSocket( + port, backlog, InetAddressUtil.toInetAddress(bindAddress)); + } + } + + // ------------------------------------------------------------------------- + // java.rmi.server.RMIServerSocketFactory impl... + // ------------------------------------------------------------------------- + + public ServerSocket createServerSocket(int port) throws IOException { + ServerSocket sock = null; + if ("".equals(bindAddress)) { + sock = socketCreator.createServerSocket(port, this.backlog); + } else { + sock = socketCreator.createServerSocket( + port, this.backlog, InetAddressUtil.toInetAddress(this.bindAddress)); + } + + if (logger.isDebugEnabled()) { + logger.debug("MX4JServerSocketFactory RMIServerSocketFactory, INetAddress {}, LocalPort {}, LocalSocketAddress {}", + sock.getInetAddress(), sock.getLocalPort(), sock.getLocalSocketAddress()); + } + return sock; + } + +} + http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/c0221bed/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/MailManager.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/MailManager.java b/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/MailManager.java new file mode 100755 index 0000000..93b19bd --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/MailManager.java @@ -0,0 +1,331 @@ +/* + * 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.internal.admin.api.jmx.impl; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.HashSet; +import java.util.Properties; + +import javax.mail.Message; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; + +import org.apache.logging.log4j.Logger; + +import org.apache.geode.SystemFailure; +import org.apache.geode.internal.i18n.LocalizedStrings; +import org.apache.geode.internal.logging.LogService; +import org.apache.geode.internal.logging.log4j.LocalizedMessage; + +/** + * Provides the ways to send emails to all the registered email id It also + * provides the way to add/remove email ids. Can be used to send email in case + * of any alerts raised / warning / failure in gemfire. + * + * @since GemFire 5.1 + */ +public class MailManager { + + private static final Logger logger = LogService.getLogger(); + + public MailManager() { + } + + public MailManager(Properties mailProperties) { + setMailProperties(mailProperties); + } + + public MailManager(File mailPropertiesFile) throws IOException { + Properties prop = new Properties(); + FileInputStream fio = new FileInputStream(mailPropertiesFile); + try { + prop.load(fio); + } + finally { + fio.close(); + } + setMailProperties(prop); + } + + public MailManager(String mailHost, String mailFrom) { + this.mailHost = mailHost; + this.mailFrom = mailFrom; + } + + /** + * Send email to all the registered email id with given subject and message + */ + public void sendEmail(String subject, String message) { + processEmail(new EmailData(subject, message)); + } + + /** + * Send Emails to all the registered email id + * + * @param emailData + * Instance of EmailData + */ + // Why a separate method & class EmailData needed??? + private void processEmail(EmailData emailData) { + if (logger.isTraceEnabled()) { + logger.trace("Entered MailManager:processEmail"); + } + + if (mailHost == null || mailHost.length() == 0 + || emailData == null || mailToAddresses.length == 0) { + logger.error(LocalizedMessage.create(LocalizedStrings.MailManager_REQUIRED_MAILSERVER_CONFIGURATION_NOT_SPECIFIED)); + if (logger.isDebugEnabled()) { + logger.debug("Exited MailManager:processEmail: Not sending email as conditions not met"); + } + return; + } + + Session session = Session.getDefaultInstance(getMailHostConfiguration()); + MimeMessage mimeMessage = new MimeMessage(session); + String subject = emailData.subject; + String message = emailData.message; + String mailToList = getMailToAddressesAsString(); + + try { + for (int i = 0; i < mailToAddresses.length; i++) { + mimeMessage.addRecipient(Message.RecipientType.TO, new InternetAddress( + mailToAddresses[i])); + } + + if (subject == null) { + subject = LocalizedStrings.MailManager_ALERT_MAIL_SUBJECT.toLocalizedString(); + } + mimeMessage.setSubject(subject); + + if (message == null) { + message = ""; + } + mimeMessage.setText(message); + + Transport.send(mimeMessage); + logger.info(LocalizedMessage.create( + LocalizedStrings.MailManager_EMAIL_ALERT_HAS_BEEN_SENT_0_1_2, + new Object[] { mailToList, subject, message })); + } catch (VirtualMachineError err) { + SystemFailure.initiateFailure(err); + // If this ever returns, rethrow the error. We're poisoned + // now, so don't let this thread continue. + throw err; + } catch (Throwable ex) { + // Whenever you catch Error or Throwable, you must also + // catch VirtualMachineError (see above). However, there is + // _still_ a possibility that you are dealing with a cascading + // error condition, so you also need to check to see if the JVM + // is still usable: + SystemFailure.checkFailure(); + StringBuilder buf = new StringBuilder(); + buf.append(LocalizedStrings.MailManager_AN_EXCEPTION_OCCURRED_WHILE_SENDING_EMAIL.toLocalizedString()); + buf.append(LocalizedStrings.MailManager_UNABLE_TO_SEND_EMAIL_PLEASE_CHECK_YOUR_EMAIL_SETTINGS_AND_LOG_FILE.toLocalizedString()); + buf.append("\n\n").append(LocalizedStrings.MailManager_EXCEPTION_MESSAGE_0.toLocalizedString(ex.getMessage())); + buf.append("\n\n").append(LocalizedStrings.MailManager_FOLLOWING_EMAIL_WAS_NOT_DELIVERED.toLocalizedString()); + buf.append("\n\t").append(LocalizedStrings.MailManager_MAIL_HOST_0.toLocalizedString(mailHost)); + buf.append("\n\t").append(LocalizedStrings.MailManager_FROM_0.toLocalizedString(mailFrom)); + buf.append("\n\t").append(LocalizedStrings.MailManager_TO_0.toLocalizedString(mailToList)); + buf.append("\n\t").append(LocalizedStrings.MailManager_SUBJECT_0.toLocalizedString(subject)); + buf.append("\n\t").append(LocalizedStrings.MailManager_CONTENT_0.toLocalizedString(message)); + + logger.error(buf.toString(), ex); + } + if (logger.isTraceEnabled()) { + logger.trace("Exited MailManager:processEmail"); + } + } + + /** + * Not yet implemented + */ + public void close() { + } + + /** + * @return All the registered email id as string + */ + private String getMailToAddressesAsString() { + StringBuffer mailToList = new StringBuffer(); + for (int i = 0; i < mailToAddresses.length; i++) { + mailToList.append(mailToAddresses[i]); + mailToList.append(", "); + } + return mailToList.toString(); + } + + /** + * + * @return Properties consisting mailHost and mailFrom property + */ + private Properties getMailHostConfiguration() { + Properties result = new Properties(); + if (mailHost == null) { + mailHost = ""; + } + if (mailFrom == null) { + mailFrom = ""; + } + result.setProperty("mail.host", mailHost); + result.put("mail.from", mailFrom); + return result; + } + + /** + * + * @param host + * mail host server name + */ + public void setMailHost(String host) { + this.mailHost = host; + } + + /** + * + * @return mail host server name + */ + public String getMailHost() { + return this.mailHost; + } + + /** + * + * @param fromAddress + * mailFrom email id + */ + public void setMailFromAddress(String fromAddress) { + mailFrom = fromAddress; + } + + /** + * + * @return mailFrom email id + */ + public String getMailFromAddress() { + return mailFrom; + } + + /** + * add new mail id to ToList + */ + public void addMailToAddress(String toAddress) { + mailToSet.add(toAddress); + mailToAddresses = getAllToAddresses(); + } + + /** + * remove given mail id from ToList + */ + public void removeMailToAddress(String toAddress) { + mailToSet.remove(toAddress); + mailToAddresses = getAllToAddresses(); + } + + /** + * @return list all the registered email id + */ + public String[] getAllToAddresses() { + return (String[])mailToSet.toArray(new String[0]); + } + + /** + * remove all the registered email ids from ToList + */ + public void removeAllMailToAddresses() { + mailToSet.clear(); + mailToAddresses = new String[0]; + } + + /** + * Set the mail properties, e.g mail host, mailFrom, MailTo etc + */ + public void setMailProperties(Properties mailProperties) { + mailHost = mailProperties.getProperty(PROPERTY_MAIL_HOST); + mailFrom = mailProperties.getProperty(PROPERTY_MAIL_FROM); + String mailList = mailProperties.getProperty(PROPERTY_MAIL_TO_LIST, ""); + String split[] = mailList.split(","); + removeAllMailToAddresses(); + for (int i = 0; i < split.length; i++) { + addMailToAddress(split[i].trim()); + } + } + + @Override + public String toString() { + StringBuffer buffer = new StringBuffer(200); + buffer.append("[Mail Host: "); + buffer.append(getMailHost()); + buffer.append("]"); + buffer.append(" [Mail From: "); + buffer.append(getMailFromAddress()); + buffer.append("]"); + buffer.append(" [Mail To: "); + if (mailToAddresses.length > 0) { + + for (int i = 0; i < mailToAddresses.length; i++) { + buffer.append(mailToAddresses[i]); + buffer.append(", "); + } + buffer.replace(buffer.length() - 2, buffer.length(), ""); + } + else { + buffer.append(" Undefined"); + } + buffer.append("]"); + return buffer.toString(); + } + + private HashSet mailToSet = new HashSet(); + + private String mailToAddresses[] = new String[0]; + + protected String mailHost; + + protected String mailFrom; + + public final static String PROPERTY_MAIL_HOST = "mail.host"; + + public final static String PROPERTY_MAIL_FROM = "mail.from"; + + public final static String PROPERTY_MAIL_TO_LIST = "mail.toList"; + + /** + * Incorporating subject and message of email + * + * + */ + static private class EmailData { + String subject; + + String message; + + EmailData(String subject, String message) { + this.subject = subject; + this.message = message; + } + } + + public static void main(String args[]) { + MailManager mailManager = new MailManager("mailsrv1.gemstone.com", + "hkha...@gemstone.com"); + mailManager.sendEmail("Alert!", "Test"); + } +} http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/c0221bed/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/ManagedResource.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/ManagedResource.java b/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/ManagedResource.java new file mode 100755 index 0000000..61e76de --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/ManagedResource.java @@ -0,0 +1,77 @@ +/* + * 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.internal.admin.api.jmx.impl; + +import javax.management.ObjectName; +import javax.management.modelmbean.ModelMBean; + +/** + * Represents a component or resource that is managed by a + * {@link javax.management.modelmbean.ModelMBean}. + * + * @since GemFire 3.5 + * + */ +public interface ManagedResource { + + /** + * The prefix of MBean names. Note: this is NOT used by Members, Stats, or + * any other MBean that has it's own domain. + * + * @see #getMBeanName + */ + public static final String MBEAN_NAME_PREFIX = "GemFire:type="; + + /** + * Returns the name of the ModelMBean that will manage this + * resource. They [some] are of the form + * + * <PRE> + * MBEAN_NAME_PREFIX + typeName + ",id=" + id + * </PRE> + * + * @see #MBEAN_NAME_PREFIX + */ + public String getMBeanName(); + + /** Returns the ModelMBean that is configured to manage this resource */ + public ModelMBean getModelMBean(); + + /** Sets the ModelMBean that is configured to manage this resource */ + public void setModelMBean(ModelMBean modelMBean); + + /** + * Returns the enumerated ManagedResourceType of this resource. + * + * @see ManagedResourceType + */ + public ManagedResourceType getManagedResourceType(); + + /** + * Returns the JMX <code>ObjectName</code> of this managed resource. + * + * @see #getMBeanName + */ + public ObjectName getObjectName(); + + /** + * Perform any cleanup necessary before stopping management of this resource. + */ + public void cleanupResource(); + +} + http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/c0221bed/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/ManagedResourceType.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/ManagedResourceType.java b/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/ManagedResourceType.java new file mode 100755 index 0000000..b70cade --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/ManagedResourceType.java @@ -0,0 +1,222 @@ +/* + * 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.internal.admin.api.jmx.impl; + +import org.apache.commons.lang.StringUtils; + +import org.apache.geode.internal.admin.api.AdminDistributedSystem; +import org.apache.geode.internal.admin.api.CacheVm; +import org.apache.geode.internal.admin.api.DistributedSystemHealthConfig; +import org.apache.geode.internal.admin.api.DistributionLocator; +import org.apache.geode.internal.admin.api.GemFireHealth; +import org.apache.geode.internal.admin.api.GemFireHealthConfig; +import org.apache.geode.internal.admin.api.StatisticResource; +import org.apache.geode.internal.admin.api.SystemMember; +import org.apache.geode.internal.admin.api.SystemMemberCache; +import org.apache.geode.internal.admin.api.SystemMemberCacheServer; +import org.apache.geode.internal.admin.api.SystemMemberRegion; +import org.apache.geode.internal.admin.api.jmx.Agent; + +/** + * Type-safe definition for ModelMBean managed resources. The class type + * ({@link #getClassTypeName}) must match the fully qualified class name listed + * in the type descriptor in mbeans-descriptors.xml. + * + * @since GemFire 3.5 + * + */ +public class ManagedResourceType implements java.io.Serializable { + private static final long serialVersionUID = 3752874768667480449L; + + /** Agent managed resource type */ + public static final ManagedResourceType AGENT = + new ManagedResourceType("Agent", Agent.class); + + /** DistributedSystem managed resource type */ + public static final ManagedResourceType DISTRIBUTED_SYSTEM = + new ManagedResourceType("AdminDistributedSystem", AdminDistributedSystem.class); + + /** SystemMember managed resource type */ + public static final ManagedResourceType SYSTEM_MEMBER = + new ManagedResourceType("SystemMember", SystemMember.class); + + /** SystemMemberCache managed resource type */ + public static final ManagedResourceType SYSTEM_MEMBER_CACHE = + new ManagedResourceType("SystemMemberCache", SystemMemberCache.class); + + /** SystemMemberCache managed resource type */ + public static final ManagedResourceType SYSTEM_MEMBER_REGION = + new ManagedResourceType("SystemMemberRegion", SystemMemberRegion.class); + + /** SystemMemberCacheServer managed resource type */ + public static final ManagedResourceType SYSTEM_MEMBER_CACHE_SERVER = + new ManagedResourceType("SystemMemberCacheServer", SystemMemberCacheServer.class); + + /** CacheVm managed resource type */ + public static final ManagedResourceType CACHE_VM = + new ManagedResourceType("CacheVm", CacheVm.class); + + /** StatisticResource managed resource type */ + public static final ManagedResourceType STATISTIC_RESOURCE = + new ManagedResourceType("StatisticResource", StatisticResource.class); + + public static final ManagedResourceType GEMFIRE_HEALTH = + new ManagedResourceType("GemFireHealth", GemFireHealth.class); + + public static final ManagedResourceType DISTRIBUTED_SYSTEM_HEALTH_CONFIG = + new ManagedResourceType("DistributedSystemHealthConfig", DistributedSystemHealthConfig.class); + + public static final ManagedResourceType GEMFIRE_HEALTH_CONFIG = + new ManagedResourceType("GemFireHealthConfig", GemFireHealthConfig.class); + + public static final ManagedResourceType DISTRIBUTION_LOCATOR = + new ManagedResourceType("DistributionLocator", DistributionLocator.class); + + //////////////////// Instance Fields //////////////////// + + /** The display-friendly name of this managed resource type. */ + private final transient String name; + + /** + * The interface/class used to externally represent this type. Note: this must + * match the mbean type descriptor in mbeans-descriptors.xml. + */ + private final transient Class clazz; + + // The 4 declarations below are necessary for serialization + /** int used as ordinal to represent this Scope */ + public final int ordinal = nextOrdinal++; + + private static int nextOrdinal = 0; + + private static final ManagedResourceType[] VALUES = + { AGENT, DISTRIBUTED_SYSTEM, SYSTEM_MEMBER, + SYSTEM_MEMBER_CACHE, SYSTEM_MEMBER_REGION, + SYSTEM_MEMBER_CACHE_SERVER, CACHE_VM, + STATISTIC_RESOURCE, GEMFIRE_HEALTH, DISTRIBUTED_SYSTEM_HEALTH_CONFIG, + GEMFIRE_HEALTH_CONFIG, DISTRIBUTION_LOCATOR + }; + + private Object readResolve() throws java.io.ObjectStreamException { + return VALUES[ordinal]; // Canonicalize + } + + /** Creates a new instance of ManagedResourceType. */ + private ManagedResourceType(String name, Class clazz) { + this.name = name; + this.clazz = clazz; + } + + /** Returns the ManagedResourceType represented by specified ordinal */ + public static ManagedResourceType fromOrdinal(int ordinal) { + return VALUES[ordinal]; + } + + /** Returns the display-friendly name of this managed resource type */ + public String getName() { + return this.name; + } + + /** Returns the interface/class used to externally represent this type */ + public Class getClassType() { + return this.clazz; + } + + /** + * Returns the fully qualified name of the interface/class used to externally + * represent this type + */ + public String getClassTypeName() { + return this.clazz.getName(); + } + + /** Returns true if this is <code>AGENT</code>. */ + public boolean isAgent() { + return this.equals(AGENT); + } + + /** Returns true if this is <code>DISTRIBUTED_SYSTEM</code>. */ + public boolean isDistributedSystem() { + return this.equals(DISTRIBUTED_SYSTEM); + } + + /** Returns true if this is <code>SYSTEM_MEMBER</code>. */ + public boolean isSystemMember() { + return this.equals(SYSTEM_MEMBER); + } + + /** Returns whether this is <code>STATISTIC_RESOURCE</code>. */ + public boolean isStatisticResource() { + return this.equals(STATISTIC_RESOURCE); + } + + /** Return whether this is <code>GEMFIRE_HEALTH</code>. */ + public boolean isGemFireHealth() { + return this.equals(GEMFIRE_HEALTH); + } + + /** + * Returns a string representation for this type. + */ + @Override + public String toString() { + return this.name; + } + + /** + * Indicates whether some other object is "equal to" this one. + * + * @param other the reference object with which to compare. + * @return true if this object is the same as the obj argument; + * false otherwise. + */ + @Override + public boolean equals(Object other) { + if (other == this) return true; + if (other == null) return false; + if (!(other instanceof ManagedResourceType)) return false; + final ManagedResourceType that = (ManagedResourceType) other; + + if (!StringUtils.equals(this.name, that.name)) return false; + if (this.clazz != that.clazz && + !(this.clazz != null && + this.clazz.equals(that.clazz))) return false; + + return true; + } + + /** + * Returns a hash code for the object. This method is supported for the + * benefit of hashtables such as those provided by java.util.Hashtable. + * + * @return the integer 0 if description is null; otherwise a unique integer. + */ + @Override + public int hashCode() { + int result = 17; + final int mult = 37; + + result = mult * result + + (this.name == null ? 0 : this.name.hashCode()); + result = mult * result + + (this.clazz == null ? 0 : this.clazz.hashCode()); + + return result; + } + +} +