/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 *      Portions Copyright 2014-2015 ForgeRock AS
 */
package org.opends.server.extensions;

import org.forgerock.i18n.LocalizableMessage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.DynamicMBean;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanConstructorInfo;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanServer;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.ObjectName;

import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.server.AlertHandlerCfg;
import org.opends.server.admin.std.server.JMXAlertHandlerCfg;
import org.opends.server.api.AlertGenerator;
import org.opends.server.api.AlertHandler;
import org.opends.server.api.DirectoryServerMBean;
import org.forgerock.opendj.config.server.ConfigException;
import org.opends.server.config.JMXMBean;
import org.opends.server.core.DirectoryServer;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.config.server.ConfigChangeResult;
import org.opends.server.types.DN;
import org.opends.server.types.InitializationException;
import static org.opends.messages.ConfigMessages.*;
import static org.opends.messages.ExtensionMessages.*;
import static org.opends.server.util.ServerConstants.*;

/**
 * This class provides an implementation of a Directory Server alert handler
 * that will send alerts using JMX notifications.
 */
public class JMXAlertHandler
       extends NotificationBroadcasterSupport
       implements AlertHandler<JMXAlertHandlerCfg>,
                  ConfigurationChangeListener<JMXAlertHandlerCfg>, DynamicMBean,
                  DirectoryServerMBean
{
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();

  /** The fully-qualified name of this class. */
  private static final String CLASS_NAME =
       "org.opends.server.extensions.JMXAlertHandler";



  /** The current configuration for this alert handler. */
  private AlertHandlerCfg currentConfig;

  /** The sequence number generator used for this alert handler. */
  private AtomicLong sequenceNumber;

  /**
   * The DN of the configuration entry with which this alert handler is
   * associated.
   */
  private DN configEntryDN;

  /** The JMX object name used for this JMX alert handler. */
  private ObjectName objectName;



  /**
   * Creates a new instance of this JMX alert handler.  No initialization should
   * be done here, as it should all be performed in the
   * <CODE>initializeAlertHandler</CODE> method.
   */
  public JMXAlertHandler()
  {
    super();
  }



  /** {@inheritDoc} */
  public void initializeAlertHandler(JMXAlertHandlerCfg configuration)
       throws ConfigException, InitializationException
  {
    sequenceNumber = new AtomicLong(1);

    if (configuration == null)
    {
      configEntryDN = null;
    }
    else
    {
      configEntryDN = configuration.dn();
    }

    MBeanServer mBeanServer = DirectoryServer.getJMXMBeanServer();
    if (mBeanServer != null)
    {
      try
      {
        String nameStr = MBEAN_BASE_DOMAIN + ":type=JMXAlertHandler";
        objectName = new ObjectName(nameStr);
        if (mBeanServer.isRegistered(objectName))
        {
          mBeanServer.unregisterMBean(objectName);
        }

        mBeanServer.registerMBean(this, objectName);
      }
      catch (Exception e)
      {
        logger.traceException(e);

        LocalizableMessage message = ERR_JMX_ALERT_HANDLER_CANNOT_REGISTER.get(e);
        throw new InitializationException(message, e);
      }
    }

    if (configuration != null)
    {
      configuration.addJMXChangeListener(this);
      currentConfig = configuration;
    }
  }



  /** {@inheritDoc} */
  public AlertHandlerCfg getAlertHandlerConfiguration()
  {
    return currentConfig;
  }



  /** {@inheritDoc} */
  public boolean isConfigurationAcceptable(AlertHandlerCfg configuration,
                                           List<LocalizableMessage> unacceptableReasons)
  {
    JMXAlertHandlerCfg cfg = (JMXAlertHandlerCfg) configuration;
    return isConfigurationChangeAcceptable(cfg, unacceptableReasons);
  }



  /** {@inheritDoc} */
  public void finalizeAlertHandler()
  {
    // No action is required.
  }



  /**
   * Retrieves the JMX object name for this JMX alert handler.
   *
   * @return  The JMX object name for this JMX alert handler.
   */
  public ObjectName getObjectName()
  {
    return objectName;
  }



  /** {@inheritDoc} */
  public void sendAlertNotification(AlertGenerator generator, String alertType,
                                    LocalizableMessage alertMessage)
  {
    sendNotification(new Notification(alertType, generator.getClassName(),
                                      sequenceNumber.getAndIncrement(),
                                      System.currentTimeMillis(),
                                      alertMessage.toString()));
  }



  /**
   * Retrieves information about the types of JMX notifications that may be
   * generated.
   *
   * @return  Information about the types of JMX notifications that may be
   *          generated.
   */
  public MBeanNotificationInfo[] getNotificationInfo()
  {
    ArrayList<MBeanNotificationInfo> notifications = new ArrayList<>();
    ConcurrentHashMap<DN,JMXMBean> mBeans = DirectoryServer.getJMXMBeans();
    for (JMXMBean mBean : mBeans.values())
    {
      MBeanInfo mBeanInfo = mBean.getMBeanInfo();
      Collections.addAll(notifications, mBeanInfo.getNotifications());
    }

    return notifications.toArray(new MBeanNotificationInfo[notifications.size()]);
  }



  /**
   * Obtain the value of a specific attribute of the Dynamic MBean.
   *
   * @param  attribute  The name of the attribute to be retrieved.
   *
   * @return  The requested MBean attribute.
   *
   * @throws  AttributeNotFoundException  If the specified attribute is not
   *                                      associated with this MBean.
   */
  public Attribute getAttribute(String attribute)
         throws AttributeNotFoundException
  {
    // There are no attributes for this MBean.
    LocalizableMessage message = ERR_CONFIG_JMX_ATTR_NO_ATTR.get(configEntryDN, attribute);
    throw new AttributeNotFoundException(message.toString());
  }



  /**
   * Set the value of a specific attribute of the Dynamic MBean.
   *
   * @param  attribute  The identification of the attribute to be set and the
   *                    value it is to be set to.
   *
   * @throws  AttributeNotFoundException  If the specified attribute is not
   *                                       associated with this MBean.
   *
   * @throws  InvalidAttributeValueException  If the provided value is not
   *                                          acceptable for this MBean.
   */
  public void setAttribute(Attribute attribute)
         throws AttributeNotFoundException, InvalidAttributeValueException
  {
    // There are no attributes for this MBean.
    LocalizableMessage message = ERR_CONFIG_JMX_ATTR_NO_ATTR.get(configEntryDN, attribute);
    throw new AttributeNotFoundException(message.toString());
  }



  /**
   * Get the values of several attributes of the Dynamic MBean.
   *
   * @param  attributes  A list of the attributes to be retrieved.
   *
   * @return  The list of attributes retrieved.
   */
  public AttributeList getAttributes(String[] attributes)
  {
    // There are no attributes for this MBean.
    return new AttributeList();
  }



  /**
   * Sets the values of several attributes of the Dynamic MBean.
   *
   * @param  attributes  A list of attributes:  The identification of the
   *                     attributes to be set and the values they are to be set
   *                     to.
   *
   * @return  The list of attributes that were set with their new values.
   */
  public AttributeList setAttributes(AttributeList attributes)
  {
    // There are no attributes for this MBean.
    return new AttributeList();
  }



  /**
   * Allows an action to be invoked on the Dynamic MBean.
   *
   * @param  actionName  The name of the action to be invoked.
   * @param  params      An array containing the parameters to be set when the
   *                     action is invoked.
   * @param  signature   An array containing the signature of the action.  The
   *                     class objects will be loaded through the same class
   *                     loader as the one used for loading the MBean on which
   *                     action is invoked.
   *
   * @return  The object returned by the action, which represents the result of
   *          invoking the action on the MBean specified.
   *
   * @throws  MBeanException  If a problem is encountered while invoking the
   *                          method.
   */
  public Object invoke(String actionName, Object[] params, String[] signature)
         throws MBeanException
  {
    // There are no invokable components for this MBean.
    StringBuilder buffer = new StringBuilder();
    buffer.append(actionName);
    buffer.append("(");

    if (signature.length > 0)
    {
      buffer.append(signature[0]);

      for (int i=1; i < signature.length; i++)
      {
        buffer.append(", ");
        buffer.append(signature[i]);
      }
    }

    buffer.append(")");

    LocalizableMessage message = ERR_CONFIG_JMX_NO_METHOD.get(buffer, configEntryDN);
    throw new MBeanException(new ConfigException(message));
  }



  /**
   * Provides the exposed attributes and actions of the Dynamic MBean using an
   * MBeanInfo object.
   *
   * @return  An instance of <CODE>MBeanInfo</CODE> allowing all attributes and
   *          actions exposed by this Dynamic MBean to be retrieved.
   */
  public MBeanInfo getMBeanInfo()
  {
    return new MBeanInfo(CLASS_NAME, "JMX Alert Handler",
                         new MBeanAttributeInfo[0], new MBeanConstructorInfo[0],
                         new MBeanOperationInfo[0], getNotificationInfo());
  }



  /** {@inheritDoc} */
  public boolean isConfigurationChangeAcceptable(
                      JMXAlertHandlerCfg configuration,
                      List<LocalizableMessage> unacceptableReasons)
  {
    return true;
  }



  /** {@inheritDoc} */
  public ConfigChangeResult applyConfigurationChange(
                                        JMXAlertHandlerCfg configuration)
  {
    currentConfig = configuration;
    return new ConfigChangeResult();
  }
}
