/*
 * 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 2014-2015 ForgeRock AS.
 *
 *      Portions Copyrighted 2019 OGIS-RI Co., Ltd.
 */
package org.opends.server.core;

import static org.mockito.Mockito.*;
import static org.opends.server.ServerContextBuilder.*;
import static org.testng.Assert.*;

import java.io.File;
import java.util.Arrays;
import java.util.Set;

import org.forgerock.i18n.LocalizableMessageBuilder;
import org.forgerock.opendj.config.server.ConfigChangeResult;
import org.forgerock.opendj.config.server.spi.ConfigAddListener;
import org.forgerock.opendj.config.server.spi.ConfigChangeListener;
import org.forgerock.opendj.config.server.spi.ConfigDeleteListener;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.Entry;
import org.forgerock.opendj.ldap.LinkedHashMapEntry;
import org.forgerock.opendj.ldap.ResultCode;
import org.opends.server.TestCaseUtils;
import org.opends.server.config.ConfigConstants;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.InitializationException;
import org.testng.annotations.Test;

@SuppressWarnings("javadoc")
public class ConfigurationHandlerTestCase extends CoreTestCase
{
  private static final DN DN_CONFIG = DN.valueOf(ConfigConstants.DN_CONFIG_ROOT);
  private static final DN DN_SCHEMA_PROVIDERS = DN.valueOf("cn=Schema Providers,cn=config");
  private static final DN DN_CORE_SCHEMA = DN.valueOf("cn=Core Schema,cn=Schema Providers,cn=config");

  /** Returns the configuration handler fully initialized from configuration file. */
  private ConfigurationHandler getConfigurationHandler()
      throws InitializationException
  {
    final ServerContext context = aServerContext().
        schemaDirectory(new File(TestCaseUtils.getBuildRoot(), "resource/schema")).
        configFile(TestCaseUtils.getTestResource("config-small.ldif")).
        build();

    final ConfigurationHandler configHandler = new ConfigurationHandler(context);
    configHandler.initialize();
    return configHandler;
  }

  @Test
  public void testInitializeConfiguration() throws Exception
  {
    ConfigurationHandler configHandler = getConfigurationHandler();

    assertTrue(configHandler.hasEntry(DN_CONFIG));
  }

  @Test
  public void testGetEntry() throws Exception
  {
    ConfigurationHandler configHandler = getConfigurationHandler();

    Entry entry = configHandler.getEntry(DN_SCHEMA_PROVIDERS);
    assertTrue(entry.containsAttribute("objectclass", "top", "ds-cfg-branch"));
  }

  @Test
  public void testGetChildren() throws Exception
  {
    ConfigurationHandler configHandler = getConfigurationHandler();

    Set<DN> dns = configHandler.getChildren(DN_SCHEMA_PROVIDERS);
    assertTrue(dns.contains(DN_CORE_SCHEMA));
  }

  @Test
  public void testNumSubordinates() throws Exception
  {
    ConfigurationHandler configHandler = getConfigurationHandler();

    long numSubordinates = configHandler.numSubordinates(DN_SCHEMA_PROVIDERS, false);
    assertEquals(numSubordinates, 1);

    numSubordinates = configHandler.numSubordinates(DN_CONFIG, true);
    assertEquals(numSubordinates, 2);
  }

  @Test
  public void testRegisterChangeListener() throws Exception
  {
    ConfigurationHandler configHandler = getConfigurationHandler();
    ConfigChangeListener listener1 = mock(ConfigChangeListener.class);
    ConfigChangeListener listener2 = mock(ConfigChangeListener.class);

    configHandler.registerChangeListener(DN_SCHEMA_PROVIDERS, listener1);
    configHandler.registerChangeListener(DN_SCHEMA_PROVIDERS, listener2);

    assertEquals(configHandler.getChangeListeners(DN_SCHEMA_PROVIDERS), Arrays.asList(listener1 ,listener2));
  }

  @Test
  public void testRegisterDeregisterChangeListener() throws Exception
  {
    ConfigurationHandler configHandler = getConfigurationHandler();
    ConfigChangeListener listener1 = mock(ConfigChangeListener.class);
    ConfigChangeListener listener2 = mock(ConfigChangeListener.class);

    configHandler.registerChangeListener(DN_SCHEMA_PROVIDERS, listener1);
    configHandler.registerChangeListener(DN_SCHEMA_PROVIDERS, listener2);
    configHandler.deregisterChangeListener(DN_SCHEMA_PROVIDERS, listener1);

    assertEquals(configHandler.getChangeListeners(DN_SCHEMA_PROVIDERS), Arrays.asList(listener2));
  }

  @Test
  public void testRegisterAddListener() throws Exception
  {
    ConfigurationHandler configHandler = getConfigurationHandler();
    ConfigAddListener listener1 = mock(ConfigAddListener.class);
    ConfigAddListener listener2 = mock(ConfigAddListener.class);

    configHandler.registerAddListener(DN_SCHEMA_PROVIDERS, listener1);
    configHandler.registerAddListener(DN_SCHEMA_PROVIDERS, listener2);

    assertEquals(configHandler.getAddListeners(DN_SCHEMA_PROVIDERS), Arrays.asList(listener1 ,listener2));
  }

  @Test
  public void testRegisterDeleteListener() throws Exception
  {
    ConfigurationHandler configHandler = getConfigurationHandler();
    ConfigDeleteListener listener1 = mock(ConfigDeleteListener.class);
    ConfigDeleteListener listener2 = mock(ConfigDeleteListener.class);

    configHandler.registerDeleteListener(DN_SCHEMA_PROVIDERS, listener1);
    configHandler.registerDeleteListener(DN_SCHEMA_PROVIDERS, listener2);

    assertEquals(configHandler.getDeleteListeners(DN_SCHEMA_PROVIDERS), Arrays.asList(listener1 ,listener2));
  }

  @Test
  public void testAddEntry() throws Exception
  {
    ConfigurationHandler configHandler = getConfigurationHandler();
    String dn = "cn=New schema provider,cn=Schema Providers,cn=config";

    configHandler.addEntry(new LinkedHashMapEntry(dn));

    assertTrue(configHandler.hasEntry(DN.valueOf(dn)));
  }

  @Test(expectedExceptions=DirectoryException.class)
  public void testAddEntryExistingEntry() throws Exception
  {
    ConfigurationHandler configHandler = getConfigurationHandler();
    configHandler.addEntry(new LinkedHashMapEntry(DN_CORE_SCHEMA));
  }

  /** TODO : disabled because fail when converting to server DN. Re-enable once migrated to SDK DN. */
  @Test(enabled=false, expectedExceptions=DirectoryException.class)
  public void testAddEntryParentUnknown() throws Exception
  {
    ConfigurationHandler configHandler = getConfigurationHandler();
    configHandler.addEntry(new LinkedHashMapEntry("cn=Core Schema,cn=Schema Providers,cn=Providers,cn=config"));
  }

  @Test(expectedExceptions=DirectoryException.class)
  public void testAddEntryNoParent() throws Exception
  {
    ConfigurationHandler configHandler = getConfigurationHandler();

    configHandler.addEntry(new LinkedHashMapEntry(DN.rootDN()));
  }

  @Test
  public void testAddListenerWithAddEntry() throws Exception
  {
    String dn = "cn=New schema provider,cn=Schema Providers,cn=config";
    ConfigurationHandler configHandler = getConfigurationHandler();

    ConfigAddListener listener = mock(ConfigAddListener.class);
    configHandler.registerAddListener(DN_SCHEMA_PROVIDERS, listener);
    when(listener.configAddIsAcceptable(any(Entry.class), any(LocalizableMessageBuilder.class))).
      thenReturn(true);
    when(listener.applyConfigurationAdd(any(Entry.class))).
      thenReturn(new ConfigChangeResult());

    configHandler.addEntry(new LinkedHashMapEntry(dn));

    // ensure apply is called for listener
    verify(listener).applyConfigurationAdd(configHandler.getEntry(DN.valueOf(dn)));
  }

  @Test(expectedExceptions=DirectoryException.class)
  public void testAddListenerWithAddEntryWhenConfigNotAcceptable() throws Exception
  {
    ConfigurationHandler configHandler = getConfigurationHandler();
    ConfigAddListener listener = mock(ConfigAddListener.class);
    configHandler.registerAddListener(DN_SCHEMA_PROVIDERS, listener);
    when(listener.configAddIsAcceptable(any(Entry.class), any(LocalizableMessageBuilder.class))).
    thenReturn(false);

    configHandler.addEntry(new LinkedHashMapEntry("cn=New schema provider,cn=Schema Providers,cn=config"));
  }

  @Test(expectedExceptions=DirectoryException.class)
  public void testAddListenerWithAddEntryWhenFailureApplyingConfig() throws Exception
  {
    final ConfigChangeResult ccr = new ConfigChangeResult();
    ccr.setResultCode(ResultCode.OTHER);
    ConfigurationHandler configHandler = getConfigurationHandler();
    ConfigAddListener listener = mock(ConfigAddListener.class);
    configHandler.registerAddListener(DN_SCHEMA_PROVIDERS, listener);
    when(listener.configAddIsAcceptable(any(Entry.class), any(LocalizableMessageBuilder.class))).
      thenReturn(true);
    when(listener.applyConfigurationAdd(any(Entry.class))).
      thenReturn(ccr);

    configHandler.addEntry(new LinkedHashMapEntry("cn=New schema provider,cn=Schema Providers,cn=config"));
  }

  @Test
  public void testDeleteEntry() throws Exception
  {
    ConfigurationHandler configHandler = getConfigurationHandler();

    configHandler.deleteEntry(DN_CORE_SCHEMA);

    assertFalse(configHandler.hasEntry(DN_CORE_SCHEMA));
  }

  /** TODO : disabled because fail when converting to server DN. Re-enable once migrated to SDK DN. */
  @Test(enabled=false, expectedExceptions=DirectoryException.class)
  public void testDeleteEntryUnexistingEntry() throws Exception
  {
    ConfigurationHandler configHandler = getConfigurationHandler();
    configHandler.deleteEntry(DN.valueOf("cn=Unexisting provider,cn=Schema Providers,cn=config"));
  }

  @Test(enabled=false, expectedExceptions=DirectoryException.class)
  public void testDeleteEntryWithChildren() throws Exception
  {
    ConfigurationHandler configHandler = getConfigurationHandler();
    configHandler.deleteEntry(DN_SCHEMA_PROVIDERS);
  }

  /** TODO : disabled because fail when converting to server DN. Re-enable once migrated to SDK DN. */
  @Test(enabled=false, expectedExceptions=DirectoryException.class)
  public void testDeleteEntryUnknownParent() throws Exception
  {
    ConfigurationHandler configHandler = getConfigurationHandler();
    configHandler.deleteEntry(DN.valueOf("cn=Core Schema,cn=Schema Providers,cn=Providers,cn=config"));
  }

  @Test
  public void testDeleteListenerWithDeleteEntry() throws Exception
  {
    ConfigurationHandler configHandler = getConfigurationHandler();

    ConfigDeleteListener listener = mock(ConfigDeleteListener.class);
    configHandler.registerDeleteListener(DN_SCHEMA_PROVIDERS, listener);
    Entry entryToDelete = configHandler.getEntry(DN_CORE_SCHEMA);
    when(listener.configDeleteIsAcceptable(any(Entry.class), any(LocalizableMessageBuilder.class))).
      thenReturn(true);
    when(listener.applyConfigurationDelete(any(Entry.class))).
      thenReturn(new ConfigChangeResult());

    configHandler.deleteEntry(DN_CORE_SCHEMA);

    // ensure apply is called for listener
    verify(listener).applyConfigurationDelete(entryToDelete);
  }

  @Test(expectedExceptions=DirectoryException.class)
  public void testDeleteListenerWithDeleteEntryWhenConfigNotAcceptable() throws Exception
  {
    ConfigurationHandler configHandler = getConfigurationHandler();

    ConfigDeleteListener listener = mock(ConfigDeleteListener.class);
    configHandler.registerDeleteListener(DN_SCHEMA_PROVIDERS, listener);
    when(listener.configDeleteIsAcceptable(any(Entry.class), any(LocalizableMessageBuilder.class))).
      thenReturn(false);

    configHandler.deleteEntry(DN_CORE_SCHEMA);
  }

  @Test(expectedExceptions=DirectoryException.class)
  public void testDeleteListenerWithDeleteEntryWhenFailureApplyingConfig() throws Exception
  {
    ConfigurationHandler configHandler = getConfigurationHandler();

    final ConfigChangeResult ccr = new ConfigChangeResult();
    ccr.setResultCode(ResultCode.OTHER);

    ConfigDeleteListener listener = mock(ConfigDeleteListener.class);
    configHandler.registerDeleteListener(DN_SCHEMA_PROVIDERS, listener);
    when(listener.configDeleteIsAcceptable(any(Entry.class), any(LocalizableMessageBuilder.class))).
      thenReturn(true);
    when(listener.applyConfigurationDelete(any(Entry.class))).
      thenReturn(ccr);

    configHandler.deleteEntry(DN_CORE_SCHEMA);
  }

  @Test
  public void testReplaceEntry() throws Exception
  {
    ConfigurationHandler configHandler = getConfigurationHandler();
    String dn = DN_CORE_SCHEMA.toString();

    configHandler.replaceEntry(
        new LinkedHashMapEntry("dn: " + dn, "objectclass: ds-cfg-schema-provider", "ds-cfg-enabled: true"),
        new LinkedHashMapEntry("dn: " + dn, "objectclass: ds-cfg-schema-provider", "ds-cfg-enabled: false"));

    assertTrue(configHandler.hasEntry(DN_CORE_SCHEMA));
    assertEquals(configHandler.getEntry(DN_CORE_SCHEMA).getAttribute("ds-cfg-enabled").firstValueAsString(), "false");
  }

  @Test
  public void testChangeListenerWithReplaceEntry() throws Exception
  {
    ConfigurationHandler configHandler = getConfigurationHandler();

    ConfigChangeListener listener = mock(ConfigChangeListener.class);
    configHandler.registerChangeListener(DN_CORE_SCHEMA, listener);
    when(listener.configChangeIsAcceptable(any(Entry.class), any(LocalizableMessageBuilder.class))).
      thenReturn(true);
    when(listener.applyConfigurationChange(any(Entry.class))).
      thenReturn(new ConfigChangeResult());
    Entry oldEntry = configHandler.getEntry(DN_CORE_SCHEMA);

    configHandler.replaceEntry(oldEntry,
        new LinkedHashMapEntry("dn: " + DN_CORE_SCHEMA,
            "objectclass: ds-cfg-schema-provider",
            "ds-cfg-enabled: false"));

    // ensure apply is called for listener
    verify(listener).applyConfigurationChange(configHandler.getEntry(DN_CORE_SCHEMA));
  }

  @Test(expectedExceptions=DirectoryException.class)
  public void testChangeListenerWithReplaceEntryWhenConfigNotAcceptable() throws Exception
  {
    ConfigurationHandler configHandler = getConfigurationHandler();

    ConfigChangeListener listener = mock(ConfigChangeListener.class);
    configHandler.registerChangeListener(DN_CORE_SCHEMA, listener);
    when(listener.configChangeIsAcceptable(any(Entry.class), any(LocalizableMessageBuilder.class))).
      thenReturn(false);
    Entry oldEntry = configHandler.getEntry(DN_CORE_SCHEMA);

    configHandler.replaceEntry(oldEntry,
        new LinkedHashMapEntry("dn: " + DN_CORE_SCHEMA,
            "objectclass: ds-cfg-schema-provider",
            "ds-cfg-enabled: false"));
  }

  @Test(expectedExceptions=DirectoryException.class)
  public void testChangeListenerWithReplaceEntryWhenFailureApplyingConfig() throws Exception
  {
    ConfigurationHandler configHandler = getConfigurationHandler();

    final ConfigChangeResult ccr = new ConfigChangeResult();
    ccr.setResultCode(ResultCode.OTHER);

    ConfigChangeListener listener = mock(ConfigChangeListener.class);
    configHandler.registerChangeListener(DN_CORE_SCHEMA, listener);
    when(listener.configChangeIsAcceptable(any(Entry.class), any(LocalizableMessageBuilder.class))).
      thenReturn(true);
    when(listener.applyConfigurationChange(any(Entry.class))).
      thenReturn(ccr);
    Entry oldEntry = configHandler.getEntry(DN_CORE_SCHEMA);

    configHandler.replaceEntry(oldEntry,
        new LinkedHashMapEntry("dn: " + DN_CORE_SCHEMA,
            "objectclass: ds-cfg-schema-provider",
            "ds-cfg-enabled: false"));
  }

}
