/*
 * 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 2009-2010 Sun Microsystems, Inc.
 *      Portions Copyright 2011-2015 ForgeRock AS
 */
package org.opends.server.replication.plugin;

import java.util.*;

import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ModificationType;
import org.opends.server.TestCaseUtils;
import org.opends.server.backends.task.Task;
import org.opends.server.core.DirectoryServer;
import org.opends.server.replication.ReplicationTestCase;
import org.opends.server.replication.common.CSNGenerator;
import org.opends.server.replication.common.ServerStatus;
import org.opends.server.replication.protocol.AddMsg;
import org.opends.server.replication.protocol.ModifyDNMsg;
import org.opends.server.replication.protocol.ModifyMsg;
import org.opends.server.replication.server.ReplServerFakeConfiguration;
import org.opends.server.replication.server.ReplicationServer;
import org.opends.server.replication.service.FakeReplicationDomain;
import org.opends.server.replication.service.ReplicationDomain;
import org.opends.server.types.*;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import static org.opends.server.TestCaseUtils.*;
import static org.opends.server.util.CollectionUtils.*;
import static org.testng.Assert.*;

/**
 * Various tests around fractional replication
 */
@SuppressWarnings("javadoc")
public class FractionalReplicationTest extends ReplicationTestCase {

  /** The RS */
  private ReplicationServer replicationServer;
  /** RS port */
  private int replServerPort = -1;
  /** Represents the real domain to test (replays and filters) */
  private Entry fractionalDomainCfgEntry;
  /** The domain used to send updates to the real domain */
  private FakeReplicationDomain replicationDomain;

  /** Ids of servers */
  private static final int DS1_ID = 1; // fractional domain
  private static final int DS2_ID = 2; // fake domain
  private static final int RS_ID = 91; // replication server

  private final String testName = getClass().getSimpleName();

  /** Fractional mode */
  private static final int EXCLUDE_FRAC_MODE = 0;
  private static final int INCLUDE_FRAC_MODE = 1;

  private CSNGenerator gen;

  /** The tracer object for the debug logger */
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();

  /** Number of seconds before generating an error if some conditions not met */
  private static final int TIMEOUT = 10000;

  /** Uuid of the manipulated entry */
  private static final String ENTRY_UUID =
    "11111111-1111-1111-1111-111111111111";
  private static final String ENTRY_UUID2 =
    "22222222-2222-2222-2222-222222222222";
  private static final String ENTRY_UUID3 =
    "33333333-3333-3333-3333-333333333333";
  /** Dn of the manipulated entry */
  private static final String ENTRY_DN = "uid=1," + TEST_ROOT_DN_STRING;

  /**
   * Optional attribute not part of concerned attributes of the fractional
   * configuration during tests. It should not be impacted by fractional
   * mechanism
   */
  private static final String OPTIONAL_ATTR = "description";

  /**
   * Optional attribute used as synchronization attribute to know when the
   * modify operation has been processed (used as add new attribute in the
   * modify operation) It may or may not be part of the filtered attributes,
   * depending on the fractional test mode : exclusive or inclusive
   */
  private static final String SYNCHRO_OPTIONAL_ATTR = "seeAlso";

  /** Second test backend */
  private static final String TEST2_ROOT_DN_STRING = "dc=example,dc=com";
  private static final String TEST2_ORG_DN_STRING = "o=test2," + TEST2_ROOT_DN_STRING;
  private static final String ENTRY_DN2 = "uid=1," + TEST2_ORG_DN_STRING;

  private void debugInfo(String s) {
    logger.error(LocalizableMessage.raw(s));
    if (logger.isTraceEnabled())
    {
      logger.trace("** TEST **" + s);
    }
  }

  /**
   * Before starting the tests configure some stuff
   */
  @BeforeClass
  @Override
  public void setUp() throws Exception
  {
    super.setUp();

    replServerPort = TestCaseUtils.findFreePort();
  }

  /**
   * Returns a bunch of single values for fractional-exclude configuration
   * attribute
   */
  @SuppressWarnings("unused")
  @DataProvider(name = "testExcludePrecommitProvider")
  private Object[][] testExcludePrecommitProvider()
  {
    return new Object[][]
    {
      { 1, new String[] {"inetOrgPerson", "displayName"}}
    };
  }

  /**
   * Returns a bunch of single values for fractional-exclude configuration
   * attribute
   */
  @SuppressWarnings("unused")
  @DataProvider(name = "testExcludeNightlyProvider")
  private Object[][] testExcludeNightlyProvider()
  {
    return new Object[][]
    {
      { 1, new String[] {"INETORGPERSON", "DISPLAYNAME"}},
      { 2, new String[] {"inetOrgPerson", "2.16.840.1.113730.3.1.241"}},
      { 3, new String[] {"2.16.840.1.113730.3.2.2", "displayName"}},
      { 4, new String[] {"2.16.840.1.113730.3.2.2", "2.16.840.1.113730.3.1.241"}},
      { 5, new String[] {"inetOrgPerson", "displayName", "carLicense"}},
      { 6, new String[] {"organizationalPerson", "title", "postalCode"}},
      { 7, new String[] {"2.5.6.7", "title", "postalCode"}},
      { 8, new String[] {"2.5.6.7", "TITLE", "2.5.4.17"}},
      { 9, new String[] {"2.5.6.7", "2.5.4.12", "2.5.4.17"}},
      { 10, new String[] {"*", "roomNumber"}},
      { 11, new String[] {"*", "0.9.2342.19200300.100.1.6"}},
      { 12, new String[] {"*", "postOfficeBox", "0.9.2342.19200300.100.1.6"}},
      { 13, new String[] {"*", "2.5.4.18", "0.9.2342.19200300.100.1.6"}}
    };
  }

  /**
   * Calls the testExclude test with a small set of data, for precommit test
   * purpose
   */
  @Test(dataProvider = "testExcludePrecommitProvider")
  public void testExcludePrecommit(int testProviderLineId,
    String... fractionalConf) throws Exception
  {
    testExclude(testProviderLineId, fractionalConf);
  }

  /**
   * Calls the testExclude test with a larger set of data, for nightly tests
   * purpose
   */
  @Test(dataProvider = "testExcludeNightlyProvider", groups = "slow")
  public void testExcludeNightly(int testProviderLineId,
    String... fractionalConf) throws Exception
  {
    testExclude(testProviderLineId, fractionalConf);
  }

  /**
   * Performs Add and Modify operations including attributes that are excluded
   * with the passed fractional configuration and checks that these attributes
   * are not part of the concerned entry.
   * Note: testProviderLineId just here to know what is the provider problematic
   * line if the test fail: prevent some display like:
   *  [testng] parameter[0]: [Ljava.lang.String;@151e824
   * but have instead:
   *  [testng] parameter[0]: 6
   *  [testng] parameter[1]: [Ljava.lang.String;@151e824
   */
  private void testExclude(int testProviderLineId,
    String... fractionalConf) throws Exception
  {

    String testcase = "testExclude" + testProviderLineId;

    initTest();

    try
    {
      createReplicationServer(testcase);
      createFractionalDomain(true, EXCLUDE_FRAC_MODE, fractionalConf);

      // create fake domain to send operations
      createFakeReplicationDomain(true, readGenIdFromSuffixRootEntry(TEST_ROOT_DN_STRING));

      // perform add operation
      sendAddMsg(true, fractionalConf);

      // check that entry has been created and that it does not contain
      // forbidden attributes
      Entry newEntry = getEntry(DN.valueOf(ENTRY_DN), TIMEOUT, true);
      checkEntryFilteredAfterAdd(newEntry, EXCLUDE_FRAC_MODE, fractionalConf);

      // perform modify operation (modify forbidden attributes +
      // modify authorized attribute (not a no op))
      sendModifyMsg(true, fractionalConf);

      // Wait for modify operation being replayed and
      // check that entry does not contain forbidden attributes
      Entry entry = waitTillEntryHasSynchroAttribute(ENTRY_DN);
      checkEntryFilteredAfterModify(entry, EXCLUDE_FRAC_MODE, fractionalConf);
    }
    finally
    {
      endTest();
    }
  }

  /**
   * Returns a bunch of single values for fractional-include configuration
   * attribute
   */
  @SuppressWarnings("unused")
  @DataProvider(name = "testIncludePrecommitProvider")
  private Object[][] testIncludePrecommitProvider()
  {
    return new Object[][]
    {
      { 1, new String[] {"inetOrgPerson", "displayName"}}
    };
  }

  /**
   * Returns a bunch of single values for fractional-include configuration
   * attribute
   */
  @SuppressWarnings("unused")
  @DataProvider(name = "testIncludeNightlyProvider")
  private Object[][] testIncludeNightlyProvider()
  {
    return new Object[][]
    {
      { 1, new String[] {"INETORGPERSON", "DISPLAYNAME"}},
      { 2, new String[] {"inetOrgPerson", "2.16.840.1.113730.3.1.241"}},
      { 3, new String[] {"2.16.840.1.113730.3.2.2", "displayName"}},
      { 4, new String[] {"2.16.840.1.113730.3.2.2", "2.16.840.1.113730.3.1.241"}},
      { 5, new String[] {"inetOrgPerson", "displayName", "carLicense"}},
      { 6, new String[] {"organizationalPerson", "title", "postalCode"}},
      { 7, new String[] {"2.5.6.7", "title", "postalCode"}},
      { 8, new String[] {"2.5.6.7", "TITLE", "2.5.4.17"}},
      { 9, new String[] {"2.5.6.7", "2.5.4.12", "2.5.4.17"}},
      { 10, new String[] {"*", "roomNumber"}},
      { 11, new String[] {"*", "0.9.2342.19200300.100.1.6"}},
      { 12, new String[] {"*", "postOfficeBox", "0.9.2342.19200300.100.1.6"}},
      { 13, new String[] {"*", "2.5.4.18", "0.9.2342.19200300.100.1.6"}}
    };
  }

  /**
   * Calls the testInclude test with a small set of data, for precommit test
   * purpose
   */
  @Test(dataProvider = "testIncludePrecommitProvider")
  public void testIncludePrecommit(int testProviderLineId,
    String... fractionalConf) throws Exception
  {
    testInclude(testProviderLineId, fractionalConf);
  }

  /**
   * Calls the testInclude test with a larger set of data, for nightly tests
   * purpose
   */
  @Test(dataProvider = "testIncludeNightlyProvider", groups = "slow")
  public void testIncludeNightly(int testProviderLineId,
    String... fractionalConf) throws Exception
  {
    testInclude(testProviderLineId, fractionalConf);
  }

  /**
   * Performs Add and Modify operations including attributes that are excluded
   * with the passed fractional configuration and checks that these attributes
   * are not part of the concerned entry.
   * Note: testProviderLineId just here to know what is the provider problematic
   * line if the test fail: prevent some display like:
   *  [testng] parameter[0]: [Ljava.lang.String;@151e824
   * but have instead:
   *  [testng] parameter[0]: 6
   *  [testng] parameter[1]: [Ljava.lang.String;@151e824
   */
  private void testInclude(int testProviderLineId,
    String... fractionalConf) throws Exception
  {
    String testcase = "testInclude" + testProviderLineId;

    initTest();

    try
    {
      createReplicationServer(testcase);
      createFractionalDomain(true, INCLUDE_FRAC_MODE, fractionalConf);

      // create fake domain to send operations
      createFakeReplicationDomain(true, readGenIdFromSuffixRootEntry(TEST_ROOT_DN_STRING));

      // perform add operation
      sendAddMsg(true, fractionalConf);

      // check that entry has been created and that it does not contain
      // forbidden attributes
      Entry newEntry = getEntry(DN.valueOf(ENTRY_DN), TIMEOUT, true);
      checkEntryFilteredAfterAdd(newEntry, INCLUDE_FRAC_MODE, fractionalConf);

      // perform modify operation (modify forbidden attributes +
      // modify authorized attribute (not a no op))
      sendModifyMsg(true, fractionalConf);

      Entry entry = waitTillEntryHasSynchroAttribute(ENTRY_DN);
      checkEntryFilteredAfterModify(entry, INCLUDE_FRAC_MODE, fractionalConf);
    }
    finally
    {
      endTest();
    }
  }

  /**
   * Creates connects (to the RS) and starts the fake replication domain
   * Use the passed generation id.
   */
  private void createFakeReplicationDomain(boolean firstBackend,
      long generationId) throws Exception
  {
    SortedSet<String> replicationServers = newTreeSet("localhost:" + replServerPort);

    DN baseDN = DN.valueOf(firstBackend ? TEST_ROOT_DN_STRING : TEST2_ROOT_DN_STRING);
    replicationDomain = new FakeReplicationDomain(baseDN, DS2_ID, replicationServers, 1000, generationId);

    // Test connection
    assertTrue(replicationDomain.isConnected());
    // Check connected server port
    String serverStr = replicationDomain.getReplicationServer();
    assertEquals(HostPort.valueOf(serverStr).getPort(), replServerPort);
  }

  private void initTest() throws Exception
  {
    replicationDomain = null;
    fractionalDomainCfgEntry = null;
    replicationServer = null;

    TestCaseUtils.initializeTestBackend(false);

    gen = new CSNGenerator(DS2_ID, 0L);
  }

  private void endTest() throws Exception
  {
    if (replicationDomain != null)
    {
      replicationDomain.disableService();
      replicationDomain = null;
    }

    removeDomain(fractionalDomainCfgEntry);
    fractionalDomainCfgEntry = null;

    remove(replicationServer);
    replicationServer = null;
  }

  /**
   * Creates a fractional domain with the passed configuration.
   * Before that, initializes the backend with the root entry and if requested
   * with the correct fractional configuration in it
   */
  private void createFractionalDomain(boolean initializeDomain,
      int fractionalMode, String... fractionalConf) throws Exception
  {
    {
      String fractModeAttrName = null;
      String opFractModeAttrName = null;
      boolean addSynchroAttribute = false;
      switch (fractionalMode)
      {
        case EXCLUDE_FRAC_MODE:
          fractModeAttrName = "ds-cfg-fractional-exclude";
          opFractModeAttrName = "ds-sync-fractional-exclude";
          break;
        case INCLUDE_FRAC_MODE:
          fractModeAttrName = "ds-cfg-fractional-include";
          opFractModeAttrName = "ds-sync-fractional-include";
          // For inclusive mode, we use an attribute that is added in the modify
          // operation to know when the modify operation has been played. The added
          // attribute can only be part of the include config to be taken into account
          addSynchroAttribute = true;
          break;
        default:
          fail("Unexpected fractional mode.");
      }

      /**
       * Create a root entry with potentially with fractional configuration before domain creation
       */

      // Create base entry with correct fractional config
      String topEntryLdif = null;

      if (initializeDomain)
      {
        // Add first backend top entry
        topEntryLdif = "dn: " + TEST_ROOT_DN_STRING + "\n" +
        "objectClass: top\n" +
        "objectClass: organization\n" +
        "o: " + TEST_BACKEND_ID + "\n" +
        "entryUUID: " + ENTRY_UUID3 + "\n";

        // Add fractional config
        int i=0;
        int size = fractionalConf.length;
        for (String fracCfgValue : fractionalConf) // Add fractional operational attributes
        {
          if (i==0)
          {
            // First string is the class
            topEntryLdif += opFractModeAttrName + ": " + fracCfgValue + ":";
          }
          else
          {
            // Other strings are attributes
            String endString = addSynchroAttribute ? ("," + SYNCHRO_OPTIONAL_ATTR + "\n") : "\n";
            topEntryLdif += fracCfgValue + ( (i<size-1) ? "," : endString);
          }
          i++;
        }
      }
      else
      {
        // Add second backend top entry
        topEntryLdif = "dn: " + TEST2_ROOT_DN_STRING + "\n" +
        "objectClass: top\n" +
        "objectClass: domain\n" +
        "dc: example\n";
      }
      addEntry(TestCaseUtils.entryFromLdifString(topEntryLdif));

      /**
       * Create the domain with the passed fractional configuration
       */

      // Create a config entry ldif, matching passed settings
      String configEntryLdif = "dn: cn=" + testName + ", cn=domains, " +
        SYNCHRO_PLUGIN_DN + "\n" + "objectClass: top\n" +
        "objectClass: ds-cfg-replication-domain\n" + "cn: " + testName + "\n" +
        "ds-cfg-base-dn: " + (initializeDomain ? TEST_ROOT_DN_STRING : TEST2_ROOT_DN_STRING) + "\n" +
        "ds-cfg-replication-server: localhost:" + replServerPort + "\n" +
        "ds-cfg-server-id: " + DS1_ID + "\n";

      int i=0;
      int size = fractionalConf.length;
      for (String fracCfgValue : fractionalConf) // Add fractional configuration attributes
      {
        if (i==0)
        {
          // First string is the class
          configEntryLdif += fractModeAttrName + ": " + fracCfgValue + ":";
        }
        else
        {
          // Other strings are attributes
          String endString = addSynchroAttribute ? ("," + SYNCHRO_OPTIONAL_ATTR + "\n") : "\n";
          configEntryLdif += fracCfgValue + ( (i<size-1) ? "," : endString);
        }
          i++;
      }
      fractionalDomainCfgEntry = TestCaseUtils.entryFromLdifString(configEntryLdif);

      // Add the config entry to create the replicated domain
      DirectoryServer.getConfigHandler().addEntry(fractionalDomainCfgEntry, null);
      assertNotNull(DirectoryServer.getConfigEntry(fractionalDomainCfgEntry.getName()),
        "Unable to add the domain config entry: " + configEntryLdif);
    }
  }

  /**
   * Creates a new ReplicationServer.
   */
  private void createReplicationServer(String testCase) throws Exception
  {
    SortedSet<String> replServers = new TreeSet<>();

    String dir = testName + RS_ID + testCase + "Db";
    replicationServer = new ReplicationServer(
        new ReplServerFakeConfiguration(replServerPort, dir, 0, RS_ID, 0, 100, replServers));
  }

  private static final String REPLICATION_GENERATION_ID =
    "ds-sync-generation-id";
  private static final Task NO_INIT_TASK = null;

  private long readGenIdFromSuffixRootEntry(String rootDn) throws Exception
  {
    DN baseDN = DN.valueOf(rootDn);
    Entry resultEntry = getEntry(baseDN, 1000, true);
    if (resultEntry == null)
    {
      debugInfo("Entry not found <" + rootDn + ">");
    }
    else
    {
      debugInfo("Entry found <" + rootDn + ">");

      AttributeType synchronizationGenIDType =
          DirectoryServer.getAttributeTypeOrNull(REPLICATION_GENERATION_ID);
      List<Attribute> attrs =
          resultEntry.getAttribute(synchronizationGenIDType);
      if (attrs != null)
      {
        Attribute attr = attrs.get(0);
        if (attr.size() == 1)
        {
          return Long.decode(attr.iterator().next().toString());
        }
      }
    }
    return -1;
  }

  /**
   * Send the AddMsg (from the fake replication domain) for the passed entry
   * containing the attributes defined in the passed fractional configuration
   */
  private void sendAddMsg(boolean firstBackend, String... fractionalConf)
      throws Exception
  {
      String entryLdif = "dn: " + (firstBackend ? ENTRY_DN : ENTRY_DN2) + "\n" + "objectClass: top\n" +
        "objectClass: person\n" + "objectClass: organizationalPerson\n";
        String classStr = "";
        if ( fractionalConf[0].equalsIgnoreCase("inetOrgPerson") ||
        fractionalConf[0].equalsIgnoreCase("2.16.840.1.113730.3.2.2"))
        {
          classStr = "objectClass: " + fractionalConf[0] + "\n";
        }
        entryLdif += classStr + "uid: 1\n" +
        "entryUUID: " + ENTRY_UUID + "\n" +
        "sn: snValue\n" + "cn: cnValue\n" +
        OPTIONAL_ATTR + ": " + OPTIONAL_ATTR + "Value\n";

      // Add attributes concerned by fractional configuration
      boolean first = true;
      for (String fracCfgValue : fractionalConf)
      {
        if (!first)
        {
          // First string is the class
          entryLdif += fracCfgValue + ": " + fracCfgValue + "Value\n";
        }
        first = false;
      }

    Entry entry = TestCaseUtils.entryFromLdifString(entryLdif);

      // Create an update message to add an entry.
      AddMsg addMsg = new AddMsg(gen.newCSN(),
        entry.getName(),
        ENTRY_UUID,
        null,
        entry.getObjectClassAttribute(),
        entry.getAttributes(), new ArrayList<Attribute>());

      replicationDomain.publish(addMsg);
  }

  /**
   * Send (from the fake replication domain) a ModifyMsg for the passed entry
   * modifying attributes defined in the passed fractional configuration
   */
  private void sendModifyMsg(boolean firstBackend, String... fractionalConf)
      throws Exception
  {
      // Create modifications on the fractional attributes
      List<Modification> mods = new ArrayList<>();
      boolean first = true;
      for (String fracCfgValue : fractionalConf)
      {
        if (!first)
        {
          // First string is the class
          Attribute attr =
            Attributes.create(fracCfgValue.toLowerCase(), fracCfgValue + "NewValue");
          Modification mod = new Modification(ModificationType.REPLACE, attr);
          mods.add(mod);
        }
        first = false;
      }

      // Add modification for the special attribute (modified attribute)
      Attribute attr =
        Attributes.create(OPTIONAL_ATTR.toLowerCase(), OPTIONAL_ATTR + "NewValue");
      Modification mod = new Modification(ModificationType.REPLACE, attr);
      mods.add(mod);

      // Add modification for the synchro attribute (added attribute)
      attr = Attributes.create(SYNCHRO_OPTIONAL_ATTR.toLowerCase(), SYNCHRO_OPTIONAL_ATTR + "Value");
      mod = new Modification(ModificationType.ADD, attr);
      mods.add(mod);

      DN entryDn = DN.valueOf(firstBackend ? ENTRY_DN : ENTRY_DN2);
      ModifyMsg modifyMsg = new ModifyMsg(gen.newCSN(), entryDn, mods, ENTRY_UUID);
      replicationDomain.publish(modifyMsg);
  }

  /**
   * Utility method : Add an entry in the database
   */
  private void addEntry(Entry entry) throws Exception
  {
    connection.processAdd(entry);
    assertNotNull(getEntry(entry.getName(), 1000, true));
  }

  /**
   * Check that the just added entry (newEntry) meets the fractional criteria
   * regarding the passed configuration : mode and attributes to be filtered/not
   * filtered
   */
  private void checkEntryFilteredAfterAdd(Entry newEntry,
    int fractionalMode, String... fractionalConf) throws Exception
  {
    {
      // Is the added entry of the expected object class ?
      String objectClassStr = fractionalConf[0];
      if (!objectClassStr.equals("*"))
      {
        ObjectClass objectClass = DirectoryServer.getObjectClass(objectClassStr.toLowerCase());

        assertTrue(newEntry.hasObjectClass(objectClass));
      }

      // Go through each interesting attribute and check it is present or not
      // according to the fractional mode
      boolean first = true;
      switch (fractionalMode)
      {
        case EXCLUDE_FRAC_MODE:
          // Exclude mode: attributes should not be there, but OPTIONAL_ATTR
          // attribute should
          for (String fracAttr : fractionalConf)
          {
            if (!first)
            {
              assertFalse(newEntry.hasAttribute(DirectoryServer.
                getAttributeTypeOrNull(fracAttr.toLowerCase())));
            }
            first = false;
          }
          checkEntryAttributeValue(newEntry, OPTIONAL_ATTR, OPTIONAL_ATTR + "Value");
          break;
        case INCLUDE_FRAC_MODE:
          // Include mode: attributes should be there, but OPTIONAL_ATTR
          // attribute should not
          for (String fracAttr : fractionalConf)
          {
            if (!first)
            {
              checkEntryAttributeValue(newEntry, fracAttr, fracAttr + "Value");
            }
            first = false;
          }
          assertFalse(newEntry.hasAttribute(DirectoryServer.
                getAttributeTypeOrNull(OPTIONAL_ATTR.toLowerCase())));
          break;
        default:
          fail("Unexpected fractional mode.");
      }
    }
  }

  /**
   * Check that the just modified entry (entry) meets the fractional criteria
   * regarding the passed configuration : mode and attributes to be filtered/not
   * filtered
   */
  private void checkEntryFilteredAfterModify(Entry entry,
    int fractionalMode, String... fractionalConf) throws Exception
  {
    {
      // Is the added entry of the expected object class ?
      String objectClassStr = fractionalConf[0];
      if (!objectClassStr.equals("*"))
      {
        ObjectClass objectClass = DirectoryServer.getObjectClass(objectClassStr.toLowerCase());

        assertTrue(entry.hasObjectClass(objectClass));
      }

      // Go through each interesting attribute and check it has been modifed or
      // not according to the fractional mode
      boolean first = true;
      switch (fractionalMode)
      {
        case EXCLUDE_FRAC_MODE:
          // Exclude mode: attributes should not be there, but OPTIONAL_ATTR
          // attribute should have been modified
          for (String fracAttr : fractionalConf)
          {
            if (!first)
            {
              assertFalse(entry.hasAttribute(DirectoryServer.
                getAttributeTypeOrNull(fracAttr.toLowerCase())));
            }
            first = false;
          }
          checkEntryAttributeValue(entry, OPTIONAL_ATTR, OPTIONAL_ATTR + "NewValue");
          break;
        case INCLUDE_FRAC_MODE:
          // Include mode: attributes should have been modified, but OPTIONAL_ATTR
          // attribute should not be there
          for (String fracAttr : fractionalConf)
          {
            if (!first)
            {
              checkEntryAttributeValue(entry, fracAttr, fracAttr + "NewValue");
            }
            first = false;
          }
          assertFalse(entry.hasAttribute(DirectoryServer.
                getAttributeTypeOrNull(OPTIONAL_ATTR.toLowerCase())));
          break;
        default:
          fail("Unexpected fractional mode.");
      }
      // In both modes, SYNCHRO_OPTIONAL_ATTR attribute should have been added
      checkEntryAttributeValue(entry, SYNCHRO_OPTIONAL_ATTR, SYNCHRO_OPTIONAL_ATTR + "Value");
    }
  }

  /**
   * Check that the provided entry has a single value attribute which has the
   * expected attribute value
   */
  private static void checkEntryAttributeValue(Entry entry, String attributeName,
    String attributeValue)
  {
    List<Attribute> attrs = entry.getAttribute(attributeName.toLowerCase());
    assertNotNull(attrs, "Was expecting attribute " + attributeName + "=" +
      attributeValue + " but got no attribute");
    assertEquals(attrs.size(), 1);
    Attribute attr = attrs.get(0);
    assertNotNull(attr);
    Iterator<ByteString> attrValues = attr.iterator();
    assertNotNull(attrValues);
    assertTrue(attrValues.hasNext());
    ByteString attrValue = attrValues.next();
    assertNotNull(attrValue);
    assertFalse(attrValues.hasNext());
    assertEquals(attrValue.toString(), attributeValue, "Was expecting attribute " +
      attributeName + "=" + attributeValue + " but got value: " + attrValue);

  }

  /**
   * Returns a bunch of single values for fractional configuration
   * attributes
   */
  @SuppressWarnings("unused")
  @DataProvider(name = "testInitWithFullUpdateExcludePrecommitProvider")
  private Object[][] testInitWithFullUpdateExcludePrecommitProvider()
  {
    return new Object[][]
    {
      { 1, true, new String[] {"inetOrgPerson", "displayName"}}
    };
  }

  /**
   * Returns a bunch of single values for fractional configuration
   * attributes
   */
  @SuppressWarnings("unused")
  @DataProvider(name = "testInitWithFullUpdateExcludeNightlyProvider")
  private Object[][] testInitWithFullUpdateExcludeNightlyProvider()
  {
    return new Object[][]
    {
      { 1, false, new String[] {"inetOrgPerson", "displayName"}}
    };
  }

  /**
   * Calls the testInitWithFullUpdateExclude test with a small set of data, for precommit test
   * purpose
   */
  @Test(dataProvider = "testInitWithFullUpdateExcludePrecommitProvider")
  public void testInitWithFullUpdateExcludePrecommit(int testProviderLineId,
    boolean importedDomainIsFractional, String... fractionalConf) throws Exception
  {
    testInitWithFullUpdateExclude(testProviderLineId, importedDomainIsFractional, fractionalConf);
  }

  /**
   * Calls the testInitWithFullUpdateExclude test with a larger set of data, for nightly tests
   * purpose
   */
  @Test(dataProvider = "testInitWithFullUpdateExcludeNightlyProvider", groups = "slow")
  public void testInitWithFullUpdateExcludeNightly(int testProviderLineId,
    boolean importedDomainIsFractional, String... fractionalConf) throws Exception
  {
    testInitWithFullUpdateExclude(testProviderLineId, importedDomainIsFractional, fractionalConf);
  }

  /**
   * Configures a domain which is not fractional to fractional exclusive,
   * then emulates an online full update to initialize the fractional domain and
   * have it operational.
   * Note: testProviderLineId just here to know what is the provider problematic
   * line if the test fail: prevent some display like:
   *  [testng] parameter[0]: [Ljava.lang.String;@151e824
   * but have instead:
   *  [testng] parameter[0]: 6
   *  [testng] parameter[1]: [Ljava.lang.String;@151e824
   */
  private void testInitWithFullUpdateExclude(int testProviderLineId,
    boolean importedDomainIsFractional, String... fractionalConf) throws Exception
  {
    String testcase = "testInitWithFullUpdateExclude" + testProviderLineId;

    initTest();

    // We need a backend with a real configuration in cn=config as at import time
    // the real domain will check for backend existence in cn=config. So we use
    // dc=example,dc=com for this particular test.
    clearBackend("userRoot");

    try
    {
      /*
       * Create replication server and connect fractional domain to it then fake
       * domain
       */
      createReplicationServer(testcase);

      // create fractional domain with the passed fractional configuration
      // without initializing the backend
      createFractionalDomain(false, EXCLUDE_FRAC_MODE, fractionalConf);

      // The domain should go in bad gen as backend is not initialized with
      // fractional data
      LDAPReplicationDomain fractionalReplicationDomain =
        MultimasterReplication.findDomain(DN.valueOf(TEST2_ROOT_DN_STRING), null);
      waitForDomainStatus(fractionalReplicationDomain,
        ServerStatus.BAD_GEN_ID_STATUS, 5);

      // create fake domain to perform the full update
      long generationId = readGenIdFromSuffixRootEntry(TEST2_ROOT_DN_STRING);
      assertTrue(generationId != 0L);
      createFakeReplicationDomain(false, generationId);

      /*
       * Create the LDIF that will be used to initialize the domain from the
       * fake one. Initialize the fake domain with it.
       */

      //      Top Entry
      String exportLdif = "dn: " + TEST2_ROOT_DN_STRING + "\n" +
        "objectClass: top\n" +
        "objectClass: domain\n" +
        "dc: example\n" +
        "ds-sync-generation-id: " + generationId + "\n";
      if (importedDomainIsFractional)
      {
        //                Add fractional config
        int i=0;
        int size = fractionalConf.length;
        for (String fracCfgValue : fractionalConf) // Add fractional operational attributes
        {
          if (i==0)
          {
            // First string is the class
            exportLdif += "ds-sync-fractional-exclude: " + fracCfgValue + ":";
          }
          else
          {
            // Other strings are attributes
            exportLdif += fracCfgValue + ( (i<size-1) ? "," : "\n");
          }
            i++;
        }
      }
      //      Org Entry
      exportLdif += "\ndn: " + TEST2_ORG_DN_STRING + "\n" +
        "objectClass: top\n" +
        "objectClass: organization\n" +
        "o: test2\n\n";
      //      User entry
      exportLdif += "dn: " + ENTRY_DN2 + "\n" + "objectClass: top\n" +
        "objectClass: person\n" + "objectClass: organizationalPerson\n" +
        "objectClass: inetOrgPerson\n" + "sn: snValue\n" + "cn: cnValue\n" +
        "uid: 1\n" + "entryUUID: " +
        ENTRY_UUID + "\n" + OPTIONAL_ATTR + ": " + OPTIONAL_ATTR + "Value\n";
      if (!importedDomainIsFractional)
      {
        //                Add attributes concerned by fractional configuration
        boolean first = true;
        for (String fracCfgValue : fractionalConf)
        {
          if (!first)
          {
            // First string is the class
            exportLdif += fracCfgValue + ": " + fracCfgValue + "Value\n";
          }
          first = false;
        }
      }
      exportLdif += "\n"; // Needed ?

      replicationDomain.initExport(exportLdif, 2);

      // Perform full update from fake domain to fractional domain
      replicationDomain.initializeRemote(DS1_ID, NO_INIT_TASK);

      /*
       * Check fractional domain is operational and that filtering has been done
       * during the full update
       */

      // The domain should go back in normal status
      waitForDomainStatus(fractionalReplicationDomain,
        ServerStatus.NORMAL_STATUS, 15);

      // check that entry has been created and that it does not contain
      // forbidden attributes
      Entry newEntry = getEntry(DN.valueOf(ENTRY_DN2), TIMEOUT, true);
      checkEntryFilteredAfterAdd(newEntry, EXCLUDE_FRAC_MODE, fractionalConf);

      // perform modify operation (modify forbidden attributes +
      // modify authorized attribute (not a no op))
      sendModifyMsg(false, fractionalConf);

      // Wait for modify operation being replayed and
      // check that entry does not contain forbidden attributes
      Entry entry = waitTillEntryHasSynchroAttribute(ENTRY_DN2);
      checkEntryFilteredAfterModify(entry, EXCLUDE_FRAC_MODE, fractionalConf);
    }
    finally
    {
      endTest();
    }
  }

  private Entry waitTillEntryHasSynchroAttribute(String entryDN)
      throws Exception
  {
    AttributeType synchroAttrType = DirectoryServer.getAttributeTypeOrNull(SYNCHRO_OPTIONAL_ATTR.toLowerCase());
    DN dn = DN.valueOf(entryDN);

    Entry entry = null;
    boolean synchroAttrFound = false;
    int timeout = TIMEOUT;
    while (timeout > 0)
    {
      entry = getEntry(dn, TIMEOUT, true);
      if (entry.hasAttribute(synchroAttrType))
      {
        synchroAttrFound = true;
        break;
      }
      Thread.sleep(1000);
      timeout--;
    }
    assertTrue(synchroAttrFound, "Modify operation not replayed");
    return entry;
  }

  /**
   * Wait for the passed domain to have the desired status or fail if timeout
   * waiting.
   */
  private void waitForDomainStatus(ReplicationDomain replicationDomain,
    ServerStatus expectedStatus, int nSec)
  {
    int toWait = nSec;
    ServerStatus serverStatus = null;
    while(nSec > 0)
    {
      serverStatus = replicationDomain.getStatus();
      if ( serverStatus ==  expectedStatus )
      {
        debugInfo("waitForDomainStatus: expected replication " +
          "domain status obtained after " + (toWait-nSec) + " second(s).");
        return;
      }
      TestCaseUtils.sleep(1000);
      nSec--;
    }
    fail("Did not get expected replication domain status: expected <" + expectedStatus +
      "> but got <" + serverStatus + ">, after " + toWait + " second(s)");
  }

  /**
   * Returns a bunch of single values for fractional configuration
   * attributes
   */
  @SuppressWarnings("unused")
  @DataProvider(name = "testInitWithFullUpdateIncludePrecommitProvider")
  private Object[][] testInitWithFullUpdateIncludePrecommitProvider()
  {
    return new Object[][]
    {
      { 1, true, new String[] {"inetOrgPerson", "displayName"}}
    };
  }

  /**
   * Returns a bunch of single values for fractional configuration
   * attributes
   */
  @SuppressWarnings("unused")
  @DataProvider(name = "testInitWithFullUpdateIncludeNightlyProvider")
  private Object[][] testInitWithFullUpdateIncludeNightlyProvider()
  {
    return new Object[][]
    {
      { 1, false, new String[] {"inetOrgPerson", "displayName"}}
    };
  }

  /**
   * Calls the testInitWithFullUpdateExclude test with a small set of data, for precommit test
   * purpose
   */
  @Test(dataProvider = "testInitWithFullUpdateIncludePrecommitProvider")
  public void testInitWithFullUpdateIncludePrecommit(int testProviderLineId,
    boolean importedDomainIsFractional, String... fractionalConf) throws Exception
  {
    testInitWithFullUpdateInclude(testProviderLineId, importedDomainIsFractional, fractionalConf);
  }

  /**
   * Calls the testInitWithFullUpdateExclude test with a larger set of data, for nightly tests
   * purpose
   */
  @Test(dataProvider = "testInitWithFullUpdateIncludeNightlyProvider", groups = "slow")
  public void testInitWithFullUpdateIncludeNightly(int testProviderLineId,
    boolean importedDomainIsFractional, String... fractionalConf) throws Exception
  {
    testInitWithFullUpdateInclude(testProviderLineId, importedDomainIsFractional, fractionalConf);
  }

  /**
   * Configures a domain which is not fractional to fractional inclusive,
   * then emulates an online full update to initialize the fractional domain and
   * have it operational.
   * Note: testProviderLineId just here to know what is the provider problematic
   * line if the test fail: prevent some display like:
   *  [testng] parameter[0]: [Ljava.lang.String;@151e824
   * but have instead:
   *  [testng] parameter[0]: 6
   *  [testng] parameter[1]: [Ljava.lang.String;@151e824
   */
  private void testInitWithFullUpdateInclude(int testProviderLineId,
    boolean importedDomainIsFractional, String... fractionalConf) throws Exception
  {
    String testcase = "testInitWithFullUpdateInclude" + testProviderLineId;

    initTest();

    // We need a backend with a real configuration in cn=config as at import time
    // the real domain will check for backend existence in cn=config. So we use
    // dc=example,dc=com for this particular test.
    clearBackend("userRoot");

    try
    {
      /*
       * Create replication server and connect fractional domain to it then fake
       * domain
       */
      createReplicationServer(testcase);

      // create fractional domain with the passed fractional configuration
      // without initializing the backend
      createFractionalDomain(false, INCLUDE_FRAC_MODE, fractionalConf);

      // The domain should go in bad gen as backend is not initialized with
      // fractional data
      LDAPReplicationDomain fractionalReplicationDomain =
        MultimasterReplication.findDomain(DN.valueOf(TEST2_ROOT_DN_STRING), null);
      waitForDomainStatus(fractionalReplicationDomain,
        ServerStatus.BAD_GEN_ID_STATUS, 5);

      // create fake domain to perform the full update
      long generationId = readGenIdFromSuffixRootEntry(TEST2_ROOT_DN_STRING);
      assertTrue(generationId != 0L);
      createFakeReplicationDomain(false, generationId);

      /*
       * Create the LDIF that will be used to initialize the domain from the
       * fake one. Initialize the fake domain with it.
       */

      //      Top Entry
      String exportLdif = "dn: " + TEST2_ROOT_DN_STRING + "\n" +
        "objectClass: top\n" +
        "objectClass: domain\n" +
        "dc: example\n" +
        "ds-sync-generation-id: " + generationId + "\n";
      if (importedDomainIsFractional)
      {
        //                Add fractional config
        int i=0;
        int size = fractionalConf.length;
        for (String fracCfgValue : fractionalConf) // Add fractional operational attributes
        {
          if (i==0)
          {
            // First string is the class
            exportLdif += "ds-sync-fractional-include: " + fracCfgValue + ":";
          }
          else
          {
            // Other strings are attributes
            exportLdif += fracCfgValue + ( (i<size-1) ? "," : "," + SYNCHRO_OPTIONAL_ATTR + "\n");
          }
            i++;
        }
      }
      //      Org Entry
      exportLdif += "\ndn: " + TEST2_ORG_DN_STRING + "\n" +
        "objectClass: top\n" +
        "objectClass: organization\n" +
        "o: test2\n\n";
      //      User entry
      exportLdif += "dn: " + ENTRY_DN2 + "\n" + "objectClass: top\n" +
        "objectClass: person\n" + "objectClass: organizationalPerson\n" +
        "objectClass: inetOrgPerson\n" +
        "sn: snValue\n" + "cn: cnValue\n" + "uid: 1\n" + "entryUUID: " +
        ENTRY_UUID + "\n" + OPTIONAL_ATTR + ": " + OPTIONAL_ATTR + "Value\n";
      //                Add attributes concerned by fractional configuration
      boolean first = true;
      for (String fracCfgValue : fractionalConf)
      {
        if (!first)
        {
          // First string is the class
          exportLdif += fracCfgValue + ": " + fracCfgValue + "Value\n";
        }
        first = false;
      }
      exportLdif += "\n"; // Needed ?

      replicationDomain.initExport(exportLdif, 2);

      // Perform full update from fake domain to fractional domain
      replicationDomain.initializeRemote(DS1_ID, NO_INIT_TASK);

      /*
       * Check fractional domain is operational and that filtering has been done
       * during the full update
       */

      // The domain should go back in normal status
      waitForDomainStatus(fractionalReplicationDomain,
        ServerStatus.NORMAL_STATUS, 15);

      // check that entry has been created and that it does not contain
      // forbidden attributes
      Entry newEntry = getEntry(DN.valueOf(ENTRY_DN2), TIMEOUT, true);
      checkEntryFilteredAfterAdd(newEntry, INCLUDE_FRAC_MODE, fractionalConf);

      // perform modify operation (modify forbidden attributes +
      // modify authorized attribute (not a no op))
      sendModifyMsg(false, fractionalConf);

      Entry entry = waitTillEntryHasSynchroAttribute(ENTRY_DN2);
      checkEntryFilteredAfterModify(entry, INCLUDE_FRAC_MODE, fractionalConf);
    }
    finally
    {
      endTest();
    }
  }

  /**
   * Tests an add operation on an entry with RDN containing forbidden attribute
   * by fractional exclude configuration
   */
  @Test
  public void testAddWithForbiddenAttrInRDNExclude() throws Exception
  {
    String testcase = "testAddWithForbiddenAttrInRDNExclude";

    initTest();

    try
    {
      createReplicationServer(testcase);
      createFractionalDomain(true, EXCLUDE_FRAC_MODE, "inetOrgPerson",
          "displayName", "description");

      // create fake domain to send operations
      createFakeReplicationDomain(true, readGenIdFromSuffixRootEntry(TEST_ROOT_DN_STRING));

      // Perform add operation with forbidden attribute in RDN
      String entryLdif = "dn: displayName=ValueToBeKept," +
        TEST_ROOT_DN_STRING + "\n" + "objectClass: top\n" +
        "objectClass: person\n" + "objectClass: organizationalPerson\n" +
        "objectClass: inetOrgPerson\n" + "sn: snValue\n" + "cn: cnValue\n" +
        "entryUUID: " + ENTRY_UUID + "\n" +
        "displayName: ValueToBeKept\ndisplayName: displayNameValue\n";

      Entry entry = TestCaseUtils.entryFromLdifString(entryLdif);

      // Create an update message to add an entry.
      AddMsg addMsg = new AddMsg(gen.newCSN(),
        entry.getName(),
        ENTRY_UUID,
        null,
        entry.getObjectClassAttribute(),
        entry.getAttributes(), new ArrayList<Attribute>());

      replicationDomain.publish(addMsg);

      /*
       * check that entry has been created and has attribute values from RDN
       * only
       */
      Entry newEntry = getEntry(entry.getName(), TIMEOUT, true);
      assertNotNull(newEntry);
      assertEquals(entry.getName(), newEntry.getName());
      ObjectClass objectClass = DirectoryServer.getObjectClass("inetOrgPerson".toLowerCase());
      assertTrue(newEntry.hasObjectClass(objectClass));
      checkEntryAttributeValue(newEntry, "displayName", "ValueToBeKept");

      /**
       * Now perform same test, but with 2 forbidden attributes in RDN, using '+'
       */

      // Perform add operation with forbidden attribute in RDN
      entryLdif = "dn: displayName=ValueToBeKept+description=ValueToBeKeptToo," +
        TEST_ROOT_DN_STRING + "\n" + "objectClass: top\n" +
        "objectClass: person\n" + "objectClass: organizationalPerson\n" +
        "objectClass: inetOrgPerson\n" + "entryUUID: " + ENTRY_UUID2 + "\n" +
        "sn: snValue\n" + "cn: cnValue\n" +
        "displayName: ValueToBeKept\ndisplayName: displayNameValue\n" +
        "description: descriptionValue\ndescription: ValueToBeKeptToo\n";

      entry = TestCaseUtils.entryFromLdifString(entryLdif);

      // Create an update message to add an entry.
      addMsg = new AddMsg(gen.newCSN(),
        entry.getName(),
        ENTRY_UUID2,
        null,
        entry.getObjectClassAttribute(),
        entry.getAttributes(), new ArrayList<Attribute>());

      replicationDomain.publish(addMsg);

      /*
       * check that entry has been created and has attribute values from RDN
       * only
       */
      newEntry = getEntry(entry.getName(), TIMEOUT, true);
      assertNotNull(newEntry);
      assertEquals(entry.getName(), newEntry.getName());
      objectClass = DirectoryServer.getObjectClass("inetOrgPerson".toLowerCase());
      assertTrue(newEntry.hasObjectClass(objectClass));
      checkEntryAttributeValue(newEntry, "displayName", "ValueToBeKept");
      checkEntryAttributeValue(newEntry, "description", "ValueToBeKeptToo");
    }
    finally
    {
      endTest();
    }
  }

  /**
   * Tests an add operation on an entry with RDN containing forbidden attribute
   * by fractional include configuration
   */
  @Test
  public void testAddWithForbiddenAttrInRDNInclude() throws Exception
  {
    String testcase = "testAddWithForbiddenAttrInRDNInclude";

    initTest();

    try
    {
      createReplicationServer(testcase);
      createFractionalDomain(true, INCLUDE_FRAC_MODE, "inetOrgPerson",
          "carLicense");

      // create fake domain to send operations
      createFakeReplicationDomain(true, readGenIdFromSuffixRootEntry(TEST_ROOT_DN_STRING));

      // Perform add operation with forbidden attribute in RDN
      String entryLdif = "dn: displayName=ValueToBeKept," +
        TEST_ROOT_DN_STRING + "\n" + "objectClass: top\n" +
        "objectClass: person\n" + "objectClass: organizationalPerson\n" +
        "objectClass: inetOrgPerson\n" + "sn: snValue\n" + "cn: cnValue\n" +
        "entryUUID: " + ENTRY_UUID + "\n" +
        "displayName: ValueToBeKept\ndisplayName: displayNameValue\n" +
        "carLicense: cirLicenseValue\n";


      Entry entry = TestCaseUtils.entryFromLdifString(entryLdif);

      // Create an update message to add an entry.
      AddMsg addMsg = new AddMsg(gen.newCSN(),
        entry.getName(),
        ENTRY_UUID,
        null,
        entry.getObjectClassAttribute(),
        entry.getAttributes(), new ArrayList<Attribute>());

      replicationDomain.publish(addMsg);

      /*
       * check that entry has been created and has attribute values from RDN
       * only
       */
      Entry newEntry = getEntry(entry.getName(), TIMEOUT, true);
      assertNotNull(newEntry);
      assertEquals(entry.getName(), newEntry.getName());
      ObjectClass objectClass = DirectoryServer.getObjectClass("inetOrgPerson".toLowerCase());
      assertTrue(newEntry.hasObjectClass(objectClass));
      checkEntryAttributeValue(newEntry, "displayName", "ValueToBeKept");
      checkEntryAttributeValue(newEntry, "carLicense", "cirLicenseValue");

      /**
       * Now perform same test, but with 2 forbidden attributes in RDN, using '+'
       */

      // Perform add operation with forbidden attribute in RDN
      entryLdif = "dn: displayName=ValueToBeKept+description=ValueToBeKeptToo," +
        TEST_ROOT_DN_STRING + "\n" + "objectClass: top\n" +
        "objectClass: person\n" + "objectClass: organizationalPerson\n" +
        "objectClass: inetOrgPerson\n" + "sn: snValue\n" + "cn: cnValue\n" +
        "entryUUID: " + ENTRY_UUID2 + "\n" +
        "displayName: ValueToBeKept\ndisplayName: displayNameValue\n" +
        "description: descriptionValue\ndescription: ValueToBeKeptToo\n" +
        "carLicense: cirLicenseValue\n";

      entry = TestCaseUtils.entryFromLdifString(entryLdif);

      // Create an update message to add an entry.
      addMsg = new AddMsg(gen.newCSN(),
        entry.getName(),
        ENTRY_UUID2,
        null,
        entry.getObjectClassAttribute(),
        entry.getAttributes(), new ArrayList<Attribute>());

      replicationDomain.publish(addMsg);

      /*
       * check that entry has been created and has attribute values from RDN
       * only
       */
      newEntry = getEntry(entry.getName(), TIMEOUT, true);
      assertNotNull(newEntry);
      assertEquals(entry.getName(), newEntry.getName());
      objectClass = DirectoryServer.getObjectClass("inetOrgPerson".toLowerCase());
      assertTrue(newEntry.hasObjectClass(objectClass));
      checkEntryAttributeValue(newEntry, "displayName", "ValueToBeKept");
      checkEntryAttributeValue(newEntry, "description", "ValueToBeKeptToo");
      checkEntryAttributeValue(newEntry, "carLicense", "cirLicenseValue");
    }
    finally
    {
      endTest();
    }
  }

  /**
   * Tests modify dn operation on an entry with old RDN containing forbidden
   * attribute by fractional exclude configuration
   */
  @Test
  public void testModifyDnWithForbiddenAttrInRDNExclude() throws Exception
  {
     String testcase = "testModifyDnWithForbiddenAttrInRDNExclude";

    initTest();

    try
    {
      createReplicationServer(testcase);
      createFractionalDomain(true, EXCLUDE_FRAC_MODE, "inetOrgPerson",
          "displayName", "description");

      // create fake domain to send operations
      createFakeReplicationDomain(true, readGenIdFromSuffixRootEntry(TEST_ROOT_DN_STRING));

      // Perform add operation with forbidden attribute in RDN
      String entryName = "displayName=ValueToBeKept+description=ValueToBeRemoved," + TEST_ROOT_DN_STRING ;
      String entryLdif = "dn: " + entryName + "\n" + "objectClass: top\n" +
        "objectClass: person\n" + "objectClass: organizationalPerson\n" +
        "objectClass: inetOrgPerson\n" + "sn: snValue\n" + "cn: cnValue\n" +
        "entryUUID: " + ENTRY_UUID + "\n" +
        "displayName: ValueToBeKept\ndescription: ValueToBeRemoved\n";

      Entry entry = TestCaseUtils.entryFromLdifString(entryLdif);

      // Create an update message to add an entry.
      AddMsg addMsg = new AddMsg(gen.newCSN(),
        entry.getName(),
        ENTRY_UUID,
        null,
        entry.getObjectClassAttribute(),
        entry.getAttributes(), new ArrayList<Attribute>());

      replicationDomain.publish(addMsg);

      // check that entry has been created and has attribute values from RDN
      Entry newEntry = getEntry(entry.getName(), TIMEOUT, true);
      assertNotNull(newEntry);
      assertEquals(entry.getName(), newEntry.getName());
      ObjectClass objectClass = DirectoryServer.getObjectClass("inetOrgPerson".toLowerCase());
      assertTrue(newEntry.hasObjectClass(objectClass));
      checkEntryAttributeValue(newEntry, "displayName", "ValueToBeKept");
      checkEntryAttributeValue(newEntry, "description", "ValueToBeRemoved");

      /*
       * Perform modify dn operation by renaming the entry keeping only one of
       * the forbidden attributes
       */
      String newEntryName = "displayName=ValueToBeKept," + TEST_ROOT_DN_STRING ;
      DN newEntryDn = DN.valueOf(newEntryName);

      // Create modify dn message to modify the entry.
      ModifyDNMsg modDnMsg = new ModifyDNMsg(DN.valueOf(entryName), gen.newCSN(),
        ENTRY_UUID, ENTRY_UUID3, false, TEST_ROOT_DN_STRING,
        "displayName=ValueToBeKept", null);

      replicationDomain.publish(modDnMsg);

      /*
       * check that entry has been renamed  and has only attribute left in the
       * new RDN
       */
      newEntry = getEntry(newEntryDn, TIMEOUT, true);
      assertNotNull(newEntry);
      assertEquals(newEntryDn, newEntry.getName());
      objectClass = DirectoryServer.getObjectClass("inetOrgPerson".toLowerCase());
      assertTrue(newEntry.hasObjectClass(objectClass));
      checkEntryAttributeValue(newEntry, "displayName", "ValueToBeKept");
      assertNull(newEntry.getAttribute("description"));
    }
    finally
    {
      endTest();
    }
  }

  /**
   * Tests modify dn operation on an entry with old RDN containing forbidden
   * attribute by fractional include configuration
   */
  @Test
  public void testModifyDnWithForbiddenAttrInRDNInclude() throws Exception
  {
    String testcase = "testModifyDnWithForbiddenAttrInRDNInclude";

    initTest();

    try
    {
      createReplicationServer(testcase);
      createFractionalDomain(true, INCLUDE_FRAC_MODE, "inetOrgPerson",
          "carLicense");

      // create fake domain to send operations
      createFakeReplicationDomain(true, readGenIdFromSuffixRootEntry(TEST_ROOT_DN_STRING));

      // Perform add operation with forbidden attribute in RDN
      String entryName = "displayName=ValueToBeKept+description=ValueToBeRemoved," + TEST_ROOT_DN_STRING ;
      String entryLdif = "dn: " + entryName + "\n" + "objectClass: top\n" +
        "objectClass: person\n" + "objectClass: organizationalPerson\n" +
        "objectClass: inetOrgPerson\n" + "sn: snValue\n" + "cn: cnValue\n" +
        "entryUUID: " + ENTRY_UUID + "\n" +
        "displayName: ValueToBeKept\ndescription: ValueToBeRemoved\n";

      Entry entry = TestCaseUtils.entryFromLdifString(entryLdif);

      // Create an update message to add an entry.
      AddMsg addMsg = new AddMsg(gen.newCSN(),
        entry.getName(),
        ENTRY_UUID,
        null,
        entry.getObjectClassAttribute(),
        entry.getAttributes(), new ArrayList<Attribute>());

      replicationDomain.publish(addMsg);

      // check that entry has been created and has attribute values from RDN
      Entry newEntry = getEntry(entry.getName(), TIMEOUT, true);
      assertNotNull(newEntry);
      assertEquals(entry.getName(), newEntry.getName());
      ObjectClass objectClass = DirectoryServer.getObjectClass("inetOrgPerson".toLowerCase());
      assertTrue(newEntry.hasObjectClass(objectClass));
      checkEntryAttributeValue(newEntry, "displayName", "ValueToBeKept");
      checkEntryAttributeValue(newEntry, "description", "ValueToBeRemoved");

      /*
       * Perform modify dn operation by renaming the entry keeping only one of
       * the forbidden attributes
       */
      String newEntryName = "displayName=ValueToBeKept," + TEST_ROOT_DN_STRING ;
      DN newEntryDn = DN.valueOf(newEntryName);

      // Create modify dn message to modify the entry.
      ModifyDNMsg modDnMsg = new ModifyDNMsg(DN.valueOf(entryName), gen.newCSN(),
        ENTRY_UUID, ENTRY_UUID3, false, TEST_ROOT_DN_STRING,
        "displayName=ValueToBeKept", null);

      replicationDomain.publish(modDnMsg);

      /*
       * check that entry has been renamed  and has only attribute left in the
       * new RDN
       */
      newEntry = getEntry(newEntryDn, TIMEOUT, true);
      assertNotNull(newEntry);
      assertEquals(newEntryDn, newEntry.getName());
      objectClass = DirectoryServer.getObjectClass("inetOrgPerson".toLowerCase());
      assertTrue(newEntry.hasObjectClass(objectClass));
      checkEntryAttributeValue(newEntry, "displayName", "ValueToBeKept");
      assertNull(newEntry.getAttribute("description"));
    }
    finally
    {
      endTest();
    }
  }
}
