/*
 * 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 2007-2008 Sun Microsystems, Inc.
 *      Portions Copyright 2015 ForgeRock AS.
 */
package org.opends.server.admin.client.ldap;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.SortedSet;
import java.util.TreeSet;

import org.opends.server.TestCaseUtils;
import org.opends.server.admin.AdminTestCase;
import org.opends.server.admin.PropertyException;
import org.opends.server.admin.TestCfg;
import org.opends.server.admin.TestChildCfgClient;
import org.opends.server.admin.TestChildCfgDefn;
import org.opends.server.admin.TestParentCfgClient;
import org.opends.server.admin.client.ManagedObject;
import org.opends.server.admin.client.ManagedObjectDecodingException;
import org.opends.server.admin.client.ManagementContext;
import org.opends.server.admin.std.client.RootCfgClient;
import org.opends.server.core.DirectoryServer;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

/**
 * Test cases for aggregations on the client-side.
 */
@Test(sequential = true)
public class AggregationClientTest extends AdminTestCase {

  /** Test LDIF. */
  private static final String[] TEST_LDIF = new String[] {
      // Base entries.
      "dn: cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-branch",
      "cn: config",
      "",
      "dn: cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-branch",
      "cn: test-parents",
      "",
      // Parent 1 - uses default values for
      // optional-multi-valued-dn-property.
      "dn: cn=test parent 1,cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-test-parent-dummy",
      "cn: test parent 1",
      "ds-cfg-enabled: true",
      "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
      "ds-cfg-attribute-type: description",
      "",
      // Child base entry.
      "dn:cn=test children,cn=test parent 1,cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-branch",
      "cn: multiple children",
      "",
      // Child 1 has no references.
      "dn: cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-test-child-dummy",
      "cn: test child 1",
      "ds-cfg-enabled: true",
      "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
      "ds-cfg-attribute-type: description",
      "",
      // Child 2 has a single valid reference.
      "dn: cn=test child 2,cn=test children,cn=test parent 1,cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-test-child-dummy",
      "cn: test child 2",
      "ds-cfg-enabled: true",
      "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
      "ds-cfg-attribute-type: description",
      "ds-cfg-rotation-policy: cn=LDAP Connection Handler, cn=connection handlers, cn=config",
      "",
      // Child 3 has a multiple valid references.
      "dn: cn=test child 3,cn=test children,cn=test parent 1,cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-test-child-dummy",
      "cn: test child 3",
      "ds-cfg-enabled: true",
      "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
      "ds-cfg-attribute-type: description",
      "ds-cfg-rotation-policy: cn=LDAP Connection Handler, cn=connection handlers, cn=config",
      "ds-cfg-rotation-policy: cn=LDAPS Connection Handler, cn=connection handlers, cn=config",
      "",
      // Child 4 has a single bad reference.
      "dn: cn=test child 4,cn=test children,cn=test parent 1,cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-test-child-dummy",
      "cn: test child 4",
      "ds-cfg-enabled: true",
      "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
      "ds-cfg-attribute-type: description",
      "ds-cfg-rotation-policy: cn=LDAP Connection Handler, cn=bad rdn, cn=config",
      "",
      "dn: cn=Connection Handlers,cn=config",
      "objectClass: top",
      "objectClass: ds-cfg-branch",
      "cn: Connection Handlers",
      "",
      "dn: cn=LDAP Connection Handler,cn=Connection Handlers,cn=config",
      "objectClass: top",
      "objectClass: ds-cfg-connection-handler",
      "objectClass: ds-cfg-ldap-connection-handler",
      "cn: LDAP Connection Handler",
      "ds-cfg-java-class: org.opends.server.protocols.ldap.LDAPConnectionHandler",
      "ds-cfg-enabled: true",
      "ds-cfg-listen-address: 0.0.0.0",
      "ds-cfg-listen-port: 389",
      "",
      "dn: cn=LDAPS Connection Handler,cn=Connection Handlers,cn=config",
      "objectClass: top",
      "objectClass: ds-cfg-connection-handler",
      "objectClass: ds-cfg-ldap-connection-handler",
      "cn: LDAPS Connection Handler",
      "ds-cfg-java-class: org.opends.server.protocols.ldap.LDAPConnectionHandler",
      "ds-cfg-enabled: false",
      "ds-cfg-listen-address: 0.0.0.0",
      "ds-cfg-listen-port: 636",
      "ds-cfg-use-ssl: true",
      "ds-cfg-ssl-client-auth-policy: optional",
      "ds-cfg-ssl-cert-nickname: server-cert",
      "ds-cfg-key-manager-provider: cn=JKS,cn=Key Manager Providers,cn=config",
      "ds-cfg-trust-manager-provider: cn=JKS,cn=Trust Manager Providers,cn=config",
      "",
      "dn: cn=JMX Connection Handler,cn=Connection Handlers,cn=config",
      "objectClass: top",
      "objectClass: ds-cfg-connection-handler",
      "objectClass: ds-cfg-jmx-connection-handler",
      "cn: JMX Connection Handler",
      "ds-cfg-java-class: org.opends.server.protocols.jmx.JmxConnectionHandler",
      "ds-cfg-enabled: false",
      "ds-cfg-listen-port: 1689",
      ""
  };



  /**
   * Sets up tests
   *
   * @throws Exception
   *           If the server could not be initialized.
   */
  @BeforeClass
  public void setUp() throws Exception {
    // This test suite depends on having the schema available, so
    // we'll start the server.
    TestCaseUtils.startServer();
    TestCfg.setUp();
  }



  /**
   * Tears down test environment.
   */
  @AfterClass
  public void tearDown() {
    TestCfg.cleanup();
  }



  /**
   * Tests that aggregation contains no values when it contains does
   * not contain any DN attribute values.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testAggregationEmpty() throws Exception {
    MockLDAPConnection c = new MockLDAPConnection();
    c.importLDIF(TEST_LDIF);
    ManagementContext ctx = LDAPManagementContext.createFromContext(c);
    TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
    TestChildCfgClient child = parent.getTestChild("test child 1");
    assertSetEquals(child.getAggregationProperty(), new String[0]);
  }



  /**
   * Tests that aggregation contains single valid value when it
   * contains a single valid DN attribute values.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testAggregationSingle() throws Exception {
    MockLDAPConnection c = new MockLDAPConnection();
    c.importLDIF(TEST_LDIF);
    ManagementContext ctx = LDAPManagementContext.createFromContext(c);
    TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
    TestChildCfgClient child = parent.getTestChild("test child 2");

    // Test normalization.
    assertSetEquals(child.getAggregationProperty(), "LDAP Connection Handler");
    assertSetEquals(child.getAggregationProperty(),
        "  LDAP   Connection  Handler ");
    assertSetEquals(child.getAggregationProperty(),
        "  ldap connection HANDLER ");
  }



  /**
   * Tests that aggregation contains multiple valid values when it
   * contains a multiple valid DN attribute values.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testAggregationMultiple() throws Exception {
    MockLDAPConnection c = new MockLDAPConnection();
    c.importLDIF(TEST_LDIF);
    ManagementContext ctx = LDAPManagementContext.createFromContext(c);
    TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
    TestChildCfgClient child = parent.getTestChild("test child 3");
    assertSetEquals(child.getAggregationProperty(), "LDAPS Connection Handler",
        "LDAP Connection Handler");
  }



  /**
   * Tests that aggregation is rejected when the LDAP DN contains a
   * valid RDN but an invalid parent DN.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testAggregationBadBaseDN() throws Exception {
    MockLDAPConnection c = new MockLDAPConnection();
    c.importLDIF(TEST_LDIF);
    ManagementContext ctx = LDAPManagementContext.createFromContext(c);
    TestParentCfgClient parent = getTestParent(ctx, "test parent 1");

    try {
      parent.getTestChild("test child 4");
      Assert.fail("Unexpectedly retrieved test child 4"
          + " when it had a bad aggregation value");
    } catch (ManagedObjectDecodingException e) {
      Collection<PropertyException> causes = e.getCauses();
      Assert.assertEquals(causes.size(), 1);

      Throwable cause = causes.iterator().next();
      if (cause instanceof PropertyException) {
        PropertyException pe = (PropertyException) cause;
        Assert.assertEquals(pe.getPropertyDefinition(), TestChildCfgDefn
            .getInstance().getAggregationPropertyPropertyDefinition());
      } else {
        // Got an unexpected cause.
        throw e;
      }
    }
  }



  /**
   * Tests creation of a child managed object with a single reference.
   *
   * @throws Exception
   *           If an unexpected error occurred.
   */
  @Test
  public void testCreateChildManagedObject() throws Exception {
    CreateEntryMockLDAPConnection c = new CreateEntryMockLDAPConnection(
        "cn=test child new,cn=test children,cn=test parent 1,cn=test parents,cn=config");
    c.importLDIF(TEST_LDIF);
    c.addExpectedAttribute("cn", "test child new");
    c.addExpectedAttribute("objectclass", "top", "ds-cfg-test-child-dummy");
    c.addExpectedAttribute("ds-cfg-enabled", "true");
    c.addExpectedAttribute("ds-cfg-java-class",
        "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
    c.addExpectedAttribute("ds-cfg-attribute-type", "description");
    c.addExpectedAttribute("ds-cfg-rotation-policy",
        "cn=LDAP Connection Handler,cn=connection handlers,cn=config");

    ManagementContext ctx = LDAPManagementContext.createFromContext(c);
    TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
    TestChildCfgClient child = parent.createTestChild(TestChildCfgDefn.getInstance(), "test child new", null);
    child.setMandatoryBooleanProperty(true);
    child.setMandatoryReadOnlyAttributeTypeProperty(DirectoryServer.getAttributeTypeOrNull("description"));
    child.setAggregationProperty(Collections.singleton("LDAP Connection Handler"));
    child.commit();

    c.assertEntryIsCreated();
  }



  /**
   * Tests modification of a child managed object so that it has a
   * different reference.
   *
   * @throws Exception
   *           If an unexpected error occurred.
   */
  @Test
  public void testModifyChildManagedObject() throws Exception {
    ModifyEntryMockLDAPConnection c = new ModifyEntryMockLDAPConnection(
        "cn=test child 2,cn=test children,cn=test parent 1,cn=test parents,cn=config");
    c.importLDIF(TEST_LDIF);
    c.addExpectedModification("ds-cfg-rotation-policy",
        "cn=LDAPS Connection Handler,cn=connection handlers,cn=config",
        "cn=JMX Connection Handler,cn=connection handlers,cn=config");
    ManagementContext ctx = LDAPManagementContext.createFromContext(c);
    TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
    TestChildCfgClient child = parent.getTestChild("test child 2");
    child.setAggregationProperty(Arrays.asList("LDAPS Connection Handler",
        "JMX Connection Handler"));
    child.commit();
    Assert.assertTrue(c.isEntryModified());
  }



  /** Retrieve the named test parent managed object. */
  private TestParentCfgClient getTestParent(ManagementContext context, String name) throws Exception {
    ManagedObject<RootCfgClient> root = context.getRootConfigurationManagedObject();
    return root.getChild(TestCfg.getTestOneToManyParentRelationDefinition(),
        name).getConfiguration();
  }



  /** Asserts that the actual set of DNs contains the expected values. */
  private void assertSetEquals(SortedSet<String> actual, String... expected) {
    SortedSet<String> values = new TreeSet<>(TestChildCfgDefn
        .getInstance().getAggregationPropertyPropertyDefinition());
    if (expected != null) {
      Collections.addAll(values, expected);
    }
    Assert.assertEquals((Object) actual, (Object) values);
  }
}
