@Override
  public void setAttribute(Attribute attribute) throws AttributeNotFoundException,
      InvalidAttributeValueException, MBeanException, ReflectionException {
    if (attribute == null) {
      throw new RuntimeOperationsException(new IllegalArgumentException(
          "Attribute cannot be null."));
    }

    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(
          "ModelMBeanInfo is null");
    }
    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(
          String.format("Cannot find ModelMBeanAttributeInfo for attribute %s",
              attrName));
    }
    if (logger.isEnabledFor(Logger.DEBUG)) {
      logger.debug("Attribute info is: " + attrInfo);
    }

    if (!attrInfo.isWritable()) {
      throw new AttributeNotFoundException(
          String.format("Attribute %s is not writable", attrName));
    }

    // This returns a clone of the mbean descriptor, we use it read only
    Descriptor mbeanDescriptor = info.getMBeanDescriptor();
    if (mbeanDescriptor == null) {
      throw new AttributeNotFoundException(
          "MBean descriptor cannot be null");
    }
    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(
          String.format("Attribute descriptor for attribute %s cannot be null",
              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, 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("Cannot store ModelMBean after setAttribute", x);
        if (x instanceof MBeanException) {
          throw (MBeanException) x;
        } else {
          throw new MBeanException(x);
        }
      }
    }
  }
@author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a>
@version $Revision: 1.14 $
geode-core/src/main/java/org/apache/geode/admin/jmx/internal/MX4JModelMBean.java