/*
 * 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.io.File;
import java.io.IOException;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.OperationsException;
import javax.management.ReflectionException;
import javax.management.modelmbean.ModelMBean;
import javax.management.remote.JMXConnectionNotification;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnectorServer;
import javax.rmi.ssl.SslRMIClientSocketFactory;

import mx4j.tools.adaptor.http.HttpAdaptor;
import org.apache.logging.log4j.Logger;

import org.apache.geode.GemFireException;
import org.apache.geode.GemFireIOException;
import org.apache.geode.LogWriter;
import org.apache.geode.SystemFailure;
import org.apache.geode.admin.AdminDistributedSystem;
import org.apache.geode.admin.AdminException;
import org.apache.geode.admin.jmx.Agent;
import org.apache.geode.admin.jmx.AgentConfig;
import org.apache.geode.admin.jmx.AgentFactory;
import org.apache.geode.distributed.internal.ClusterDistributionManager;
import org.apache.geode.internal.ExitCode;
import org.apache.geode.internal.GemFireVersion;
import org.apache.geode.internal.admin.remote.TailLogResponse;
import org.apache.geode.internal.logging.InternalLogWriter;
import org.apache.geode.internal.logging.LogWriterFactory;
import org.apache.geode.internal.logging.log4j.LogMarker;
import org.apache.geode.internal.statistics.StatisticsConfig;
import org.apache.geode.logging.internal.LoggingSession;
import org.apache.geode.logging.internal.executors.LoggingThread;
import org.apache.geode.logging.internal.log4j.api.LogService;
import org.apache.geode.logging.internal.spi.LogConfig;
import org.apache.geode.logging.internal.spi.LogConfigListener;
import org.apache.geode.logging.internal.spi.LogConfigSupplier;

/**
 * The GemFire JMX Agent provides the ability to administrate one GemFire distributed system via
 * JMX.
 *
 * @since GemFire 3.5
 */
@Deprecated
public class AgentImpl implements org.apache.geode.admin.jmx.Agent,
    org.apache.geode.admin.jmx.internal.ManagedResource, LogConfigSupplier {

  private static final Logger logger = LogService.getLogger();

  /**
   * MX4J HttpAdaptor only supports "basic" as an authentication method. Enabling HttpAdaptor
   * authentication ({@link AgentConfig#HTTP_AUTHENTICATION_ENABLED_NAME}) causes the browser to
   * require a login with username ({@link AgentConfig#HTTP_AUTHENTICATION_USER_NAME}) and password
   * ({@link AgentConfig#HTTP_AUTHENTICATION_PASSWORD_NAME}).
   */
  private static final String MX4J_HTTPADAPTOR_BASIC_AUTHENTICATION = "basic";

  /** JMX Service URL template for JMX/RMI Connector Server */
  private static final String JMX_SERVICE_URL = "service:jmx:rmi://{0}:{1}/jndi/rmi://{2}:{3}{4}";

  /*
   * Set third-party logging configration: MX4J, Jakarta Commons-Logging.
   */
  static {
    checkDebug();
    String commonsLog = System.getProperty("org.apache.commons.logging.log");
    if (commonsLog == null || commonsLog.length() == 0) {
      System.setProperty("org.apache.commons.logging.log",
          "org.apache.commons.logging.impl.SimpleLog");
    }
  }

  /** Enables mx4j tracing if Agent debugging is enabled. */
  private static void checkDebug() {
    try {
      if (Boolean.getBoolean("gfAgentDebug")) {
        mx4j.log.Log.setDefaultPriority(mx4j.log.Logger.TRACE); // .DEBUG
      }
    } 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 (Throwable t) {
      // 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();
      /* ignore */
    }
  }

  // -------------------------------------------------------------------------
  // Member variables
  // -------------------------------------------------------------------------

  /** This Agent's log writer */
  private InternalLogWriter logWriter;

  /** This Agent's JMX http adaptor from MX4J */
  private HttpAdaptor httpAdaptor;

  /** This Agent's RMI Connector Server from MX4J */
  private JMXConnectorServer rmiConnector;

  /** The name of the MBean manages this resource */
  private final String mbeanName;

  /** The ObjectName of the MBean that manages this resource */
  private final ObjectName objectName;

  /** The actual ModelMBean that manages this resource */
  private ModelMBean modelMBean;

  /** The configuration for this Agent */
  private final AgentConfigImpl agentConfig;

  /**
   * The <code>AdminDistributedSystem</code> this Agent is currently connected to or
   * <code>null</code>
   */
  private AdminDistributedSystem system;

  /** The agent's configuration file */
  private String propertyFile;

  /**
   * A lock object to guard the Connect and Disconnect calls being made on the agent for connections
   * to the DS
   **/
  private final Object CONN_SYNC = new Object();

  protected MemberInfoWithStatsMBean memberInfoWithStatsMBean;

  private MBeanServer mBeanServer;

  private final LoggingSession loggingSession;
  private final Set<LogConfigListener> logConfigListeners = new HashSet<>();

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

  /**
   * Constructs a new Agent using the specified configuration.
   *
   * @param agentConfig instance of configuration for Agent
   * @throws org.apache.geode.admin.AdminException if the generated mbeanName does not have the
   *         correct format
   * @throws IllegalArgumentException if agentConfig is null
   */
  public AgentImpl(AgentConfigImpl agentConfig) throws AdminException, IllegalArgumentException {
    loggingSession = LoggingSession.create();

    shutdownHook = new LoggingThread("Shutdown", false, this::disconnectFromSystem);
    addShutdownHook();
    if (agentConfig == null) {
      throw new IllegalArgumentException(
          "AgentConfig must not be null");
    }
    this.agentConfig = agentConfig;
    mbeanName = MBEAN_NAME_PREFIX + MBeanUtils.makeCompliantMBeanNameProperty("Agent");

    try {
      objectName = new ObjectName(mbeanName);
    } catch (MalformedObjectNameException ex) {
      String s = String.format("While creating ObjectName: %s",
          mbeanName);
      throw new AdminException(s, ex);
    }

    propertyFile = this.agentConfig.getPropertyFile().getAbsolutePath();

    // bind address only affects how the Agent VM connects to the system...
    // It should be set only once in the agent lifecycle
    this.agentConfig.setBindAddress(getBindAddress());

    // LOG: create LogWriterAppender and LogWriterLogger
    initLogWriter();

    mBeanServer = MBeanUtils.start();

    MBeanUtils.createMBean(this);

    initializeHelperMbean();
  }

  private void initializeHelperMbean() {
    try {
      memberInfoWithStatsMBean = new MemberInfoWithStatsMBean(this);

      MBeanServer mbs = getMBeanServer();
      mbs.registerMBean(memberInfoWithStatsMBean, memberInfoWithStatsMBean.getObjectName());
      /*
       * We are not re-throwing these exceptions as failure create/register the GemFireTypesWrapper
       * will not stop the Agent from working. But we are logging it as it could be an indication of
       * some problem. Also not creating Localized String for the exception.
       */
    } catch (OperationsException e) {
      logger.info("Failed to initialize MemberInfoWithStatsMBean.", e);
    } catch (MBeanRegistrationException e) {
      logger.info("Failed to initialize MemberInfoWithStatsMBean.", e);
    } catch (AdminException e) {
      logger.info("Failed to initialize MemberInfoWithStatsMBean.", e);
    }
  }

  // -------------------------------------------------------------------------
  // Public operations
  // -------------------------------------------------------------------------

  @Override
  public AgentConfig getConfig() {
    return agentConfig;
  }

  @Override
  public AdminDistributedSystem getDistributedSystem() {
    return system;
  }

  /**
   * Persists the current Agent configuration to its property file.
   *
   * @throws GemFireIOException if unable to persist the configuration to props
   * @see #getPropertyFile
   */
  @Override
  public void saveProperties() {
    throw new GemFireIOException("saveProperties is no longer supported for security reasons");
  }

  /**
   * Starts the jmx agent
   */
  @Override
  public void start() {
    checkDebug();

    agentConfig.validate();

    if (mBeanServer == null) {
      mBeanServer = MBeanUtils.start();
    }

    try {
      startHttpAdaptor();
    } catch (StartupException e) {
      loggingSession.stopSession();
      loggingSession.shutdown();
      throw e;
    }

    try {
      startRMIConnectorServer();
    } catch (StartupException e) {
      stopHttpAdaptor();
      loggingSession.stopSession();
      loggingSession.shutdown();
      throw e;
    }

    try {
      startSnmpAdaptor();
    } catch (StartupException e) {
      stopRMIConnectorServer();
      stopHttpAdaptor();
      loggingSession.stopSession();
      loggingSession.shutdown();
      throw e;
    }

    if (agentConfig.getAutoConnect()) {
      try {
        connectToSystem();
        /*
         * Call Agent.stop() if connectToSystem() fails. This should clean up agent-DS connection &
         * stop all the HTTP/RMI/SNMP adapters started earlier.
         */
      } catch (AdminException ex) {
        logger.error("auto connect failed:  {}",
            ex.getMessage());
        stop();
        throw new StartupException(ex);
      } catch (MalformedObjectNameException ex) {
        String autoConnectFailed = "auto connect failed:  {}";
        logger.error(autoConnectFailed, ex.getMessage());
        stop();
        throw new StartupException(new AdminException(
            String.format("auto connect failed: %s", ex.getMessage()), ex));
      }
    } // getAutoConnect

    logger.info("GemFire JMX Agent is running...");

    if (memberInfoWithStatsMBean == null) {
      initializeHelperMbean();
    }
  }

  /**
   * Deregisters everything this Agent registered and releases the MBeanServer.
   */
  @Override
  public void stop() {
    try {
      logger.info("Stopping JMX agent");

      loggingSession.stopSession();

      // stop the GemFire Distributed System
      stopDistributedSystem();

      // stop all JMX Adaptors and Connectors...
      stopHttpAdaptor();
      stopRMIConnectorServer();
      memberInfoWithStatsMBean = null;
      stopSnmpAdaptor();

      // release the MBeanServer for cleanup...
      MBeanUtils.stop();
      mBeanServer = null;

      // remove the register shutdown hook which disconnects the Agent from the Distributed System
      // upon JVM shutdown
      removeShutdownHook();

      logger.info("Agent has stopped");
    } finally {
      loggingSession.shutdown();
    }

  }

  private void stopDistributedSystem() {
    // disconnect from the distributed system...
    try {
      disconnectFromSystem();
    } catch (Exception e) {
      // disconnectFromSystem already prints any Exceptions
    } catch (VirtualMachineError err) {
      SystemFailure.initiateFailure(err);
      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();
    }
  }

  @Override
  public ObjectName manageDistributedSystem() throws MalformedObjectNameException {
    synchronized (CONN_SYNC) {
      if (isConnected()) {
        return ((AdminDistributedSystemJmxImpl) system).getObjectName();
      }
      return null;
    }
  }

  /**
   * Connects to the DistributedSystem currently described by this Agent's attributes for
   * administration and monitoring.
   *
   * @return the object name of the system that the Agent is now connected to
   */
  @Override
  @edu.umd.cs.findbugs.annotations.SuppressWarnings(
      value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD",
      justification = "This is only a style warning.")
  public ObjectName connectToSystem() throws AdminException, MalformedObjectNameException {
    synchronized (CONN_SYNC) {
      try {
        if (isConnected()) {
          return ((AdminDistributedSystemJmxImpl) system).getObjectName();
        }

        ClusterDistributionManager.setIsDedicatedAdminVM(true);

        AdminDistributedSystemJmxImpl systemJmx = (AdminDistributedSystemJmxImpl) system;
        if (systemJmx == null) {
          systemJmx = (AdminDistributedSystemJmxImpl) createDistributedSystem(agentConfig);
          system = systemJmx;
        }
        systemJmx.connect(logWriter);

        return new ObjectName(systemJmx.getMBeanName());
      } catch (AdminException e) {
        logger.warn(e.getMessage(), e);
        throw e;
      } catch (RuntimeException e) {
        logger.warn(e.getMessage(), 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();
        logger.error(e.getMessage(), e);
        throw e;
      }
    }
  }

  /**
   * Disconnects from the current DistributedSystem (if connected to one).
   */
  @Override
  @edu.umd.cs.findbugs.annotations.SuppressWarnings(
      value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD",
      justification = "This is only a style warning.")
  public void disconnectFromSystem() {
    synchronized (CONN_SYNC) {
      try {
        if (system == null || !system.isConnected()) {
          return;
        }
        system.disconnect();
      } catch (RuntimeException e) {
        logger.warn(e.getMessage(), 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();
        logger.warn(e.getMessage(), e);
        throw e;
      } finally {
        ClusterDistributionManager.setIsDedicatedAdminVM(false);
      }
    }
  }

  /**
   * Retrieves a displayable snapshot of this Agent's log.
   *
   * @return snapshot of the current log
   */
  public String getLog() {
    File childLogFile = loggingSession.getLogFile().isPresent()
        ? loggingSession.getLogFile().get().getChildLogFile() : null;
    String childTail = tailFile(childLogFile);
    String mainTail = tailFile(new File(agentConfig.getLogFile()));
    if (childTail == null && mainTail == null) {
      return "No log file configured, log messages will be directed to stdout.";
    } else {
      StringBuilder result = new StringBuilder();
      if (mainTail != null) {
        result.append(mainTail);
      }
      if (childTail != null) {
        result
            .append("\n" + "-------------------- tail of child log --------------------" + "\n");
        result.append(childTail);
      }
      return result.toString();
    }
  }

  /**
   * Retrieves display-friendly GemFire version information.
   */
  public String getVersion() {
    return GemFireVersion.asString();
  }

  // -------------------------------------------------------------------------
  // Public attribute accessors/mutators
  // -------------------------------------------------------------------------

  /** Returns true if this Agent is currently connected to a system. */
  @Override
  public boolean isConnected() {
    boolean result = false;
    synchronized (CONN_SYNC) {
      result = ((system != null) && system.isConnected());
    }
    return result;
  }

  /**
   * Gets the agent's property file. This is the file it will use when saving its configuration. It
   * was also used when the agent started to initialize its configuration.
   *
   * @return the agent's property file
   */
  public String getPropertyFile() {
    return propertyFile;
  }

  /**
   * Sets the agent's property file.
   *
   * @param value the name of the file to save the agent properties in.
   * @throws IllegalArgumentException if the specified file is a directory.
   * @throws IllegalArgumentException if the specified file's parent is not an existing directory.
   */
  public void setPropertyFile(String value) {
    File f = (new File(value)).getAbsoluteFile();
    if (f.isDirectory()) {
      throw new IllegalArgumentException(
          String.format("The file %s is a directory.", f));
    }
    File parent = f.getParentFile();
    if (parent != null) {
      if (!parent.isDirectory()) {
        throw new IllegalArgumentException(
            String.format("The directory %s does not exist.", parent));
      }
    }
    propertyFile = f.getPath();
  }

  /**
   * Gets the mcastAddress of the distributed system that this Agent is managing.
   *
   * @return The mcastAddress value
   */
  public String getMcastAddress() {
    return agentConfig.getMcastAddress();
  }

  /**
   * Sets the mcastAddress of the distributed system that this Agent is managing.
   *
   * @param mcastAddress The new mcastAddress value
   */
  public void setMcastAddress(String mcastAddress) {
    agentConfig.setMcastAddress(mcastAddress);
  }

  /**
   * Gets the mcastPort of the distributed system that this Agent is managing.
   *
   * @return The mcastPort value
   */
  public int getMcastPort() {
    return agentConfig.getMcastPort();
  }

  /**
   * Sets the mcastPort of the distributed system that this Agent is managing.
   *
   * @param mcastPort The new mcastPort value
   */
  public void setMcastPort(int mcastPort) {
    agentConfig.setMcastPort(mcastPort);
  }

  /**
   * Gets the locators of the distributed system that this Agent is managing.
   * <p>
   * Format is a comma-delimited list of "host[port]" entries.
   *
   * @return The locators value
   */
  public String getLocators() {
    return agentConfig.getLocators();
  }

  /**
   * Sets the locators of the distributed system that this Agent is managing.
   * <p>
   * Format is a comma-delimited list of "host[port]" entries.
   *
   * @param locators The new locators value
   */
  public void setLocators(String locators) {
    agentConfig.setLocators(locators);
  }

  /**
   * Gets the membership UDP port range in the distributed system that this Agent is monitoring.
   * <p>
   * This range is given as two numbers separated by a minus sign like "min-max"
   *
   * @return membership UDP port range
   */
  public String getMembershipPortRange() {
    return agentConfig.getMembershipPortRange();
  }

  /**
   * Sets the membership UDP port range in the distributed system that this Agent is monitoring.
   * <p>
   * This range is given as two numbers separated by a minus sign like "min-max"
   *
   * @param membershipPortRange membership UDP port range
   */
  public void setMembershipPortRange(String membershipPortRange) {
    agentConfig.setMembershipPortRange(membershipPortRange);
  }

  /**
   * Gets the bindAddress of the distributed system that this Agent is managing.
   *
   * @return The bindAddress value
   */
  public String getBindAddress() {
    return agentConfig.getBindAddress();
  }

  /**
   * Sets the bindAddress of the distributed system that this Agent is managing.
   *
   * @param bindAddress The new bindAddress value
   */
  public void setBindAddress(String bindAddress) {
    agentConfig.setBindAddress(bindAddress);
  }

  /**
   * Retrieves the command that the DistributedSystem will use to perform remote manipulation of
   * config files and log files.
   *
   * @return the remote command for DistributedSystem
   */
  public String getRemoteCommand() {
    return agentConfig.getRemoteCommand();
  }

  /**
   * Sets the command that the DistributedSystem will use to perform remote manipulation of config
   * files and log files.
   *
   * @param remoteCommand the remote command for DistributedSystem
   */
  public void setRemoteCommand(String remoteCommand) {
    agentConfig.setRemoteCommand(remoteCommand);
  }

  /** Returns the system identity for the DistributedSystem */
  public String getSystemId() {
    return agentConfig.getSystemId();
  }

  /** Sets the system identity for the DistributedSystem */
  public void setSystemId(String systemId) {
    agentConfig.setSystemId(systemId);
  }

  /**
   * Gets the logFileSizeLimit in megabytes of this Agent. Zero indicates no limit.
   *
   * @return The logFileSizeLimit value
   */
  public int getLogFileSizeLimit() {
    return agentConfig.getLogFileSizeLimit();
  }

  /**
   * Sets the logFileSizeLimit in megabytes of this Agent. Zero indicates no limit.
   *
   * @param logFileSizeLimit The new logFileSizeLimit value
   */
  public void setLogFileSizeLimit(int logFileSizeLimit) {
    agentConfig.setLogFileSizeLimit(logFileSizeLimit);
    logConfigChanged();
  }

  /**
   * Gets the logDiskSpaceLimit in megabytes of this Agent. Zero indicates no limit.
   *
   * @return The logDiskSpaceLimit value
   */
  public int getLogDiskSpaceLimit() {
    return agentConfig.getLogDiskSpaceLimit();
  }

  /**
   * Sets the logDiskSpaceLimit in megabytes of this Agent. Zero indicates no limit.
   *
   * @param logDiskSpaceLimit The new logDiskSpaceLimit value
   */
  public void setLogDiskSpaceLimit(int logDiskSpaceLimit) {
    agentConfig.setLogDiskSpaceLimit(logDiskSpaceLimit);
    logConfigChanged();
  }

  /**
   * Gets the logFile name for this Agent to log to.
   *
   * @return The logFile value
   */
  public String getLogFile() {
    return agentConfig.getLogFile();
  }

  /**
   * Sets the logFile name for this Agent to log to.
   *
   * @param logFile The new logFile value
   */
  public void setLogFile(String logFile) {
    agentConfig.setLogFile(logFile);
    logConfigChanged();
  }

  /**
   * Gets the logLevel of this Agent.
   *
   * @return The logLevel value
   */
  public String getLogLevel() {
    return agentConfig.getLogLevel();
  }

  /**
   * Sets the logLevel of this Agent.
   *
   * @param logLevel The new logLevel value
   */
  public void setLogLevel(String logLevel) {
    agentConfig.setLogLevel(logLevel);
    logConfigChanged();
  }

  /** Returns true if the Agent is set to auto connect to a system. */
  public boolean getAutoConnect() {
    return agentConfig.getAutoConnect();
  }

  /** Returns true if the Agent is set to auto connect to a system. */
  public boolean isAutoConnect() {
    return agentConfig.getAutoConnect();
  }

  /** Sets or unsets the option to auto connect to a system. */
  public void setAutoConnect(boolean v) {
    agentConfig.setAutoConnect(v);
  }

  /**
   * Returns the address (URL) on which the RMI connector server runs or <code>null</code> if the
   * RMI connector server has not been started. This method is used primarily for testing purposes.
   *
   * @see JMXConnectorServer#getAddress()
   */
  public JMXServiceURL getRMIAddress() {
    if (rmiConnector != null) {
      return rmiConnector.getAddress();

    } else {
      return null;
    }
  }

  /**
   * Gets the configuration for this Agent.
   *
   * @return the configuration for this Agent
   */
  protected AgentConfig getAgentConfig() {
    return agentConfig;
  }

  // -------------------------------------------------------------------------
  // Internal implementation methods
  // -------------------------------------------------------------------------

  /** Returns the tail of the system log specified by <code>File</code>. */
  private String tailFile(File f) {
    try {
      return TailLogResponse.tailSystemLog(f);
    } catch (IOException ex) {
      return String.format("Could not tail %s because: %s",
          f, ex);
    }
  }

  /**
   * Returns the active MBeanServer which has any GemFire MBeans registered.
   *
   * @return the GemFire mbeanServer
   */
  @Override
  public MBeanServer getMBeanServer() {
    return mBeanServer;
  }

  /**
   * Gets the current instance of LogWriter for logging
   *
   * @return the logWriter
   */
  @Override
  public LogWriter getLogWriter() {
    return logWriter;
  }

  private final Thread shutdownHook;

  /**
   * Adds a ShutdownHook to the Agent for cleaning up any resources
   */
  private void addShutdownHook() {
    if (!Boolean.getBoolean(
        org.apache.geode.distributed.internal.InternalDistributedSystem.DISABLE_SHUTDOWN_HOOK_PROPERTY)) {
      Runtime.getRuntime().addShutdownHook(shutdownHook);
    }
  }

  private void removeShutdownHook() {
    if (!Boolean.getBoolean(
        org.apache.geode.distributed.internal.InternalDistributedSystem.DISABLE_SHUTDOWN_HOOK_PROPERTY)) {
      Runtime.getRuntime().removeShutdownHook(shutdownHook);
    }
  }

  /**
   * Creates a LogWriterI18n for this Agent to use in logging.
   */
  @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RV_RETURN_VALUE_IGNORED_BAD_PRACTICE",
      justification = "Return value for file delete is not important here.")
  private void initLogWriter() throws org.apache.geode.admin.AdminException {
    loggingSession.createSession(this);

    final LogConfig logConfig = agentConfig.createLogConfig();

    // LOG: create logWriterAppender here
    loggingSession.startSession();

    // LOG: look in AgentConfigImpl for existing LogWriter to use
    InternalLogWriter existingLogWriter = agentConfig.getInternalLogWriter();
    if (existingLogWriter != null) {
      logWriter = existingLogWriter;
    } else {
      // LOG: create LogWriterLogger
      logWriter = LogWriterFactory.createLogWriterLogger(logConfig, false);
      // Set this log writer in AgentConfigImpl
      agentConfig.setInternalLogWriter(logWriter);
    }

    // LOG: create logWriter here
    logWriter = LogWriterFactory.createLogWriterLogger(logConfig, false);

    // Set this log writer in AgentConfig
    agentConfig.setInternalLogWriter(logWriter);

    // LOG:CONFIG: changed next three statements from config to info
    logger.info(LogMarker.CONFIG_MARKER,
        String.format("Agent config property file name: %s",
            AgentConfigImpl.retrievePropertyFile()));
    logger.info(LogMarker.CONFIG_MARKER, agentConfig.getPropertyFileDescription());
    logger.info(LogMarker.CONFIG_MARKER, agentConfig.toPropertiesAsString());
  }

  /**
   * Stops the HttpAdaptor and its XsltProcessor. Unregisters the associated MBeans.
   */
  private void stopHttpAdaptor() {
    if (!agentConfig.isHttpEnabled()) {
      return;
    }

    // stop the adaptor...
    try {
      httpAdaptor.stop();
    } catch (Exception e) {
      logger.warn(e.getMessage(), e);
    }

    try {
      MBeanUtils.unregisterMBean(getHttpAdaptorName());
      MBeanUtils.unregisterMBean(getXsltProcessorName());
    } catch (MalformedObjectNameException e) {
      logger.warn(e.getMessage(), e);
    }
  }

  /** Stops the RMIConnectorServer and unregisters its MBean. */
  private void stopRMIConnectorServer() {
    if (!agentConfig.isRmiEnabled()) {
      return;
    }

    // stop the RMI Connector server...
    try {
      rmiConnector.stop();
    } catch (Exception e) {
      logger.warn(e.getMessage(), e);
    }

    try {
      ObjectName rmiRegistryNamingName = getRMIRegistryNamingName();
      if (agentConfig.isRmiRegistryEnabled()
          && mBeanServer.isRegistered(rmiRegistryNamingName)) {
        String[] empty = new String[0];
        mBeanServer.invoke(rmiRegistryNamingName, "stop", empty, empty);
        MBeanUtils.unregisterMBean(rmiRegistryNamingName);
      }
    } catch (MalformedObjectNameException e) {
      logger.warn(e.getMessage(), e);
    } catch (InstanceNotFoundException e) {
      logger.warn(e.getMessage(), e);
    } catch (ReflectionException e) {
      logger.warn(e.getMessage(), e);
    } catch (MBeanException e) {
      logger.warn(e.getMessage(), e);
    }

    try {
      ObjectName rmiConnectorServerName = getRMIConnectorServerName();
      if (mBeanServer.isRegistered(rmiConnectorServerName)) {
        MBeanUtils.unregisterMBean(rmiConnectorServerName);
      }
    } catch (MalformedObjectNameException e) {
      logger.warn(e.getMessage(), e);
    }
  }

  /** Stops the SnmpAdaptor and unregisters its MBean. */
  private void stopSnmpAdaptor() {
    if (!agentConfig.isSnmpEnabled()) {
      return;
    }

    // stop the SnmpAdaptor...
    try {
      getMBeanServer().invoke(getSnmpAdaptorName(), "unbind", new Object[0], new String[0]);
    } catch (Exception e) {
      logger.warn(e.getMessage(), e);
    }

    try {
      MBeanUtils.unregisterMBean(getSnmpAdaptorName());
    } catch (MalformedObjectNameException e) {
      logger.warn(e.getMessage(), e);
    }
  }

  /** Returns the JMX ObjectName for the RMI registry Naming MBean. */
  private ObjectName getRMIRegistryNamingName()
      throws javax.management.MalformedObjectNameException {
    return ObjectName.getInstance("naming:type=rmiregistry");
  }

  /** Returns the JMX ObjectName for the HttpAdaptor. */
  private ObjectName getHttpAdaptorName() throws javax.management.MalformedObjectNameException {
    return new ObjectName("Server:name=HttpAdaptor");
  }

  /** Returns the JMX ObjectName for the RMIConnectorServer. */
  private ObjectName getRMIConnectorServerName()
      throws javax.management.MalformedObjectNameException {
    return new ObjectName("connectors:protocol=rmi");
  }

  /** Returns the JMX ObjectName for the SnmpAdaptor. */
  private ObjectName getSnmpAdaptorName() throws javax.management.MalformedObjectNameException {
    return new ObjectName("Adaptors:protocol=SNMP");
  }

  /** Returns the JMX ObjectName for the HttpAdaptor's XsltProcessor. */
  private ObjectName getXsltProcessorName() throws javax.management.MalformedObjectNameException {
    return new ObjectName("Server:name=XSLTProcessor");
  }

  // -------------------------------------------------------------------------
  // Factory method for creating DistributedSystem
  // -------------------------------------------------------------------------

  /**
   * Creates and connects to a <code>DistributedSystem</code>.
   *
   */
  private AdminDistributedSystem createDistributedSystem(AgentConfigImpl config)
      throws org.apache.geode.admin.AdminException {
    return new AdminDistributedSystemJmxImpl(config);
  }

  // -------------------------------------------------------------------------
  // Agent main
  // -------------------------------------------------------------------------

  /**
   * Command-line main for running the GemFire Management Agent.
   * <p>
   * Accepts command-line arguments matching the options in {@link AgentConfig} and
   * {@link org.apache.geode.admin.DistributedSystemConfig}.
   * <p>
   * <code>AgentConfig</code> will convert -Jarguments to System properties.
   */
  public static void main(String[] args) {
    SystemFailure.loadEmergencyClasses();

    AgentConfigImpl ac;
    try {
      ac = new AgentConfigImpl(args);
    } catch (RuntimeException ex) {
      System.err
          .println(String.format("Failed reading configuration: %s", ex));
      ExitCode.FATAL.doSystemExit();
      return;
    }

    try {
      Agent agent = AgentFactory.getAgent(ac);
      agent.start();

    } 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 (Throwable t) {
      // 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();
      t.printStackTrace();
      ExitCode.FATAL.doSystemExit();
    }
  }

  // -------------------------------------------------------------------------
  // MX4J Connectors/Adaptors
  // -------------------------------------------------------------------------

  private void createRMIRegistry() throws Exception {
    if (!agentConfig.isRmiRegistryEnabled()) {
      return;
    }
    MBeanServer mbs = getMBeanServer();
    String host = agentConfig.getRmiBindAddress();
    int port = agentConfig.getRmiPort();

    /*
     * Register and start the rmi-registry naming MBean, which is needed by JSR 160
     * RMIConnectorServer
     */
    ObjectName registryName = getRMIRegistryNamingName();
    try {
      RMIRegistryService registryNamingService = null;
      if (host != null && !("".equals(host.trim()))) {
        registryNamingService = new RMIRegistryService(host, port);
      } else {
        registryNamingService = new RMIRegistryService(port);
      }
      mbs.registerMBean(registryNamingService, registryName);
    } catch (javax.management.InstanceAlreadyExistsException e) {
      logger.info("{}  is already registered.",
          registryName);
    }
    mbs.invoke(registryName, "start", null, null);
  }

  /**
   * Defines and starts the JMX RMIConnector and service.
   * <p>
   * If {@link AgentConfig#isRmiEnabled} returns false, then this adaptor will not be started.
   */
  private void startRMIConnectorServer() {
    if (!agentConfig.isRmiEnabled()) {
      return;
    }

    String rmiBindAddress = agentConfig.getRmiBindAddress();

    // Set RMI Stubs to use the given RMI Bind Address
    // Default bindAddress is "", if none is set - ignore if not set
    // If java.rmi.server.hostname property is specified then
    // that override is not changed
    String rmiStubServerNameKey = "java.rmi.server.hostname";
    String overrideHostName = System.getProperty(rmiStubServerNameKey);
    if ((overrideHostName == null || overrideHostName.trim().length() == 0)
        && (rmiBindAddress != null && rmiBindAddress.trim().length() != 0)) {
      System.setProperty(rmiStubServerNameKey, rmiBindAddress);
      logger.info(("Setting " + rmiStubServerNameKey + " = "
          + rmiBindAddress));
    }

    try {
      createRMIRegistry();
      ObjectName objName = getRMIConnectorServerName();

      // make sure this adaptor is not already registered...
      if (getMBeanServer().isRegistered(objName)) {
        // dunno how we got here...
        logger.info("RMIConnectorServer already registered as {}", objName);
        return;
      }

      /*
       * url defined as: service:jmx:protocol:sap where 1. protocol: rmi 2. sap is:
       * [host[:port]][url-path] where host: rmi-binding-address port: rmi-server-port url-path:
       * /jndi/rmi://<rmi-binding-address>:<rmi-port><JNDI_NAME>
       */
      String urlString = null;
      String connectorServerHost = "";
      int connectorServerPort = agentConfig.getRmiServerPort();
      String rmiRegistryHost = "";
      int rmiRegistryPort = agentConfig.getRmiPort();

      // Set registryHost to localhost if not specified
      // RMI stubs would use a default IP if namingHost is left empty
      if (rmiBindAddress == null || rmiBindAddress.trim().length() == 0) {
        connectorServerHost = "localhost";
        rmiRegistryHost = "";
      } else {
        connectorServerHost = applyRFC2732(rmiBindAddress);
        rmiRegistryHost = connectorServerHost;
      }

      urlString = MessageFormat.format(AgentImpl.JMX_SERVICE_URL, connectorServerHost,
          String.valueOf(connectorServerPort), rmiRegistryHost, String.valueOf(rmiRegistryPort),
          JNDI_NAME);

      logger.debug("JMX Service URL string is : \"{}\"", urlString);

      // The address of the connector
      JMXServiceURL url = new JMXServiceURL(urlString);

      Map<String, Object> env = new HashMap<>();

      RMIServerSocketFactory ssf = new MX4JServerSocketFactory(agentConfig.isAgentSSLEnabled(), // true,
          agentConfig.isAgentSSLRequireAuth(), // true,
          agentConfig.getAgentSSLProtocols(), // "any",
          agentConfig.getAgentSSLCiphers(), // "any",
          agentConfig.getRmiBindAddress(), 10, // backlog
          agentConfig.getGfSecurityProperties());
      env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, ssf);

      if (agentConfig.isAgentSSLEnabled()) {
        RMIClientSocketFactory csf = new SslRMIClientSocketFactory();
        env.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, csf);
      }

      MBeanServer mbs = null; // will be set by registering w/ mbeanServer
      rmiConnector = JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs);

      // for cleanup
      rmiConnector.addNotificationListener(new ConnectionNotificationAdapter(),
          new ConnectionNotificationFilterImpl(), this);

      // Register the JMXConnectorServer in the MBeanServer
      getMBeanServer().registerMBean(rmiConnector, objName);

      // Start the JMXConnectorServer
      rmiConnector.start();
    } 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 (Throwable t) {
      // 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();
      logger.error("Failed to start RMIConnectorServer:", t);
      throw new StartupException(
          "Failed to start RMI service, verify RMI configuration properties", t);
    }
  }

  /**
   * Starts the optional third-party AdventNet SNMP Adaptor.
   * <p>
   * If {@link AgentConfig#isSnmpEnabled} returns false, then this adaptor will not be started.
   */
  private void startSnmpAdaptor() {
    if (!agentConfig.isSnmpEnabled()) {
      return;
    }
    try {
      ObjectName objName = getSnmpAdaptorName();

      // make sure this adaptor is not already registered...
      if (getMBeanServer().isRegistered(objName)) {
        // dunno how we got here...
        logger.info("SnmpAdaptor already registered as  {}", objName);
        return;
      }

      String className = "com.adventnet.adaptors.snmp.snmpsupport.SmartSnmpAdaptor";
      String snmpDir = agentConfig.getSnmpDirectory();
      // ex:/merry2/users/klund/agent

      // validate the directory...
      if (snmpDir == null || snmpDir.length() == 0) {
        throw new IllegalArgumentException(
            "snmp-directory must be specified because SNMP is enabled");
      }
      File root = new File(snmpDir);
      if (!root.exists()) {
        throw new IllegalArgumentException(
            "snmp-directory does not exist");
      }

      // create the adaptor...
      String[] sigs = new String[] {"java.lang.String"};
      Object[] args = new Object[] {snmpDir};

      String bindAddress = agentConfig.getSnmpBindAddress();
      if (bindAddress != null && bindAddress.length() > 0) {
        sigs = new String[] {"java.lang.String", sigs[0]};
        args = new Object[] {bindAddress, args[0]};
      }

      // go...
      getMBeanServer().createMBean(className, objName, args, sigs);
    } 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 (Throwable t) {
      // 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();
      logger.error("Failed to start SnmpAdaptor:  {}", t.getMessage());
      throw new StartupException(String.format("Failed to start SnmpAdaptor: %s",
          t.getMessage()), t);
    }
  }

  /**
   * Defines and starts the JMX Http Adaptor service from MX4J.
   * <p>
   * If {@link AgentConfig#isHttpEnabled} returns false, then this adaptor will not be started.
   */
  private void startHttpAdaptor() {
    if (!agentConfig.isHttpEnabled()) {
      return;
    }
    try {
      ObjectName objName = getHttpAdaptorName();

      // make sure this adaptor is not already registered...
      if (getMBeanServer().isRegistered(objName)) {
        // dunno how we got here...
        logger.info("HttpAdaptor already registered as  {}", objName);
        return;
      }

      httpAdaptor = new HttpAdaptor();

      // validate and set host and port values...
      if (agentConfig.getHttpPort() > 0) {
        httpAdaptor.setPort(agentConfig.getHttpPort());
        logger.info(LogMarker.CONFIG_MARKER,
            "HTTP adaptor listening on port: {}",
            agentConfig.getHttpPort());
      } else {
        logger.error("Incorrect port value  {}",
            agentConfig.getHttpPort());
      }

      if (agentConfig.getHttpBindAddress() != null) {
        String host = agentConfig.getHttpBindAddress();
        logger.info(LogMarker.CONFIG_MARKER, "HTTP adaptor listening on address:  {}", host);
        httpAdaptor.setHost(host);
      } else {
        logger.error("Incorrect null hostname");
      }

      // SSL support...
      MX4JServerSocketFactory socketFactory =
          new MX4JServerSocketFactory(agentConfig.isAgentSSLEnabled(),
              agentConfig.isHttpSSLRequireAuth(), agentConfig.getAgentSSLProtocols(),
              agentConfig.getAgentSSLCiphers(), agentConfig.getGfSecurityProperties());
      httpAdaptor.setSocketFactory(socketFactory);

      // authentication (user login) support...
      if (agentConfig.isHttpAuthEnabled()) {
        // this pops up a login dialog from the browser...
        httpAdaptor.setAuthenticationMethod(MX4J_HTTPADAPTOR_BASIC_AUTHENTICATION); // only
                                                                                    // basic
                                                                                    // works

        httpAdaptor.addAuthorization(agentConfig.getHttpAuthUser(),
            agentConfig.getHttpAuthPassword());
      }

      // add the XsltProcessor...
      httpAdaptor.setProcessorName(createXsltProcessor());

      // register the HttpAdaptor and snap on the XsltProcessor...
      getMBeanServer().registerMBean(httpAdaptor, objName);
      httpAdaptor.start();
    } 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 (Throwable t) {
      // 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();
      logger.error("Failed to start HttpAdaptor:  {}", t.getMessage());
      throw new StartupException(String.format("Failed to start HttpAdaptor: %s",
          t.getMessage()), t);
    }
  }

  /**
   * Defines and starts the Xslt Processor helper service for the Http Adaptor.
   */
  private ObjectName createXsltProcessor() throws javax.management.JMException {
    ObjectName objName = getXsltProcessorName();

    // make sure this mbean is not already registered...
    if (getMBeanServer().isRegistered(objName)) {
      // dunno how we got here...
      logger.info("XsltProcessor already registered as  {}", objName);
      return objName;
    }

    getMBeanServer().registerMBean(new mx4j.tools.adaptor.http.XSLTProcessor(), objName);
    return objName;
  }

  // -------------------------------------------------------------------------
  // SSL configuration for GemFire
  // -------------------------------------------------------------------------
  public boolean isSSLEnabled() {
    return agentConfig.isSSLEnabled();
  }

  public void setSSLEnabled(boolean enabled) {
    agentConfig.setSSLEnabled(enabled);
  }

  public String getSSLProtocols() {
    return agentConfig.getSSLProtocols();
  }

  public void setSSLProtocols(String protocols) {
    agentConfig.setSSLProtocols(protocols);
  }

  public String getSSLCiphers() {
    return agentConfig.getSSLCiphers();
  }

  public void setSSLCiphers(String ciphers) {
    agentConfig.setSSLCiphers(ciphers);
  }

  public boolean isSSLAuthenticationRequired() {
    return agentConfig.isSSLAuthenticationRequired();
  }

  public void setSSLAuthenticationRequired(boolean authRequired) {
    agentConfig.setSSLAuthenticationRequired(authRequired);
  }

  public Properties getSSLProperties() {
    return agentConfig.getSSLProperties();
  }

  public void setSSLProperties(Properties sslProperties) {
    agentConfig.setSSLProperties(sslProperties);
  }

  public void addSSLProperty(String key, String value) {
    agentConfig.addSSLProperty(key, value);
  }

  public void removeSSLProperty(String key) {
    agentConfig.removeSSLProperty(key);
  }

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

  @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.AGENT;
  }

  @Override
  public void cleanupResource() {}

  @Override
  public LogConfig getLogConfig() {
    return agentConfig.createLogConfig();
  }

  @Override
  public StatisticsConfig getStatisticsConfig() {
    return agentConfig.createStatisticsConfig();
  }

  @Override
  public void addLogConfigListener(LogConfigListener logConfigListener) {
    logConfigListeners.add(logConfigListener);
  }

  @Override
  public void removeLogConfigListener(LogConfigListener logConfigListener) {
    logConfigListeners.remove(logConfigListener);
  }

  void logConfigChanged() {
    for (LogConfigListener listener : logConfigListeners) {
      listener.configChanged();
    }
  }

  static class StartupException extends GemFireException {
    private static final long serialVersionUID = 6614145962199330348L;

    StartupException(Throwable cause) {
      super(cause);
    }

    StartupException(String reason, Throwable cause) {
      super(reason, cause);
    }
  }

  // -------------------------------------------------------------------------
  // Other Support methods
  // -------------------------------------------------------------------------
  /**
   * Checks the no. of active RMI clients and updates a flag in the Admin Distributed System.
   *
   * @see AdminDistributedSystemJmxImpl#setRmiClientCountZero(boolean)
   * @since GemFire 6.0
   */
  void updateRmiClientsCount() {
    int noOfClientsConnected = 0;

    String[] connectionIds = rmiConnector.getConnectionIds();

    if (connectionIds != null) {
      noOfClientsConnected = connectionIds.length;
    }

    logger.info("No. of RMI clients connected :: {}", noOfClientsConnected);

    AdminDistributedSystemJmxImpl adminDSJmx = (AdminDistributedSystemJmxImpl) system;

    adminDSJmx.setRmiClientCountZero(noOfClientsConnected == 0);
  }

  @Override
  public String toString() {
    return "AgentImpl["
        + "config=" + agentConfig.toProperties().toString()
        + "; mbeanName=" + mbeanName
        + "; modelMBean=" + modelMBean
        + "; objectName=" + objectName
        + "; propertyFile=" + propertyFile
        + ": rmiConnector=" + rmiConnector
        + "]";
  }

  /**
   * Process the String form of a hostname to make it comply with Jmx URL restrictions. Namely wrap
   * IPv6 literal address with "[", "]"
   *
   * @param hostname the name to safeguard.
   * @return a string representation suitable for use in a Jmx connection URL
   */
  private static String applyRFC2732(String hostname) {
    if (hostname.indexOf(":") != -1) {
      // Assuming an IPv6 literal because of the ':'
      return "[" + hostname + "]";
    }
    return hostname;
  }
}


/**
 * Adapter class for NotificationListener that listens to notifications of type
 * javax.management.remote.JMXConnectionNotification
 *
 * @since GemFire 6.0
 */
class ConnectionNotificationAdapter implements NotificationListener {
  private static final Logger logger = LogService.getLogger();

  /**
   * If the handback object passed is an AgentImpl, updates the JMX client count
   *
   * @param notification JMXConnectionNotification for change in client connection status
   * @param handback An opaque object which helps the listener to associate information regarding
   *        the MBean emitter. This object is passed to the MBean during the addListener call and
   *        resent, without modification, to the listener. The MBean object should not use or modify
   *        the object. (NOTE: copied from javax.management.NotificationListener)
   */
  @Override
  @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "BC_UNCONFIRMED_CAST",
      justification = "Only JMXConnectionNotification instances are used.")
  public void handleNotification(Notification notification, Object handback) {
    if (handback instanceof AgentImpl) {
      AgentImpl agent = (AgentImpl) handback;

      JMXConnectionNotification jmxNotifn = (JMXConnectionNotification) notification;

      if (logger.isDebugEnabled()) {
        logger.debug("Connection notification for connection id : '{}'",
            jmxNotifn.getConnectionId());
      }

      agent.updateRmiClientsCount();
    }
  }
}


/**
 * Filters out the notifications of the type JMXConnectionNotification.OPENED,
 * JMXConnectionNotification.CLOSED and JMXConnectionNotification.FAILED.
 *
 * @since GemFire 6.0
 */
class ConnectionNotificationFilterImpl implements NotificationFilter {

  /**
   * Default serialVersionUID
   */
  private static final long serialVersionUID = 1L;

  /**
   * Invoked before sending the specified notification to the listener. Returns whether the given
   * notification is to be sent to the listener.
   *
   * @param notification The notification to be sent.
   * @return true if the notification has to be sent to the listener, false otherwise.
   */
  @Override
  public boolean isNotificationEnabled(Notification notification) {
    return notification.getType().equals(JMXConnectionNotification.OPENED)
        || notification.getType().equals(JMXConnectionNotification.CLOSED)
        || notification.getType().equals(JMXConnectionNotification.FAILED);
  }
}
