@Override
  public Object getAttribute(String attribute)
      throws AttributeNotFoundException, MBeanException, ReflectionException {
    if (attribute == null) {
      throw new RuntimeOperationsException(new IllegalArgumentException(
          "Attribute name cannot be null."));
    }

    Logger logger = getLogger();

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

    // This is a clone, we use it read only
    ModelMBeanAttributeInfo attrInfo = info.getAttribute(attribute);
    if (attrInfo == null) {
      throw new AttributeNotFoundException(
          String.format("Cannot find ModelMBeanAttributeInfo for attribute %s",
              attribute));
    }
    if (logger.isEnabledFor(Logger.DEBUG)) {
      logger.debug("Attribute info is: " + attrInfo);
    }
    if (!attrInfo.isReadable()) {
      throw new AttributeNotFoundException(
          String.format("Attribute %s is not readable", attribute));
    }

    // 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",
              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, 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;
  }
@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