/*
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the License. You may obtain a
 * copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package org.apache.geode.admin.jmx.internal;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.modelmbean.ModelMBean;

import org.apache.commons.modeler.ManagedBean;
import org.apache.logging.log4j.Level;

import org.apache.geode.SystemFailure;
import org.apache.geode.admin.AdminException;
import org.apache.geode.admin.SystemMemberCacheServer;
import org.apache.geode.admin.SystemMemberRegion;
import org.apache.geode.admin.internal.SystemMemberBridgeServerImpl;
import org.apache.geode.cache.Region;
import org.apache.geode.internal.admin.AdminBridgeServer;
import org.apache.geode.internal.admin.GemFireVM;

/**
 * MBean representation of {@link org.apache.geode.admin.SystemMemberCache}.
 *
 * @since GemFire 3.5
 */
@Deprecated
public class SystemMemberCacheJmxImpl extends org.apache.geode.admin.internal.SystemMemberCacheImpl
    implements org.apache.geode.admin.jmx.internal.ManagedResource {

  /** The object name of this managed resource */
  private ObjectName objectName;

  /** collection to collect all the resources created for this member */
  private final Map<String, SystemMemberRegionJmxImpl> managedRegionResourcesMap =
      new HashMap<>();
  private final Map<Number, SystemMemberBridgeServerJmxImpl> managedCacheServerResourcesMap =
      new HashMap<>();

  // -------------------------------------------------------------------------
  // Constructor(s)
  // -------------------------------------------------------------------------

  /**
   * Constructs an instance of SystemMemberCacheJmxImpl.
   *
   * @param vm The vm owning the cache this object will manage
   */
  public SystemMemberCacheJmxImpl(GemFireVM vm) throws org.apache.geode.admin.AdminException {
    super(vm);
    initializeMBean();
  }

  /** Create and register the MBean to manage this resource */
  private void initializeMBean() throws org.apache.geode.admin.AdminException {
    mbeanName = "GemFire.Cache:" + "name="
        + MBeanUtils.makeCompliantMBeanNameProperty(getName()) + ",id=" + getId()
        + ",owner=" + MBeanUtils.makeCompliantMBeanNameProperty(vm.getId().toString())
        + ",type=Cache";

    objectName =
        MBeanUtils.createMBean(this, addDynamicAttributes(MBeanUtils.lookupManagedBean(this)));
  }

  // -------------------------------------------------------------------------
  // Template methods overriden from superclass...
  // -------------------------------------------------------------------------

  /**
   * Override createSystemMemberRegion by instantiating SystemMemberRegionJmxImpl. This instance is
   * also added to the managedResources collection.
   *
   * @param r reference to Region instance for which this JMX resource is to be created
   * @return SystemMemberRegionJmxImpl - JMX Implementation of SystemMemberRegion
   * @throws AdminException if constructing SystemMemberRegionJmxImpl instance fails
   */
  @Override
  protected SystemMemberRegion createSystemMemberRegion(Region r)
      throws org.apache.geode.admin.AdminException {
    SystemMemberRegionJmxImpl managedSystemMemberRegion = null;
    boolean needsRefresh = false;
    synchronized (managedRegionResourcesMap) {
      /*
       * Ensuring that a single instance of System Member Region is created per Region.
       */
      SystemMemberRegionJmxImpl managedResource = managedRegionResourcesMap.get(r.getFullPath());
      if (managedResource != null) {
        managedSystemMemberRegion = managedResource;
      } else {
        managedSystemMemberRegion = new SystemMemberRegionJmxImpl(this, r);
        managedRegionResourcesMap.put(r.getFullPath(), managedSystemMemberRegion);
        needsRefresh = true;
      }
    }
    if (needsRefresh) {
      managedSystemMemberRegion.refresh();
    }
    return managedSystemMemberRegion;
  }

  /**
   * Creates a SystemMemberBridgeServerJmxImpl instance. This instance is also added to the
   * managedResources collection.
   *
   * @param bridge reference to AdminBridgeServer for which this JMX resource is to be created
   * @return SystemMemberBridgeServerJmxImpl - JMX Implementation of SystemMemberBridgeServerImpl
   * @throws AdminException if constructing SystemMemberBridgeServerJmxImpl instance fails
   */
  @Override
  protected SystemMemberBridgeServerImpl createSystemMemberBridgeServer(AdminBridgeServer bridge)
      throws AdminException {
    SystemMemberBridgeServerJmxImpl managedSystemMemberBridgeServer = null;
    synchronized (managedCacheServerResourcesMap) {
      /*
       * Ensuring that a single instance of SystemMember BridgeServer is created per
       * AdminBridgeServer.
       */
      SystemMemberBridgeServerJmxImpl managedCacheServerResource =
          managedCacheServerResourcesMap.get(bridge.getId());
      if (managedCacheServerResource != null) {
        managedSystemMemberBridgeServer = managedCacheServerResource;
      } else {
        managedSystemMemberBridgeServer = new SystemMemberBridgeServerJmxImpl(this, bridge);
        managedCacheServerResourcesMap.put(bridge.getId(), managedSystemMemberBridgeServer);
      }
    }
    return managedSystemMemberBridgeServer;
  }

  // -------------------------------------------------------------------------
  // Create MBean attributes for each Statistic
  // -------------------------------------------------------------------------

  /**
   * Add MBean attribute definitions for each Statistic.
   *
   * @param managed the mbean definition to add attributes to
   * @return a new instance of ManagedBean copied from <code>managed</code> but with the new
   *         attributes added
   */
  ManagedBean addDynamicAttributes(ManagedBean managed)
      throws org.apache.geode.admin.AdminException {
    if (managed == null) {
      throw new IllegalArgumentException(
          "ManagedBean is null");
    }

    refresh(); // to get the stats...

    // need to create a new instance of ManagedBean to clean the "slate"...
    ManagedBean newManagedBean = new DynamicManagedBean(managed);
    for (final org.apache.geode.admin.Statistic statistic : statistics) {
      StatisticAttributeInfo attrInfo = new StatisticAttributeInfo();

      attrInfo.setName(statistic.getName());
      attrInfo.setDisplayName(statistic.getName());
      attrInfo.setDescription(statistic.getDescription());
      attrInfo.setType("java.lang.Number");

      attrInfo.setIs(false);
      attrInfo.setReadable(true);
      attrInfo.setWriteable(false);

      attrInfo.setStat(statistic);

      newManagedBean.addAttribute(attrInfo);
    }

    return newManagedBean;
  }

  // -------------------------------------------------------------------------
  // MBean Operations
  // -------------------------------------------------------------------------

  /**
   * Returns the ObjectName of the Region for the specified path.
   *
   * @throws AdminException If no region with path <code>path</code> exists
   */
  public ObjectName manageRegion(String path) throws AdminException, MalformedObjectNameException {
    try {
      SystemMemberRegionJmxImpl region = null;

      try {
        region = (SystemMemberRegionJmxImpl) getRegion(path);

      } catch (AdminException e) {
        MBeanUtils.logStackTrace(Level.WARN, e);
        throw e;
      }

      if (region == null) {
        throw new AdminException(
            String.format("This cache does not contain region %s",
                path));

      } else {
        return ObjectName.getInstance(region.getMBeanName());
      }
    } catch (RuntimeException e) {
      MBeanUtils.logStackTrace(Level.WARN, e);
      throw e;
    } catch (VirtualMachineError err) {
      SystemFailure.initiateFailure(err);
      // If this ever returns, rethrow the error. We're poisoned
      // now, so don't let this thread continue.
      throw err;
    } catch (Error e) {
      // Whenever you catch Error or Throwable, you must also
      // catch VirtualMachineError (see above). However, there is
      // _still_ a possibility that you are dealing with a cascading
      // error condition, so you also need to check to see if the JVM
      // is still usable:
      SystemFailure.checkFailure();
      MBeanUtils.logStackTrace(Level.ERROR, e);
      throw e;
    }
  }

  /**
   * Creates a new cache server MBean and returns its <code>ObjectName</code>.
   *
   * @since GemFire 5.7
   */
  public ObjectName manageCacheServer() throws AdminException, MalformedObjectNameException {

    try {
      SystemMemberBridgeServerJmxImpl bridge = (SystemMemberBridgeServerJmxImpl) addCacheServer();
      return ObjectName.getInstance(bridge.getMBeanName());
    } catch (AdminException e) {
      MBeanUtils.logStackTrace(Level.WARN, e);
      throw e;
    } catch (RuntimeException e) {
      MBeanUtils.logStackTrace(Level.WARN, e);
      throw e;
    } catch (VirtualMachineError err) {
      SystemFailure.initiateFailure(err);
      // If this ever returns, rethrow the error. We're poisoned
      // now, so don't let this thread continue.
      throw err;
    } catch (Error e) {
      // Whenever you catch Error or Throwable, you must also
      // catch VirtualMachineError (see above). However, there is
      // _still_ a possibility that you are dealing with a cascading
      // error condition, so you also need to check to see if the JVM
      // is still usable:
      SystemFailure.checkFailure();
      MBeanUtils.logStackTrace(Level.ERROR, e);
      throw e;
    }
  }

  /**
   * Returns the MBean <code>ObjectName</code>s for all cache servers that serve this cache to
   * clients.
   *
   * @since GemFire 4.0
   */
  public ObjectName[] manageCacheServers() throws AdminException, MalformedObjectNameException {

    try {
      SystemMemberCacheServer[] bridges = getCacheServers();
      ObjectName[] names = new ObjectName[bridges.length];
      for (int i = 0; i < bridges.length; i++) {
        SystemMemberBridgeServerJmxImpl bridge = (SystemMemberBridgeServerJmxImpl) bridges[i];
        names[i] = ObjectName.getInstance(bridge.getMBeanName());
      }

      return names;
    } catch (AdminException e) {
      MBeanUtils.logStackTrace(Level.WARN, e);
      throw e;
    } catch (RuntimeException e) {
      MBeanUtils.logStackTrace(Level.WARN, e);
      throw e;
    } catch (VirtualMachineError err) {
      SystemFailure.initiateFailure(err);
      // If this ever returns, rethrow the error. We're poisoned
      // now, so don't let this thread continue.
      throw err;
    } catch (Error e) {
      // Whenever you catch Error or Throwable, you must also
      // catch VirtualMachineError (see above). However, there is
      // _still_ a possibility that you are dealing with a cascading
      // error condition, so you also need to check to see if the JVM
      // is still usable:
      SystemFailure.checkFailure();
      MBeanUtils.logStackTrace(Level.ERROR, e);
      throw e;
    }
  }

  /**
   * Returns the MBean <code>ObjectName</code>s for all cache servers that serve this cache.
   *
   * @since GemFire 4.0
   * @deprecated as of 5.7
   */
  @Deprecated
  public ObjectName[] manageBridgeServers() throws AdminException, MalformedObjectNameException {
    return manageCacheServers();
  }

  // -------------------------------------------------------------------------
  // ManagedResource implementation
  // -------------------------------------------------------------------------

  /** The name of the MBean that will manage this resource */
  private String mbeanName;

  /** The ModelMBean that is configured to manage this resource */
  private ModelMBean modelMBean;

  @Override
  public String getMBeanName() {
    return mbeanName;
  }

  @Override
  public ModelMBean getModelMBean() {
    return modelMBean;
  }

  @Override
  public void setModelMBean(ModelMBean modelMBean) {
    this.modelMBean = modelMBean;
  }

  @Override
  public ObjectName getObjectName() {
    return objectName;
  }

  @Override
  public ManagedResourceType getManagedResourceType() {
    return ManagedResourceType.SYSTEM_MEMBER_CACHE;
  }


  /**
   * Un-registers all the statistics & cache managed resource created for this member. After
   * un-registering the resource MBean instances, clears this.memberResources collection.
   *
   * Creates ConfigurationParameterJmxImpl, StatisticResourceJmxImpl and SystemMemberCacheJmxImpl.
   * But cleans up only StatisticResourceJmxImpl and SystemMemberCacheJmxImpl which are of type
   * ManagedResource.
   */
  @Override
  public void cleanupResource() {
    synchronized (managedRegionResourcesMap) {
      Collection<SystemMemberRegionJmxImpl> values = managedRegionResourcesMap.values();

      for (SystemMemberRegionJmxImpl systemMemberRegionJmxImpl : values) {
        MBeanUtils.unregisterMBean(systemMemberRegionJmxImpl);
      }

      managedRegionResourcesMap.clear();
    }

    synchronized (managedCacheServerResourcesMap) {
      Collection<SystemMemberBridgeServerJmxImpl> values = managedCacheServerResourcesMap.values();

      for (SystemMemberBridgeServerJmxImpl SystemMemberBridgeServerJmxImpl : values) {
        MBeanUtils.unregisterMBean(SystemMemberBridgeServerJmxImpl);
      }

      managedCacheServerResourcesMap.clear();
    }
  }

  /**
   * Cleans up managed resources created for the region that was (created and) destroyed in a cache
   * represented by this Managed Resource.
   *
   * @param regionPath path of the region that got destroyed
   * @return a managed resource related to this region path
   */
  public ManagedResource cleanupRegionResources(String regionPath) {
    ManagedResource cleaned = null;

    synchronized (managedRegionResourcesMap) {
      Set<Entry<String, SystemMemberRegionJmxImpl>> entries = managedRegionResourcesMap.entrySet();
      for (Iterator<Entry<String, SystemMemberRegionJmxImpl>> it = entries.iterator(); it
          .hasNext();) {
        Entry<String, SystemMemberRegionJmxImpl> entry = it.next();
        SystemMemberRegionJmxImpl managedResource = entry.getValue();
        ObjectName objName = managedResource.getObjectName();

        String pathProp = objName.getKeyProperty("path");
        if (pathProp != null && pathProp.equals(regionPath)) {
          cleaned = managedResource;
          it.remove();

          break;
        }
      }
    }

    return cleaned;
  }

  /**
   * Checks equality of the given object with <code>this</code> based on the type (Class) and the
   * MBean Name returned by <code>getMBeanName()</code> methods.
   *
   * @param obj object to check equality with
   * @return true if the given object is if the same type and its MBean Name is same as
   *         <code>this</code> object's MBean Name, false otherwise
   */
  @Override
  public boolean equals(Object obj) {
    if (!(obj instanceof SystemMemberCacheJmxImpl)) {
      return false;
    }

    SystemMemberCacheJmxImpl other = (SystemMemberCacheJmxImpl) obj;

    return getMBeanName().equals(other.getMBeanName());
  }

  /**
   * Returns hash code for <code>this</code> object which is based on the MBean Name generated.
   *
   * @return hash code for <code>this</code> object
   */
  @Override
  public int hashCode() {
    return getMBeanName().hashCode();
  }
}
