@Test
  public void testNickNameIsExposed() throws Exception {
    ModelMBeanInfo inf = (ModelMBeanInfo) getMBeanInfo();
    MBeanAttributeInfo attr = inf.getAttribute("NickName");

    assertNickName(attr);
  }
  @Test
  public void testGetAgeIsReadOnly() throws Exception {
    ModelMBeanInfo info = getMBeanInfoFromAssembler();
    ModelMBeanAttributeInfo attr = info.getAttribute(AGE_ATTRIBUTE);

    assertTrue("Age is not readable", attr.isReadable());
    assertFalse("Age is not writable", attr.isWritable());
  }
  public void testAttributeInfoHasDescriptors() throws Exception {
    ModelMBeanInfo info = getMBeanInfoFromAssembler();

    ModelMBeanAttributeInfo attr = info.getAttribute(NAME_ATTRIBUTE);
    Descriptor desc = attr.getDescriptor();
    assertNotNull("getMethod field should not be null", desc.getFieldValue("getMethod"));
    assertNotNull("setMethod field should not be null", desc.getFieldValue("setMethod"));
    assertEquals("getMethod field has incorrect value", "getName", desc.getFieldValue("getMethod"));
    assertEquals("setMethod field has incorrect value", "setName", desc.getFieldValue("setMethod"));
  }
  @Test
  public void testWithFallThrough() throws Exception {
    MethodNameBasedMBeanInfoAssembler assembler =
        getWithMapping("foobar", "add,myOperation,getName,setName,getAge");
    assembler.setManagedMethods(new String[] {"getNickName", "setNickName"});

    ModelMBeanInfo inf = assembler.getMBeanInfo(getBean(), getObjectName());
    MBeanAttributeInfo attr = inf.getAttribute("NickName");

    assertNickName(attr);
  }
  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 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;
  }