/*
 * 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 2014-2015 ForgeRock AS.
 */
package org.forgerock.opendj.config.client.ldap;

import static org.fest.assertions.Assertions.assertThat;
import static org.forgerock.opendj.ldap.Connections.newInternalConnection;

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

import org.forgerock.opendj.config.AdminTestCase;
import org.forgerock.opendj.config.LDAPProfile;
import org.forgerock.opendj.config.PropertyException;
import org.forgerock.opendj.config.TestCfg;
import org.forgerock.opendj.config.TestChildCfgClient;
import org.forgerock.opendj.config.TestChildCfgDefn;
import org.forgerock.opendj.config.TestParentCfgClient;
import org.forgerock.opendj.config.client.ManagedObject;
import org.forgerock.opendj.config.client.ManagedObjectDecodingException;
import org.forgerock.opendj.config.client.ManagementContext;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.LinkedHashMapEntry;
import org.forgerock.opendj.ldap.MemoryBackend;
import org.forgerock.opendj.ldap.schema.Schema;
import org.forgerock.opendj.ldif.LDIFEntryReader;
import org.forgerock.opendj.server.config.client.RootCfgClient;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

@SuppressWarnings("javadoc")
@Test(singleThreaded = true)
public class AggregationClientTest extends AdminTestCase {

    /** Test LDIF. */
    private static final String[] TEST_LDIF = new String[] {
        // @formatter:off
        // Base entries.
        "dn:",
        "objectclass: top",
        "objectclass: ds-cfg-branch",
        "",
        "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=HTTP Connection Handler,cn=Connection Handlers,cn=config",
        "objectClass: top",
        "objectClass: ds-cfg-connection-handler",
        "objectClass: ds-cfg-http-connection-handler",
        "cn: HTTP Connection Handler",
        "ds-cfg-java-class: org.opends.server.protocols.http.HTTPConnectionHandler",
        "ds-cfg-enabled: false",
        "ds-cfg-listen-address: 0.0.0.0",
        "ds-cfg-listen-port: 8080",
        "",
        "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",
        "" };
    // @formatter:on

    @BeforeClass
    public void setUp() throws Exception {
        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 {
        MemoryBackend backend = new MemoryBackend(new LDIFEntryReader(TEST_LDIF));
        Connection c = newInternalConnection(backend);
        ManagementContext ctx =
                LDAPManagementContext.newManagementContext(c, LDAPProfile.getInstance());
        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 {
        MemoryBackend backend = new MemoryBackend(new LDIFEntryReader(TEST_LDIF));
        Connection c = newInternalConnection(backend);
        ManagementContext ctx =
                LDAPManagementContext.newManagementContext(c, LDAPProfile.getInstance());
        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 {
        MemoryBackend backend = new MemoryBackend(new LDIFEntryReader(TEST_LDIF));
        Connection c = newInternalConnection(backend);
        ManagementContext ctx =
                LDAPManagementContext.newManagementContext(c, LDAPProfile.getInstance());
        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 {
        MemoryBackend backend = new MemoryBackend(new LDIFEntryReader(TEST_LDIF));
        Connection c = newInternalConnection(backend);
        ManagementContext ctx =
                LDAPManagementContext.newManagementContext(c, LDAPProfile.getInstance());
        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 {
        MemoryBackend backend = new MemoryBackend(new LDIFEntryReader(TEST_LDIF));
        Connection c = newInternalConnection(backend);
        ManagementContext ctx =
                LDAPManagementContext.newManagementContext(c, LDAPProfile.getInstance());
        TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
        TestChildCfgClient child =
                parent.createTestChild(TestChildCfgDefn.getInstance(), "test child new", null);
        child.setMandatoryBooleanProperty(true);
        child.setMandatoryReadOnlyAttributeTypeProperty(Schema.getDefaultSchema().getAttributeType(
                "description"));
        child.setAggregationProperty(Collections.singleton("LDAP Connection Handler"));
        child.commit();

        String dn = "cn=test child new,cn=test children,cn=test parent 1,cn=test parents,cn=config";
        assertThat(backend.get(dn)).isEqualTo(new LinkedHashMapEntry(
                "dn: " + dn,
                "cn: test child new",
                "objectClass: top",
                "objectClass: ds-cfg-test-child-dummy",
                "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"));
    }

    /**
     * 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 {
        MemoryBackend backend = new MemoryBackend(new LDIFEntryReader(TEST_LDIF));
        Connection c = newInternalConnection(backend);
        ManagementContext ctx =
                LDAPManagementContext.newManagementContext(c, LDAPProfile.getInstance());
        TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
        TestChildCfgClient child = parent.getTestChild("test child 2");
        child.setAggregationProperty(Arrays.asList("JMX Connection Handler",
                "HTTP Connection Handler"));
        child.commit();

        String dn = "cn=test child 2,cn=test children,cn=test parent 1,cn=test parents,cn=config";
        assertThat(backend.get(dn).parseAttribute("ds-cfg-rotation-policy").asSetOfString())
                .containsOnly("cn=HTTP Connection Handler,cn=connection handlers, cn=config",
                        "cn=JMX Connection Handler,cn=connection handlers, cn=config");
    }

    /** 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) {
            for (String value : expected) {
                values.add(value);
            }
        }
        Assert.assertEquals((Object) actual, (Object) values);
    }

}
