@Override
  public Object invoke(String method, Object[] arguments, String[] params)
      throws MBeanException, ReflectionException {
    if (method == null) {
      throw new RuntimeOperationsException(new IllegalArgumentException(
          "Method name cannot be null."));
    }
    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(
          "ModelMBeanInfo is null"));
    }
    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(
          String.format("Cannot find ModelMBeanOperationInfo for operation %s",
              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(
          String.format("Operation descriptor for operation %s cannot be null",
              method)));
    }
    String role = (String) operationDescriptor.getFieldValue("role");
    if (role == null || !role.equals("operation")) {
      throw new MBeanException(new ServiceNotFoundException(
          String.format("Operation descriptor field 'role' must be 'operation', not %s",
              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(
          "MBean descriptor cannot be null"));
    }
    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("Cannot find operation's 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, 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(
            "Cannot store ModelMBean after operation invocation", x);
        if (x instanceof MBeanException) {
          throw (MBeanException) x;
        } else {
          throw new MBeanException(x);
        }
      }
    }

    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