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