mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

mrossign
07.15.2009 3af1c93c0de41be1cdfc0e6aff1d1c98f1d5530b
Fractional replication
Info about the feature:
https://www.opends.org/wiki/page/FractionalReplication

Note:

After configuration of a fractional domain, the domain goes into bad gen
id status and must be initialized (to filter for configured attributes)
through online full update or ldif import (limitation: for the moment
only with server running) from a non fractional domain of the topology.

3 files added
11 files modified
4476 ■■■■■ changed files
opends/resource/config/config.ldif 10 ●●●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif 29 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/FractionalLDIFImportPluginConfiguration.xml 50 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/ReplicationDomainConfiguration.xml 95 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/replication.properties 39 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/common/ChangeNumber.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/FractionalLDIFImportPlugin.java 355 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java 1671 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/protocol/MonitorMsg.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/service/ReplicationDomain.java 94 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java 30 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/AssuredReplicationPluginTest.java 30 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/DomainFakeCfg.java 38 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/FractionalReplicationTest.java 2031 ●●●●● patch | view | raw | blame | history
opends/resource/config/config.ldif
@@ -1834,6 +1834,16 @@
ds-cfg-plugin-type: postOperationModifyDn
ds-cfg-java-class: org.opends.server.plugins.ChangeNumberControlPlugin
dn: cn=Fractional Replication LDIF Import,cn=Plugins,cn=config
objectClass: top
objectClass: ds-cfg-plugin
objectClass: ds-cfg-fractional-ldif-import-plugin
cn: Fractional Replication LDIF Import
ds-cfg-java-class: org.opends.server.replication.plugin.FractionalLDIFImportPlugin
ds-cfg-enabled: true
ds-cfg-plugin-type: ldifImport
ds-cfg-invoke-for-internal-operations: true
dn: cn=Root DNs,cn=config
objectClass: top
objectClass: ds-cfg-root-dn
opends/resource/schema/02-config.ldif
@@ -2397,6 +2397,24 @@
  NAME 'ds-cfg-index-extensible-matching-rule'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.586
  NAME 'ds-cfg-fractional-include'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.587
  NAME 'ds-cfg-fractional-exclude'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.588
  NAME 'ds-sync-fractional-include'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  USAGE directoryOperation
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.589
  NAME 'ds-sync-fractional-exclude'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  USAGE directoryOperation
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.1
  NAME 'ds-cfg-access-control-handler'
  SUP top
@@ -2955,7 +2973,9 @@
        ds-cfg-assured-sd-level $
        ds-cfg-assured-timeout $
        ds-cfg-group-id $
        ds-cfg-referrals-url)
        ds-cfg-referrals-url $
        ds-cfg-fractional-exclude $
        ds-cfg-fractional-include)
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.58
  NAME 'ds-cfg-length-based-password-validator'
@@ -3958,7 +3978,7 @@
  NAME 'ds-cfg-qos-policy'
  SUP top
  STRUCTURAL
  MUST ( cn $
  MUST ( cn $
         ds-cfg-java-class)
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.185
@@ -4054,4 +4074,9 @@
  SUP ds-cfg-matching-rule
  STRUCTURAL
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.232
  NAME 'ds-cfg-fractional-ldif-import-plugin'
  SUP ds-cfg-plugin
  STRUCTURAL
  X-ORIGIN 'OpenDS Directory Server' )
opends/src/admin/defn/org/opends/server/admin/std/FractionalLDIFImportPluginConfiguration.xml
New file
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
  ! 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
  ! trunk/opends/resource/legal-notices/OpenDS.LICENSE
  ! or https://OpenDS.dev.java.net/OpenDS.LICENSE.
  ! 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
  ! trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 Sun Microsystems, Inc.
  ! -->
<adm:managed-object name="fractional-ldif-import-plugin"
  plural-name="fractional-ldif-import-plugins"
  package="org.opends.server.admin.std" extends="plugin"
  xmlns:adm="http://www.opends.org/admin"
  xmlns:ldap="http://www.opends.org/admin-ldap">
  <adm:synopsis>
    The
    <adm:user-friendly-name />
    is used internally by the replication plugin to support fractional
    replication.
  </adm:synopsis>
  <adm:description>
    It is used to check fractional configuration consistency with local domain
    one as well as to filter attributes when performing an online import from a
    remote backend to a local backend.
  </adm:description>
  <adm:profile name="ldap">
    <ldap:object-class>
      <ldap:name>ds-cfg-fractional-ldif-import-plugin</ldap:name>
      <ldap:superior>ds-cfg-plugin</ldap:superior>
    </ldap:object-class>
  </adm:profile>
</adm:managed-object>
opends/src/admin/defn/org/opends/server/admin/std/ReplicationDomainConfiguration.xml
@@ -345,4 +345,99 @@
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="fractional-include" multi-valued="true" mandatory="false"
  advanced="true">
    <adm:synopsis>
      Allows to include some attributes to replicate to this server.
    </adm:synopsis>
    <adm:description>
      If fractional-include configuration attribute is used, only attributes
      specified in this attribute will be added/modified/deleted when an
      operation performed from another directory server is being replayed in the
      local server. Note that the usage of this configuration attribute is
      mutually exclusive with the usage of the fractional-exclude attribute.
    </adm:description>
    <adm:default-behavior>
      <adm:undefined/>
    </adm:default-behavior>
    <adm:syntax>
      <adm:string>
        <adm:pattern>
          <!-- This java regex is mostly derived from keystring BNF definition
          that can be found in RFC 2252, section "4.1. Common Encoding Aspects".
          This can be read as: (oid|\*):oid(,oid)*+
          -->
          <adm:regex>^((([a-zA-Z]([a-zA-Z]|[0-9]|-|;)*+)|(0|([1-9]([0-9])*+))(\\.(0|([1-9]([0-9])*+)))*+)|\\*):(([a-zA-Z]([a-zA-Z]|[0-9]|-|;)*+)|(0|([1-9]([0-9])*+))(\\.(0|([1-9]([0-9])*+)))*+)(,(([a-zA-Z]([a-zA-Z]|[0-9]|-|;)*+)|(0|([1-9]([0-9])*+))(\\.(0|([1-9]([0-9])*+)))*+))*+$</adm:regex>
          <adm:usage>Syntax:
          className:attributeName[,attributeName]
          or
          *:attributeName[,attributeName]
          Note that any class (className) or attribute (attributeName) definition can be replaced with its OID definition.
          Examples:
          - inetOrgPerson:uid,employeeNumber : 'uid' and 'employeeNumber' attributes of any entry of type 'inetOrgPerson' class.
          This can also be 2.16.840.1.113730.3.2.2:0.9.2342.19200300.100.1.1,2.16.840.1.113730.3.1.3 or a mix.
          - *:description : the 'description' attribute of any entry that has this attribute.
          This can also be *:2.5.4.13
          </adm:usage>
          <adm:synopsis>
            Defines attribute(s) of one particular class or of all possible
            classes, to include in the replication.
          </adm:synopsis>
        </adm:pattern>
      </adm:string>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-fractional-include</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="fractional-exclude" multi-valued="true" mandatory="false"
  advanced="true">
    <adm:synopsis>
      Allows to exclude some attributes to replicate to this server.
    </adm:synopsis>
    <adm:description>
      If fractional-exclude configuration attribute is used, attributes
      specified in this attribute will be ignored (not added/modified/deleted)
      when an operation performed from another directory server is being
      replayed in the local server. Note that the usage of this configuration
      attribute is mutually exclusive with the usage of the fractional-include
      attribute.
    </adm:description>
    <adm:default-behavior>
      <adm:undefined/>
    </adm:default-behavior>
    <adm:syntax>
      <adm:string>
        <adm:pattern>
          <!-- This java regex is mostly derived from keystring BNF definition
          that can be found in RFC 2252, section "4.1. Common Encoding Aspects".
          This can be read as: (oid|\*):oid(,oid)*+
          -->
          <adm:regex>^((([a-zA-Z]([a-zA-Z]|[0-9]|-|;)*+)|(0|([1-9]([0-9])*+))(\\.(0|([1-9]([0-9])*+)))*+)|\\*):(([a-zA-Z]([a-zA-Z]|[0-9]|-|;)*+)|(0|([1-9]([0-9])*+))(\\.(0|([1-9]([0-9])*+)))*+)(,(([a-zA-Z]([a-zA-Z]|[0-9]|-|;)*+)|(0|([1-9]([0-9])*+))(\\.(0|([1-9]([0-9])*+)))*+))*+$</adm:regex>
          <adm:usage>Syntax:
          className:attributeName[,attributeName]
          or
          *:attributeName[,attributeName].
          Note that any class (className) or attribute (attributeName) definition can be replaced with its OID definition.
          Examples:
          inetOrgPerson:photo,jpegPhoto : 'photo' and 'jpegPhoto' attributes of any entry of type 'inetOrgPerson' class.
          This can also be 2.16.840.1.113730.3.2.2:0.9.2342.19200300.100.1.7,0.9.2342.19200300.100.1.60 or a mix.
          *:jpegPhoto : the 'jpegPhoto' attribute of any entry that has this attribute.
          This can also be *:0.9.2342.19200300.100.1.60
          </adm:usage>
          <adm:synopsis>
            Defines attribute(s) of one particular class or of all possible
            classes, to exclude from the replication.
          </adm:synopsis>
        </adm:pattern>
      </adm:string>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-fractional-exclude</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:managed-object>
opends/src/messages/messages/replication.properties
@@ -372,4 +372,41 @@
SEVERE_ERR_INVALID_COOKIE_FULL_RESYNC_REQUIRED_158=The provided cookie is \
 valid in the current context due to %s. Full resync is required
SEVERE_ERR_BYTE_COUNT_159=The Server Handler byte count is not correct (Fixed)
NOTICE_ERR_FRACTIONAL_CONFIG_UNKNOWN_OBJECT_CLASS_160=Wrong fractional \
 replication configuration: could not find object class definition for %s in \
 schema
NOTICE_ERR_FRACTIONAL_CONFIG_UNKNOWN_ATTRIBUTE_TYPE_161=Wrong fractional \
 replication configuration : could not find attribute type definition for %s \
 in schema
NOTICE_ERR_FRACTIONAL_CONFIG_NOT_OPTIONAL_ATTRIBUTE_162=Wrong fractional \
 replication configuration : attribute %s is not optional in class %s
NOTICE_ERR_FRACTIONAL_CONFIG_WRONG_FORMAT_163=Wrong fractional replication \
 configuration : wrong format : %s (need at least [<className>|*],attributeName)
NOTICE_ERR_FRACTIONAL_CONFIG_BOTH_MODES_164=Wrong fractional replication \
 configuration : cannot use both exclusive and inclusive modes
NOTICE_ERR_FRACTIONAL_CONFIG_PROHIBITED_ATTRIBUTE_165=Wrong fractional \
 replication configuration : prohibited attribute %s usage
NOTICE_ERR_FRACTIONAL_166=Fractional replication : exception for domain : %s : \
 %s
NOTICE_FRACTIONAL_BAD_DATA_SET_NEED_RESYNC_167=Warning : domain %s fractional \
 replication configuration is inconsistent with backend data set : need \
 resynchronization or fractional configuration to be changed
MILD_ERR_PLUGIN_FRACTIONAL_LDIF_IMPORT_INVALID_PLUGIN_TYPE_168=The fractional \
 replication ldif import plugin is configured with invalid plugin type %s. \
 Only the ldifImport plugin type is allowed
NOTICE_ERR_FULL_UPDATE_IMPORT_FRACTIONAL_BAD_REMOTE_169=The online full \
 update for importing suffix %s data from remote directory server %s has been \
 stopped due to fractional configuration inconsistency between destination \
 and source server : imported data set has not the same fractional configuration
NOTICE_ERR_FULL_UPDATE_IMPORT_FRACTIONAL_REMOTE_IS_FRACTIONAL_170=The online \
 full update for importing suffix %s data from remote directory server %s has \
 been stopped due to fractional configuration inconsistency between \
 destination and source server : imported data set has some fractional \
 configuration but not destination server
NOTICE_ERR_FRACTIONAL_FORBIDDEN_OPERATION_171=The following operation has \
 been forbidden in suffix %s due to inconsistency with the fractional \
 replication configuration : %s
NOTICE_ERR_FRACTIONAL_FORBIDDEN_FULL_UPDATE_FRACTIONAL_172=The export of \
 domain %s from server %s to all other servers of the topology is forbidden as \
 the source server has some fractional configuration : only fractional servers \
 in a replicated topology does not makes sense
opends/src/server/org/opends/server/replication/common/ChangeNumber.java
@@ -41,7 +41,7 @@
  // A String representation of the ChangeNumber suitable for network
  // transmission.
  private String formatedString = null;;
  private String formatedString = null;
  /**
   * Create a new ChangeNumber from a String.
opends/src/server/org/opends/server/replication/plugin/FractionalLDIFImportPlugin.java
New file
@@ -0,0 +1,355 @@
/*
 * 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
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 Sun Microsystems, Inc.
 */
package org.opends.server.replication.plugin;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.opends.messages.Message;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.server.FractionalLDIFImportPluginCfg;
import org.opends.server.admin.std.server.PluginCfg;
import org.opends.server.api.plugin.*;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.replication.plugin.LDAPReplicationDomain.
  AttributeValueStringIterator;
import org.opends.server.types.*;
import static org.opends.messages.ReplicationMessages.*;
/**
 * This class implements a Directory Server plugin that is used in fractional
 * replication when an online full update occurs.
 * The following tasks are done:
 * - check that the fractional configuration (if any) stored in the root entry
 * of the domain is compliant with the fractional configuration of the domain
 *  (if not make online update stop)
 * - perform filtering according to fractional configuration of the domain
 * - flush the fractional configuration of the domain in the root entry
 *  (if no one already present)
 */
public final class FractionalLDIFImportPlugin
  extends DirectoryServerPlugin<FractionalLDIFImportPluginCfg>
  implements ConfigurationChangeListener<FractionalLDIFImportPluginCfg>
{
  /**
   * Creates a new instance of this Directory Server plugin.  Every plugin must
   * implement a default constructor (it is the only one that will be used to
   * create plugins defined in the configuration), and every plugin constructor
   * must call {@code super()} as its first element.
   */
  public FractionalLDIFImportPlugin()
  {
    super();
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final void initializePlugin(Set<PluginType> pluginTypes,
    FractionalLDIFImportPluginCfg configuration)
    throws ConfigException
  {
    // Make sure that the plugin has been enabled for the appropriate types.
    for (PluginType t : pluginTypes)
    {
      switch (t)
      {
        case LDIF_IMPORT:
          // This is acceptable.
          break;
        default:
          Message message =
            ERR_PLUGIN_FRACTIONAL_LDIF_IMPORT_INVALID_PLUGIN_TYPE.get(
            t.toString());
          throw new ConfigException(message);
      }
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final void finalizePlugin()
  {
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final PluginResult.ImportLDIF doLDIFImport(
    LDIFImportConfig importConfig, Entry entry)
  {
    /**
     * See class comment for what we achieve here...
     */
    // Retrieve the replicated domain this entry belongs to
    DN entryDn = entry.getDN();
    LDAPReplicationDomain domain = MultimasterReplication.findDomain(entryDn,
      null);
    if (domain == null)
    {
      // Not part of a replicated domain : nothing to do
      return PluginResult.ImportLDIF.continueEntryProcessing();
    }
    // Is the entry to treat the root entry of the domain ? If yes, analyze the
    // fractional configuration in it and compare with local domain fractional
    // configuration. Stop the import if some inconsistency is detcted
    DN domainBaseDn = domain.getBaseDN();
    if (domainBaseDn.equals(entryDn))
    {
      /*
       * This is the root entry, try to read a fractional configuration from it
       */
      Iterator<String> exclIt = null;
      AttributeType fractionalExcludeType =
        DirectoryServer.getAttributeType(
        LDAPReplicationDomain.REPLICATION_FRACTIONAL_EXCLUDE);
      List<Attribute> exclAttrs =
        entry.getAttribute(fractionalExcludeType);
      Attribute exclAttr = null;
      if (exclAttrs != null)
      {
        exclAttr = exclAttrs.get(0);
        if (exclAttr != null)
        {
          exclIt = new AttributeValueStringIterator(exclAttr.iterator());
        }
      }
      Iterator<String> inclIt = null;
      AttributeType fractionalIncludeType =
        DirectoryServer.getAttributeType(
        LDAPReplicationDomain.REPLICATION_FRACTIONAL_INCLUDE);
      List<Attribute> inclAttrs =
        entry.getAttribute(fractionalIncludeType);
      Attribute inclAttr = null;
      if (inclAttrs != null)
      {
        inclAttr = inclAttrs.get(0);
        if (inclAttr != null)
        {
          inclIt = new AttributeValueStringIterator(inclAttr.iterator());
        }
      }
      // Compare backend and local fractional configuration
      boolean sameConfig = domain.isFractionalConfigConsistent(exclIt, inclIt);
      if (domain.isFractional())
      {
        // Local domain is fractional
        if (sameConfig)
        {
          // Both local and remote fractional configuration are equivalent :
          // follow import, no need to go with filtering as remote backend
          // should be ok
          return PluginResult.ImportLDIF.continueEntryProcessing();
        } else
        {
          // Local domain is fractional, remote domain has not same config
          boolean remoteDomainHasSomeConfig = false;
          if ((exclAttr != null && (exclAttr.size() > 0)) ||
            (inclAttr != null && (inclAttr.size() > 0)))
          {
            remoteDomainHasSomeConfig = true;
          }
          if (remoteDomainHasSomeConfig)
          {
            // Local domain is fractional, remote domain has some config which
            // is different : stop import (error will be logged when import is
            // stopped)
            domain.setImportErrorMessageId(
              LDAPReplicationDomain.IMPORT_ERROR_MESSAGE_BAD_REMOTE);
            domain.setFollowImport(false);
            return PluginResult.ImportLDIF.continueEntryProcessing();
          } else
          {
            // Local domain is fractional but remote domain has no config :
            // flush local config into root entry and follow import with
            // filtering
            flushFractionalConfigIntoEntry(domain, entry);
          }
        }
      } else
      {
        // Local domain is not fractional
        if (sameConfig)
        {
          // None of the local or remote domain has fractional config : nothing
          // more to do : let import finish
          return PluginResult.ImportLDIF.continueEntryProcessing();
        } else
        {
          // Local domain is not fractional but remote one is : stop import :
          // local domain should be configured with the same config as remote
          // one
          domain.setImportErrorMessageId(
              LDAPReplicationDomain.IMPORT_ERROR_MESSAGE_REMOTE_IS_FRACTIONAL);
          domain.setFollowImport(false);
          return PluginResult.ImportLDIF.continueEntryProcessing();
        }
      }
    }
    // If we get here, local domain fractional configuration is enabled.
    // Now filter for potential attributes to be removed.
    domain.fractionalRemoveAttributesFromEntry(entry.getDN().getRDN(),
      entry.getObjectClasses(), entry.getUserAttributes(), true);
    return PluginResult.ImportLDIF.continueEntryProcessing();
  }
  /**
   * Write the fractional configuration in the passed domain into the passed
   * entry.
   * WARNING: assumption is that no fractional attributes at all is already
   * present in the passed entry. Also assumption is that domain fractional
   * configuration is on.
   * @param domain Domain containing the fractional configuration to use
   * @param entry The entry to modify
   */
  private static void flushFractionalConfigIntoEntry(
    LDAPReplicationDomain domain, Entry entry)
  {
    if (domain.isFractional()) // Paranoia check
    {
      // Get the fractional configuration of the domain
      boolean fractionalExclusive = domain.isFractionalExclusive();
      Map<String, List<String>> fractionalSpecificClassesAttributes =
        domain.getFractionalSpecificClassesAttributes();
      List<String> fractionalAllClassesAttributes =
        domain.getFractionalAllClassesAttributes();
      // Create attribute builder for the rigth fractional mode
      String fractAttribute = null;
      if (fractionalExclusive)
      {
        fractAttribute = LDAPReplicationDomain.REPLICATION_FRACTIONAL_EXCLUDE;
      } else
      {
        fractAttribute = LDAPReplicationDomain.REPLICATION_FRACTIONAL_INCLUDE;
      }
      AttributeBuilder attrBuilder = new AttributeBuilder(fractAttribute);
      boolean somethingToFlush = false;
      // Add attribute values for all classes
      int size = fractionalAllClassesAttributes.size();
      if (size > 0)
      {
        String fracValue = "*:";
        int i = 1;
        for (String attrName : fractionalAllClassesAttributes)
        {
          fracValue += attrName;
          if (i < size)
          {
            fracValue += ",";
          }
          i++;
        }
        somethingToFlush = true;
        attrBuilder.add(fracValue);
      }
      // Add attribute values for specific classes
      size = fractionalSpecificClassesAttributes.size();
      if (size > 0)
      {
        for (String className : fractionalSpecificClassesAttributes.keySet())
        {
          int valuesSize =
            fractionalSpecificClassesAttributes.get(className).size();
          if (valuesSize > 0)
          {
            String fracValue = className + ":";
            int i = 1;
            for (String attrName : fractionalSpecificClassesAttributes.get(
              className))
            {
              fracValue += attrName;
              if (i < valuesSize)
              {
                fracValue += ",";
              }
              i++;
            }
            somethingToFlush = true;
            attrBuilder.add(fracValue);
          }
        }
      }
      // Now flush attribute values into entry
      if (somethingToFlush)
      {
        List<AttributeValue> duplicateValues = new ArrayList<AttributeValue>();
        entry.addAttribute(attrBuilder.toAttribute(), duplicateValues);
      }
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean isConfigurationAcceptable(PluginCfg configuration,
    List<Message> unacceptableReasons)
  {
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationChangeAcceptable(
    FractionalLDIFImportPluginCfg configuration,
    List<Message> unacceptableReasons)
  {
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationChange(
    FractionalLDIFImportPluginCfg configuration)
  {
    return new ConfigChangeResult(ResultCode.SUCCESS, false);
  }
}
opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java
@@ -51,13 +51,18 @@
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedSet;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeoutException;
@@ -75,6 +80,7 @@
import org.opends.server.api.DirectoryThread;
import org.opends.server.api.SynchronizationProvider;
import org.opends.server.backends.jeb.BackendImpl;
import org.opends.server.backends.task.Task;
import org.opends.server.config.ConfigException;
import org.opends.server.controls.SubtreeDeleteControl;
import org.opends.server.core.AddOperation;
@@ -99,6 +105,7 @@
import org.opends.server.replication.common.ChangeNumberGenerator;
import org.opends.server.replication.common.ServerState;
import org.opends.server.replication.common.ServerStatus;
import org.opends.server.replication.common.StatusMachineEvent;
import org.opends.server.replication.protocol.AddContext;
import org.opends.server.replication.protocol.AddMsg;
import org.opends.server.replication.protocol.DeleteContext;
@@ -108,12 +115,14 @@
import org.opends.server.replication.protocol.ModifyMsg;
import org.opends.server.replication.protocol.OperationContext;
import org.opends.server.replication.protocol.ProtocolSession;
import org.opends.server.replication.protocol.RoutableMsg;
import org.opends.server.replication.protocol.UpdateMsg;
import org.opends.server.tasks.TaskUtils;
import org.opends.server.types.AbstractOperation;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeBuilder;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.AttributeValues;
import org.opends.server.types.ByteString;
import org.opends.server.types.ConfigChangeResult;
@@ -128,10 +137,12 @@
import org.opends.server.types.LDIFImportConfig;
import org.opends.server.types.Modification;
import org.opends.server.types.ModificationType;
import org.opends.server.types.ObjectClass;
import org.opends.server.types.Operation;
import org.opends.server.types.RDN;
import org.opends.server.types.RawModification;
import org.opends.server.types.ResultCode;
import org.opends.server.types.Schema;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchResultReference;
@@ -227,7 +238,7 @@
  // This list is used to temporary store operations that needs
  // to be replayed at session establishment time.
  private final TreeMap<ChangeNumber, FakeOperation> replayOperations  =
    new TreeMap<ChangeNumber, FakeOperation>();;
    new TreeMap<ChangeNumber, FakeOperation>();
  /**
   * The isolation policy that this domain is going to use.
@@ -252,6 +263,142 @@
  private ServerStateFlush flushThread;
  /**
   * The attribute name used to store the generation id in the backend.
   */
  protected static final String REPLICATION_GENERATION_ID =
    "ds-sync-generation-id";
  /**
   * The attribute name used to store the fractional include configuration in
   * the backend.
   */
  public static final String REPLICATION_FRACTIONAL_INCLUDE =
    "ds-sync-fractional-include";
  /**
   * The attribute name used to store the fractional exclude configuration in
   * the backend.
   */
  public static final String REPLICATION_FRACTIONAL_EXCLUDE =
    "ds-sync-fractional-exclude";
  /**
   * Fractional replication variables.
   */
  // Return type of the parseFractionalConfig method
  private static final int NOT_FRACTIONAL = 0;
  private static final int EXCLUSIVE_FRACTIONAL = 1;
  private static final int INCLUSIVE_FRACTIONAL = 2;
  /**
   * Tells if fractional replication is enabled or not (some fractional
   * constraints have been put in place). If this is true then
   * fractionalExclusive explains the configuration mode and either
   * fractionalSpecificClassesAttributes or fractionalAllClassesAttributes or
   * both should be filled with something.
   */
  private boolean fractional = false;
  /**
   * - If true, tells that the configured fractional replication is exclusive:
   * Every attributes contained in fractionalSpecificClassesAttributes and
   * fractionalAllClassesAttributes should be ignored when replaying operation
   * in local backend.
   * - If false, tells that the configured fractional replication is inclusive:
   * Only attributes contained in fractionalSpecificClassesAttributes and
   * fractionalAllClassesAttributes should be taken into account in local
   * backend.
   */
  private boolean fractionalExclusive = true;
  /**
   * Used in fractional replication: holds attributes of a specific object
   * class.
   * - key = object class (name or OID of the class)
   * - value = the attributes of that class that should be taken into account
   * (inclusive or exclusive fractional replication) (name or OID of the
   * attribute)
   * When an operation coming from the network is to be locally replayed, if the
   * concerned entry has an objectClass attribute equals to 'key':
   * - inclusive mode: only the attributes in 'value' will be added/deleted/
   * modified
   * - exclusive mode: the attributes in 'value' will not be added/deleted/
   * modified
   */
  private Map<String, List<String>> fractionalSpecificClassesAttributes =
    new HashMap<String, List<String>>();
  /**
   * Used in fractional replication: holds attributes of any object class. When
   * an operation coming from the network is to be locally replayed:
   * - inclusive mode: only attributes of the matching entry not present in
   * fractionalAllClassesAttributes will be added/deleted/modified
   * - exclusive mode: attributes of the matching entry present in
   * fractionalAllClassesAttributes will not be added/deleted/modified
   * The attributes may be in human readable form of OID form.
   */
  private List<String> fractionalAllClassesAttributes =
    new ArrayList<String>();
  /**
   * The list of attributes that cannot be used in fractional replication
   * configuration.
   */
  private static final String[] FRACTIONAL_PROHIBITED_ATTRIBUTES = new String[]
  {
    "objectClass",
    "2.5.4.0" // objectClass OID
  };
  /**
   * When true, this flag is used to force the domain status to be put in bad
   * data set just after the connection to the replication server.
   * This must be used when fractional replication is enabled with a
   * configuration different from the previous one (or at the very first
   * fractional usage time) : after connection, a ChangeStatusMsg is sent
   * requesting the bad data set status. Then none of the update messages
   * received from the replication server are taken into account until the
   * backend is synchronized with brand new data set compliant with the new
   * fractional configuration (i.e with compliant fractional configuration in
   * domain root entry).
   */
  private boolean force_bad_data_set = false;
  /**
   * This flag is used by the fractional replication ldif import plugin to
   * stop the (online) import process if a fractional configuration
   * inconsistency is detected by it.
   */
  private boolean followImport = true;
  /**
   * This is the message id to be used when an import is stopped with error by
   * the fractional replication ldif import plugin.
   */
  private int importErrorMessageId = -1;
  /**
   * Message type for ERR_FULL_UPDATE_IMPORT_FRACTIONAL_BAD_REMOTE.
   */
  public static final int IMPORT_ERROR_MESSAGE_BAD_REMOTE = 1;
  /**
   * Message type for ERR_FULL_UPDATE_IMPORT_FRACTIONAL_REMOTE_IS_FRACTIONAL.
   */
  public static final int IMPORT_ERROR_MESSAGE_REMOTE_IS_FRACTIONAL = 2;
  /*
   * Definitions for the return codes of the
   * fractionalFilterOperation(PreOperationModifyOperation
   *  modifyOperation, boolean performFiltering) method
   */
  // The operation contains attributes subject to fractional filtering according
  // to the fractional configuration
  private static final int FRACTIONAL_HAS_FRACTIONAL_FILTERED_ATTRIBUTES = 1;
  // The operation contains no attributes subject to fractional filtering
  // according to the fractional configuration
  private static final int FRACTIONAL_HAS_NO_FRACTIONAL_FILTERED_ATTRIBUTES = 2;
  // The operation should become a no-op
  private static final int FRACTIONAL_BECOME_NO_OP = 3;
  /**
   * The thread that periodically saves the ServerState of this
   * LDAPReplicationDomain in the database.
   */
@@ -326,6 +473,9 @@
    // Get assured configuration
    readAssuredConfig(configuration, false);
    // Get fractional configuration
    readFractionalConfig(configuration, false);
    setGroupId((byte)configuration.getGroupId());
    setURLs(configuration.getReferralsUrl());
@@ -477,6 +627,1352 @@
  }
  /**
   * Returns true if fractional replication is configured in this domain.
   * @return True if fractional replication is configured in this domain.
   */
  public boolean isFractional()
  {
    return fractional;
  }
  /**
   * Returns true if the fractional replication configuration is exclusive mode
   * in this domain, false if inclusive mode.
   * @return True if the fractional replication configuration is exclusive mode
   * in this domain, false if inclusive mode.
   */
  public boolean isFractionalExclusive()
  {
    return fractionalExclusive;
  }
  /**
   * Returns the fractional configuration for specific classes.
   * @return The fractional configuration for specific classes.
   */
  public Map<String, List<String>> getFractionalSpecificClassesAttributes()
  {
    return fractionalSpecificClassesAttributes;
  }
  /**
   * Returns the fractional configuration for all classes.
   * @return The fractional configuration for all classes.
   */
  public List<String> getFractionalAllClassesAttributes()
  {
    return fractionalAllClassesAttributes;
  }
  /**
   * Sets the error message id to be used when online import is stopped with
   * error by the fractional replication ldif import plugin.
   * @param importErrorMessageId The message to use.
   */
  public void setImportErrorMessageId(int importErrorMessageId)
  {
    this.importErrorMessageId = importErrorMessageId;
  }
  /**
   * Sets the boolean telling if the online import currently in progress should
   * continue.
   * @param followImport The boolean telling if the online import currently in
   * progress should continue.
   */
  public void setFollowImport(boolean followImport)
  {
    this.followImport = followImport;
  }
  /**
   * Gets and stores the fractional replication configuration parameters.
   * @param configuration The configuration object
   * @param allowReconnection Tells if one must reconnect if significant changes
   *        occurred
   */
  private void readFractionalConfig(ReplicationDomainCfg configuration,
    boolean allowReconnection)
  {
    boolean needReconnection = false;
    // Prepare fractional configuration variables to parse
    Iterator<String> exclIt = null;
    SortedSet<String> fractionalExclude = configuration.getFractionalExclude();
    if (fractionalExclude != null)
    {
      exclIt = fractionalExclude.iterator();
    }
    Iterator<String> inclIt = null;
    SortedSet<String> fractionalInclude = configuration.getFractionalInclude();
    if (fractionalInclude != null)
    {
      inclIt = fractionalInclude.iterator();
    }
    // Get potentially new fractional configuration
    Map<String, List<String>> newFractionalSpecificClassesAttributes =
    new HashMap<String, List<String>>();
    List<String> newFractionalAllClassesAttributes = new ArrayList<String>();
    int newFractionalMode = NOT_FRACTIONAL;
    try
    {
      newFractionalMode = parseFractionalConfig(exclIt, inclIt,
      newFractionalSpecificClassesAttributes,
      newFractionalAllClassesAttributes);
    }
    catch(ConfigException e)
    {
      // Should not happen as normally already called without problem in
      // isConfigurationChangeAcceptable or isConfigurationAcceptable
      // if we come up to this method
      Message message = NOTE_ERR_FRACTIONAL.get(baseDn.toString(),
        e.getLocalizedMessage());
      logError(message);
      return;
    }
    /**
     * Is there any change in fractional configuration ?
     */
    // Compute current configuration
    int fractionalMode = fractionalConfigToInt();
     try
    {
      needReconnection = !isFractionalConfigEquivalent(fractionalMode,
        fractionalSpecificClassesAttributes, fractionalAllClassesAttributes,
        newFractionalMode, newFractionalSpecificClassesAttributes,
        newFractionalAllClassesAttributes);
    }
    catch  (ConfigException e)
    {
      // Should not happen
      Message message = NOTE_ERR_FRACTIONAL.get(baseDn.toString(),
        e.getLocalizedMessage());
      logError(message);
      return;
    }
    // Disable service if configuration changed
    if (needReconnection && allowReconnection)
      disableService();
    // Set new configuration
    fractional = (newFractionalMode != NOT_FRACTIONAL);
    if (fractional)
    {
      // Set new fractional configuration values
      if (newFractionalMode == EXCLUSIVE_FRACTIONAL)
        fractionalExclusive = true;
      else
        fractionalExclusive = false;
      fractionalSpecificClassesAttributes =
        newFractionalSpecificClassesAttributes;
      fractionalAllClassesAttributes = newFractionalAllClassesAttributes;
    } else
    {
      // Reset default values
      fractionalExclusive = true;
      fractionalSpecificClassesAttributes =
        new HashMap<String, List<String>>();
      fractionalAllClassesAttributes =
        new ArrayList<String>();
    }
    // Reconnect if required
    if (needReconnection && allowReconnection)
      enableService();
  }
  /**
   * Return true if the fractional configuration stored in the domain root
   * entry of the backend is equivalent to the fractional configuration stored
   * in the local variables.
   */
  private boolean isBackendFractionalConfigConsistent()
  {
    /*
     * Read config stored in domain root entry
     */
    if (debugEnabled())
      TRACER.debugInfo(
        "Attempt to read the potential fractional config in domain root " +
        "entry " + baseDn.toString());
    ByteString asn1BaseDn = ByteString.valueOf(baseDn.toString());
    boolean found = false;
    LDAPFilter filter;
    try
    {
      filter = LDAPFilter.decode("objectclass=*");
    } catch (LDAPException e)
    {
      // Can not happen
      return false;
    }
    /*
     * Search the domain root entry that is used to save the generation id
     */
    InternalSearchOperation search = null;
    LinkedHashSet<String> attributes = new LinkedHashSet<String>(1);
    attributes.add(REPLICATION_GENERATION_ID);
    attributes.add(REPLICATION_FRACTIONAL_EXCLUDE);
    attributes.add(REPLICATION_FRACTIONAL_INCLUDE);
    search = conn.processSearch(asn1BaseDn,
      SearchScope.BASE_OBJECT,
      DereferencePolicy.DEREF_ALWAYS, 0, 0, false,
      filter, attributes);
    if (((search.getResultCode() != ResultCode.SUCCESS)) &&
      ((search.getResultCode() != ResultCode.NO_SUCH_OBJECT)))
    {
      Message message = ERR_SEARCHING_GENERATION_ID.get(
        search.getResultCode().getResultCodeName() + " " +
        search.getErrorMessage(),
        baseDn.toString());
      logError(message);
      return false;
    }
    SearchResultEntry resultEntry = null;
    if (search.getResultCode() == ResultCode.SUCCESS)
    {
      LinkedList<SearchResultEntry> result = search.getSearchEntries();
      resultEntry = result.getFirst();
      if (resultEntry != null)
      {
        AttributeType synchronizationGenIDType =
          DirectoryServer.getAttributeType(REPLICATION_GENERATION_ID);
        List<Attribute> attrs =
          resultEntry.getAttribute(synchronizationGenIDType);
        if (attrs != null)
        {
          Attribute attr = attrs.get(0);
          if (attr.size() > 1)
          {
            Message message = ERR_LOADING_GENERATION_ID.get(
              baseDn.toString(), "#Values=" + attr.size() +
              " Must be exactly 1 in entry " +
              resultEntry.toLDIFString());
            logError(message);
          } else if (attr.size() == 1)
          {
            found = true;
          }
        }
      }
    }
    if (!found)
    {
      // The backend is probably empty: if there is some fractional
      // configuration in memory, we do not let the domain being connected,
      // otherwise, it's ok
      if (fractional)
      {
        return false;
      }
      else
      {
        return true;
      }
    }
    /*
     * Now extract fractional configuration if any
     */
    Iterator<String> exclIt = null;
    AttributeType fractionalExcludeType =
      DirectoryServer.getAttributeType(REPLICATION_FRACTIONAL_EXCLUDE);
    List<Attribute> exclAttrs =
      resultEntry.getAttribute(fractionalExcludeType);
    if (exclAttrs != null)
    {
      Attribute exclAttr = exclAttrs.get(0);
      if (exclAttr != null)
      {
        exclIt = new AttributeValueStringIterator(exclAttr.iterator());
      }
    }
    Iterator<String> inclIt = null;
    AttributeType fractionalIncludeType =
      DirectoryServer.getAttributeType(REPLICATION_FRACTIONAL_INCLUDE);
    List<Attribute> inclAttrs =
      resultEntry.getAttribute(fractionalIncludeType);
    if (inclAttrs != null)
    {
      Attribute inclAttr = inclAttrs.get(0);
      if (inclAttr != null)
      {
        inclIt = new AttributeValueStringIterator(inclAttr.iterator());
      }
    }
    // Compare backend and local fractional configuration
    return isFractionalConfigConsistent(exclIt, inclIt);
  }
  /**
   * Return true if the fractional configuration passed as fractional
   * configuration attribute values is equivalent to the fractional
   * configuration stored in the local variables.
   * @param exclIt Fractional exclude mode configuration attribute values to
   * analyze.
   * @param inclIt Fractional include mode configuration attribute values to
   * analyze.
   * @return True if the fractional configuration passed as fractional
   * configuration attribute values is equivalent to the fractional
   * configuration stored in the local variables.
   */
  public boolean isFractionalConfigConsistent(Iterator<String> exclIt,
    Iterator<String> inclIt)
  {
    /*
     * Parse fractional configuration stored in passed fractional configuration
     * attributes values
     */
    Map<String, List<String>> storedFractionalSpecificClassesAttributes =
      new HashMap<String, List<String>>();
    List<String> storedFractionalAllClassesAttributes = new ArrayList<String>();
    int storedFractionalMode = NOT_FRACTIONAL;
    try
    {
      storedFractionalMode = parseFractionalConfig(exclIt, inclIt,
        storedFractionalSpecificClassesAttributes,
        storedFractionalAllClassesAttributes);
    } catch (ConfigException e)
    {
      // Should not happen as configuration in domain root entry is flushed
      // from valid configuration in local variables
      Message message = NOTE_ERR_FRACTIONAL.get(baseDn.toString(),
        e.getLocalizedMessage());
      logError(message);
      return false;
    }
    /*
     * Compare configuration stored in passed fractional configuration
     * attributes with local variable one
     */
    // Compute current configuration from local variables
    int fractionalMode = fractionalConfigToInt();
    try
    {
      return isFractionalConfigEquivalent(fractionalMode,
        fractionalSpecificClassesAttributes, fractionalAllClassesAttributes,
        storedFractionalMode, storedFractionalSpecificClassesAttributes,
        storedFractionalAllClassesAttributes);
    } catch (ConfigException e)
    {
      // Should not happen as configuration in domain root entry is flushed
      // from valid configuration in local variables so both should have already
      // been checked
      Message message = NOTE_ERR_FRACTIONAL.get(baseDn.toString(),
        e.getLocalizedMessage());
      logError(message);
      return false;
    }
  }
  /**
   * Get an integer representation of the domain fractional configuration.
   * @return An integer representation of the domain fractional configuration.
   */
  public int fractionalConfigToInt()
  {
    int fractionalMode = -1;
    if (fractional)
    {
      if (fractionalExclusive)
        fractionalMode = EXCLUSIVE_FRACTIONAL;
      else
        fractionalMode = INCLUSIVE_FRACTIONAL;
    } else
    {
      fractionalMode = NOT_FRACTIONAL;
    }
    return fractionalMode;
  }
  /**
   * Utility class to have get a sting iterator from an AtributeValue iterator.
   * Assuming the attribute values are strings.
   */
  public static class AttributeValueStringIterator implements Iterator<String>
  {
    private Iterator<AttributeValue> attrValIt = null;
    /**
     * Creates a new AttributeValueStringIterator object.
     * @param attrValIt The underlying attribute iterator to use, assuming
     * internal values are strings.
     */
    public AttributeValueStringIterator(Iterator<AttributeValue> attrValIt)
    {
      this.attrValIt = attrValIt;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean hasNext()
    {
      return attrValIt.hasNext();
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public String next()
    {
      return attrValIt.next().getValue().toString();
    }
    /**
     * {@inheritDoc}
     */
    @Override
    // Should not be needed anyway
    public void remove()
    {
      attrValIt.remove();
    }
  }
  /**
   * Compare 2 fractional replication configurations and returns true if they
   * are equivalent.
   * @param mode1 Fractional mode 1
   * @param specificClassesAttributes1 Specific classes attributes 1
   * @param allClassesAttributes1 All classes attributes 1
   * @param mode2 Fractional mode 1
   * @param specificClassesAttributes2 Specific classes attributes 2
   * @param allClassesAttributes2 Fractional mode 2
   * @return True if both configurations are equivalent.
   * @throws ConfigException If some classes or attributes could not be
   * retrieved from the schema.
   */
  private static boolean isFractionalConfigEquivalent(int mode1,
    Map<String, List<String>> specificClassesAttributes1,
    List<String> allClassesAttributes1, int mode2,
    Map<String, List<String>> specificClassesAttributes2,
    List<String> allClassesAttributes2) throws ConfigException
  {
    // Compare modes
    if (mode1 != mode2)
      return false;
    // Compare all classes attributes
    if (!isAttributeListEquivalent(allClassesAttributes1,
      allClassesAttributes2))
            return false;
    // Compare specific classes attributes
    if (specificClassesAttributes1.size() != specificClassesAttributes2.size())
      return false;
    // Check consistency of specific classes attributes
    /*
     * For each class in specificClassesAttributes1, check that the attribute
     * list is equivalent to specificClassesAttributes2 attribute list
     */
    Schema schema = DirectoryServer.getSchema();
    for (String className1 : specificClassesAttributes1.keySet())
    {
      // Get class from specificClassesAttributes1
      ObjectClass objectClass1 = schema.getObjectClass(className1);
      if (objectClass1 == null)
      {
        throw new ConfigException(
          NOTE_ERR_FRACTIONAL_CONFIG_UNKNOWN_OBJECT_CLASS.get(className1));
      }
      // Look for matching one in specificClassesAttributes2
      boolean foundClass = false;
      for (String className2 : specificClassesAttributes2.keySet())
      {
        ObjectClass objectClass2 = schema.getObjectClass(className2);
        if (objectClass2 == null)
        {
          throw new ConfigException(
            NOTE_ERR_FRACTIONAL_CONFIG_UNKNOWN_OBJECT_CLASS.get(className2));
        }
        if (objectClass1.equals(objectClass2))
        {
          foundClass = true;
          // Now compare the 2 attribute lists
          List<String> attributes1 = specificClassesAttributes1.get(className1);
          List<String> attributes2 = specificClassesAttributes2.get(className2);
          if (!isAttributeListEquivalent(attributes1, attributes2))
            return false;
          break;
        }
      }
      // Found matching class ?
      if (!foundClass)
        return false;
    }
    return true;
  }
  /**
   * Compare 2 attribute lists and returns true if they are equivalent.
   * @param attributes1 First attribute list to compare.
   * @param attributes2 Second attribute list to compare.
   * @return True if both attribute lists are equivalent.
   * @throws ConfigException If some attributes could not be retrieved from the
   * schema.
   */
  private static boolean isAttributeListEquivalent(
    List<String> attributes1, List<String> attributes2) throws ConfigException
  {
    // Compare all classes attributes
    if (attributes1.size() != attributes2.size())
      return false;
    // Check consistency of all classes attributes
    Schema schema = DirectoryServer.getSchema();
    /*
     * For each attribute in attributes1, check there is the matching
     * one in attributes2.
     */
    for (String attributName1 : attributes1)
    {
      // Get attribute from attributes1
      AttributeType attributeType1 = schema.getAttributeType(attributName1);
      if (attributeType1 == null)
      {
        throw new ConfigException(
          NOTE_ERR_FRACTIONAL_CONFIG_UNKNOWN_ATTRIBUTE_TYPE.get(attributName1));
      }
      // Look for matching one in attributes2
      boolean foundAttribute = false;
      for (String attributName2 : attributes2)
      {
        AttributeType attributeType2 = schema.getAttributeType(attributName2);
        if (attributeType2 == null)
        {
          throw new ConfigException(
            NOTE_ERR_FRACTIONAL_CONFIG_UNKNOWN_ATTRIBUTE_TYPE.get(
            attributName2));
        }
        if (attributeType1.equals(attributeType2))
        {
          foundAttribute = true;
          break;
        }
      }
      // Found matching attribute ?
      if (!foundAttribute)
        return false;
    }
    return true;
  }
  /**
   * Parses a fractional replication configuration, filling the empty passed
   * variables and returning the used fractional mode. The 2 passed variables to
   * fill should be initialized (not null) and empty.
   * @param exclIt The list of fractional exclude configuration values (may be
   *               null)
   * @param inclIt The list of fractional include configuration values (may be
   *               null)
   * @param fractionalSpecificClassesAttributes An empty map to be filled with
   *        what is read from the fractional configuration properties.
   * @param fractionalAllClassesAttributes An empty list to be filled with what
   *        is read from the fractional configuration properties.
   * @return the fractional mode deduced from the passed configuration:
   *         not fractional, exclusive fractional or inclusive fractional modes
   */
  private static int parseFractionalConfig (
    Iterator<String> exclIt, Iterator<String> inclIt,
    Map<String, List<String>> fractionalSpecificClassesAttributes,
    List<String> fractionalAllClassesAttributes) throws ConfigException
  {
    int fractional_mode = NOT_FRACTIONAL;
    // Determine if fractional-exclude or fractional-include property is used
    // : only one of them is allowed
    Iterator<String> fracConfIt = null;
    // Deduce the wished fractional mode
    if ((exclIt != null) && exclIt.hasNext())
    {
      if ((inclIt != null) && inclIt.hasNext())
      {
        throw new ConfigException(NOTE_ERR_FRACTIONAL_CONFIG_BOTH_MODES.get());
      }
      else
      {
        fractional_mode = EXCLUSIVE_FRACTIONAL;
        fracConfIt = exclIt;
      }
    }
    else
    {
      if ((inclIt != null) && inclIt.hasNext())
      {
        fractional_mode = INCLUSIVE_FRACTIONAL;
        fracConfIt = inclIt;
      }
      else
      {
        return NOT_FRACTIONAL;
      }
    }
    while (fracConfIt.hasNext())
    {
      // Parse a value with the form class:attr1,attr2...
      // or *:attr1,attr2...
      String fractCfgStr = fracConfIt.next();
      StringTokenizer st = new StringTokenizer(fractCfgStr, ":");
      int nTokens = st.countTokens();
      if (nTokens < 2)
      {
        throw new ConfigException(NOTE_ERR_FRACTIONAL_CONFIG_WRONG_FORMAT.
          get(fractCfgStr));
      }
      // Get the class name
      String classNameLower = st.nextToken().toLowerCase();
      boolean allClasses = classNameLower.equals("*");
      // Get the attributes
      String attributes = st.nextToken();
      st = new StringTokenizer(attributes, ",");
      while (st.hasMoreTokens())
      {
        String attrNameLower = st.nextToken().toLowerCase();
        // Store attribute in the appropriate variable
        if (allClasses)
        {
          // Avoid duplicate attributes
          if (!fractionalAllClassesAttributes.contains(attrNameLower))
          {
            fractionalAllClassesAttributes.add(attrNameLower);
          }
        }
        else
        {
          List<String> attrList =
            fractionalSpecificClassesAttributes.get(classNameLower);
          if (attrList != null)
          {
            // Avoid duplicate attributes
            if (!attrList.contains(attrNameLower))
            {
              attrList.add(attrNameLower);
            }
          } else
          {
            attrList = new ArrayList<String>();
            attrList.add(attrNameLower);
            fractionalSpecificClassesAttributes.put(classNameLower, attrList);
          }
        }
      }
    }
    return fractional_mode;
  }
  /**
   * Check that the passed fractional configuration is acceptable
   * regarding configuration syntax, schema constraints...
   * Throws an exception if the configuration is not acceptable.
   * @param configuration The configuration to analyze.
   * @throws org.opends.server.config.ConfigException if the configuration is
   * not acceptable.
   */
  private static void isFractionalConfigAcceptable(
    ReplicationDomainCfg configuration) throws ConfigException
  {
    /*
     * Parse fractional configuration
     */
    // Prepare fractional configuration variables to parse
    Iterator<String> exclIt = null;
    SortedSet<String> fractionalExclude = configuration.getFractionalExclude();
    if (fractionalExclude != null)
    {
      exclIt = fractionalExclude.iterator();
    }
    Iterator<String> inclIt = null;
    SortedSet<String> fractionalInclude = configuration.getFractionalInclude();
    if (fractionalInclude != null)
    {
      inclIt = fractionalInclude.iterator();
    }
    // Prepare variables to be filled with config
    Map<String, List<String>> newFractionalSpecificClassesAttributes =
    new HashMap<String, List<String>>();
    List<String> newFractionalAllClassesAttributes = new ArrayList<String>();
    int fractionalMode = parseFractionalConfig(exclIt, inclIt,
      newFractionalSpecificClassesAttributes,
      newFractionalAllClassesAttributes);
    switch (fractionalMode)
    {
      case NOT_FRACTIONAL:
        // Nothing to check
        return;
      case EXCLUSIVE_FRACTIONAL:
      case INCLUSIVE_FRACTIONAL:
        // Ok, checking done out of the switch statement
        break;
      default:
      // Should not happen
        return;
    }
    /*
     * Check attributes consistency : we only allow to filter MAY (optional)
     * attributes of a class : to be compliant with the schema, no MUST
     * (mandatory) attribute can be filtered by fractional replication.
     */
    // Check consistency of specific classes attributes
    Schema schema = DirectoryServer.getSchema();
    for (String className : newFractionalSpecificClassesAttributes.keySet())
    {
      // Does the class exist ?
      ObjectClass fractionalClass = schema.getObjectClass(
        className.toLowerCase());
      if (fractionalClass == null)
      {
        throw new ConfigException(
          NOTE_ERR_FRACTIONAL_CONFIG_UNKNOWN_OBJECT_CLASS.get(className));
      }
      boolean isExtensibleObjectClass = className.
        equalsIgnoreCase("extensibleObject");
      List<String> attributes =
        newFractionalSpecificClassesAttributes.get(className);
      for (String attrName : attributes)
      {
        // Not a prohibited attribute ?
        if (isFractionalProhibitedAttr(attrName))
        {
          throw new ConfigException(
            NOTE_ERR_FRACTIONAL_CONFIG_PROHIBITED_ATTRIBUTE.get(attrName));
        }
        // Does the attribute exist ?
        AttributeType attributeType = schema.getAttributeType(attrName);
        if (attributeType != null)
        {
          // No more checking for the extensibleObject class
          if (!isExtensibleObjectClass)
          {
            if (fractionalMode == EXCLUSIVE_FRACTIONAL)
            {
              // Exclusive mode : the attribute must be optional
              if (!fractionalClass.isOptional(attributeType))
              {
                throw new ConfigException(
                  NOTE_ERR_FRACTIONAL_CONFIG_NOT_OPTIONAL_ATTRIBUTE.
                  get(attrName, className));
              }
            }
          }
        }
        else
        {
          throw new ConfigException(
            NOTE_ERR_FRACTIONAL_CONFIG_UNKNOWN_ATTRIBUTE_TYPE.get(attrName));
        }
      }
    }
    // Check consistency of all classes attributes
    for (String attrName : newFractionalAllClassesAttributes)
    {
      // Not a prohibited attribute ?
      if (isFractionalProhibitedAttr(attrName))
      {
        throw new ConfigException(
          NOTE_ERR_FRACTIONAL_CONFIG_PROHIBITED_ATTRIBUTE.get(attrName));
      }
      // Does the attribute exist ?
      if (schema.getAttributeType(attrName) == null)
      {
        throw new ConfigException(
          NOTE_ERR_FRACTIONAL_CONFIG_UNKNOWN_ATTRIBUTE_TYPE.get(attrName));
      }
    }
  }
  /**
   * Test if the passed attribute is not allowed to be used in configuration of
   * fractional replication.
   * @param attr Attribute to test.
   * @return true if the attribute is prohibited.
   */
  private static boolean isFractionalProhibitedAttr(String attr)
  {
    for (String forbiddenAttr : FRACTIONAL_PROHIBITED_ATTRIBUTES)
    {
      if (forbiddenAttr.equalsIgnoreCase(attr))
      {
        return true;
      }
    }
    return false;
  }
  /**
   * If fractional replication is enabled, this analyzes the operation and
   * suppresses the forbidden attributes in it so that they are not added in
   * the local backend.
   *
   * @param addOperation The operation to modify based on fractional
   * replication configuration
   * @param performFiltering Tells if the effective attribute filtering should
   * be performed or if the call is just to analyze if there are some
   * attributes filtered by fractional configuration
   * @return true if the operation contains some attributes subject to filtering
   * by the fractional configuration
   */
  public boolean fractionalFilterOperation(
    PreOperationAddOperation addOperation, boolean performFiltering)
  {
    return fractionalRemoveAttributesFromEntry(
      addOperation.getEntryDN().getRDN(), addOperation.getObjectClasses(),
      addOperation.getUserAttributes(), performFiltering);
  }
  /**
   * If fractional replication is enabled, this analyzes the operation and
   * suppresses the forbidden attributes in it so that they are not added in
   * the local backend.
   *
   * @param modifyDNOperation The operation to modify based on fractional
   * replication configuration
   * @param performFiltering Tells if the effective modifications should
   * be performed or if the call is just to analyze if there are some
   * inconsistency with fractional configuration
   * @return true if the operation is inconsistent with fractional configuration
   */
  public boolean fractionalFilterOperation(
    PreOperationModifyDNOperation modifyDNOperation, boolean performFiltering)
  {
    boolean inconsistentOperation = false;
    // Quick exit if not called for analyze and
    if (performFiltering)
    {
      if (modifyDNOperation.deleteOldRDN())
      {
        // The core will remove any occurence of attribute that was part of the
        // old RDN, nothing more to do.
        return true; // Will not be used as analyze was not requested
      }
    }
    /*
     * Create a list of filtered attributes for this entry
     */
    Entry concernedEntry = modifyDNOperation.getOriginalEntry();
    List<String> fractionalConcernedAttributes =
      createFractionalConcernedAttrList(
      concernedEntry.getObjectClasses().keySet());
    if ( fractionalExclusive && (fractionalConcernedAttributes.size() == 0) )
      // No attributes to filter
      return false;
    /**
     * Analyze the old and new rdn to see if they are some attributes to be
     * removed: if the oldnRDN contains some forbidden attributes (for instance
     * it is possible if the entry was created with an add operation and the
     * RDN used contains a forbidden attribute: in this case the attribute value
     * has been kept to be consistent with the dn of the entry.) that are no
     * more part of the new RDN, we must remove any attribute of this type by
     * putting a modification to delete the attribute.
     */
    RDN rdn = modifyDNOperation.getEntryDN().getRDN();
    RDN newRdn = modifyDNOperation.getNewRDN();
    // Go through each attribute of the old RDN
    for (int i=0 ; i<rdn.getNumValues() ; i++)
    {
      AttributeType attributeType = rdn.getAttributeType(i);
      boolean found = false;
      // Is it present in the fractional attributes established list ?
      for (String attrTypeStr : fractionalConcernedAttributes)
      {
        AttributeType attributeTypeFromList =
        DirectoryServer.getAttributeType(attrTypeStr);
        if (attributeTypeFromList.equals(attributeType))
        {
          found = true;
          break;
        }
      }
      boolean attributeToBeFiltered = ( (fractionalExclusive && found) ||
        (!fractionalExclusive && !found) );
      if (attributeToBeFiltered &&
        !newRdn.hasAttributeType(attributeType) &&
        !modifyDNOperation.deleteOldRDN())
      {
        // A forbidden attribute is in the old RDN and no more in the new RDN,
        // and it has not been requested to remove attributes from old RDN:
        // remove ourself the attribute from the entry to stay consistent with
        // fractional configuration
        Modification modification = new Modification(ModificationType.DELETE,
          Attributes.empty(attributeType));
        modifyDNOperation.addModification(modification);
        inconsistentOperation = true;
      }
    }
    return inconsistentOperation;
  }
  /**
   * Remove attributes from an entry, according to the current fractional
   * configuration. The entry is represented by the 2 passed parameters.
   * The attributes to be removed are removed using the remove method on the
   * passed iterator for the attributes in the entry.
   * @param entryRdn The rdn of the entry to add
   * @param classes The object classes representing the entry to modify
   * @param attributesMap The map of attributes/values to be potentially removed
   * from the entry.
   * @param performFiltering Tells if the effective attribute filtering should
   * be performed or if the call is just an analyze to see if there are some
   * attributes filtered by fractional configuration
   * @return true if the operation contains some attributes subject to filtering
   * by the fractional configuration
   */
  public boolean fractionalRemoveAttributesFromEntry(RDN entryRdn,
    Map<ObjectClass,String> classes, Map<AttributeType, List<Attribute>>
    attributesMap, boolean performFiltering)
  {
    boolean hasSomeAttributesToFilter = false;
    /*
     * Prepare a list of attributes to be included/excluded according to the
     * fractional replication configuration
     */
    List<String> fractionalConcernedAttributes =
      createFractionalConcernedAttrList(classes.keySet());
    if ( fractionalExclusive && (fractionalConcernedAttributes.size() == 0) )
      return false; // No attributes to filter
    // Prepare list of object classes of the added entry
    Set<ObjectClass> entryClasses = classes.keySet();
    /*
     * Go through the user attributes and remove those that match filtered one
     * - exclude mode : remove only attributes that are in
     * fractionalConcernedAttributes
     * - include mode : remove any attribute that is not in
     * fractionalConcernedAttributes
     */
    Iterator<AttributeType> attributeTypes = attributesMap.keySet().iterator();
    List<List<Attribute>> newRdnAttrLists = new ArrayList<List<Attribute>>();
    List<AttributeType> rdnAttrTypes = new ArrayList<AttributeType>();
    while (attributeTypes.hasNext())
    {
      AttributeType attributeType = attributeTypes.next();
      // Only optional attributes may be removed
      boolean isMandatoryAttribute = false;
      for (ObjectClass objectClass : entryClasses)
      {
        if (objectClass.isRequired(attributeType))
        {
          isMandatoryAttribute = true;
          break;
        }
      }
      if (isMandatoryAttribute)
      {
        continue;
      }
      String attributeName = attributeType.getPrimaryName();
      String attributeOid = attributeType.getOID();
      // Do not remove an attribute if it is a prohibited one
      if (((attributeName != null) &&
        isFractionalProhibitedAttr(attributeName)) ||
        isFractionalProhibitedAttr(attributeOid))
      {
        continue;
      }
      // Is the current attribute part of the established list ?
      boolean foundAttribute =
        fractionalConcernedAttributes.contains(attributeName.toLowerCase());
      if (!foundAttribute)
      {
        foundAttribute =
          fractionalConcernedAttributes.contains(attributeOid);
      }
      // Now remove the attribute if:
      // - exclusive mode and attribute is in configuration
      // - inclusive mode and attribute is not in configuration
      if ((foundAttribute && fractionalExclusive) ||
        (!foundAttribute && !fractionalExclusive))
      {
        if (performFiltering)
        {
          // Do not remove an attribute/value that is part of the RDN of the
          // entry as it is forbidden
          if (entryRdn.hasAttributeType(attributeType))
          {
            // We must remove all values of the attributes map for this
            // attribute type but the one that has the value which is in the RDN
            // of the entry. In fact the (underlying )attribute list does not
            // suppot remove so we have to create a new list, keeping only the
            // attribute value which is the same as in the RDN
            AttributeValue rdnAttributeValue =
              entryRdn.getAttributeValue(attributeType);
            List<Attribute> attrList = attributesMap.get(attributeType);
            Iterator<Attribute> attrIt = attrList.iterator();
            AttributeValue sameAttrValue = null;
            //    Locate the attribute value identical to the one in the RDN
            while(attrIt.hasNext())
            {
              Attribute attr = attrIt.next();
              if (attr.contains(rdnAttributeValue))
              {
                Iterator<AttributeValue> attrValues = attr.iterator();
                while(attrValues.hasNext())
                {
                  AttributeValue attrValue = attrValues.next();
                  if (rdnAttributeValue.equals(attrValue))
                  {
                    // Keep the value we want
                    sameAttrValue = attrValue;
                  }
                  else
                  {
                    hasSomeAttributesToFilter = true;
                  }
                }
              }
              else
              {
                hasSomeAttributesToFilter = true;
              }
            }
            //    Recreate the attribute list with only the RDN attribute value
            if (sameAttrValue != null)
              // Paranoia check: should never be the case as we should always
              // find the attribute/value pair matching the pair in the RDN
            {
              // Construct and store new atribute list
              List<Attribute> newRdnAttrList = new ArrayList<Attribute>();
              AttributeBuilder attrBuilder =
                new AttributeBuilder(attributeType);
              attrBuilder.add(sameAttrValue);
              newRdnAttrList.add(attrBuilder.toAttribute());
              newRdnAttrLists.add(newRdnAttrList);
              // Store matching attribute type
              // The mapping will be done using object from rdnAttrTypes as key
              // and object from newRdnAttrLists (at same index) as value in
              // the user attribute map to be modified
              rdnAttrTypes.add(attributeType);
            }
          }
          else
          {
            // Found an attribute to remove, remove it from the list.
            attributeTypes.remove();
            hasSomeAttributesToFilter = true;
          }
        }
        else
        {
          // The call was just to check : at least one attribute to filter
          // found, return immediatly the answer;
          return true;
        }
      }
    }
    // Now overwrite the attribute values for the attribute types present in the
    // RDN, if there are some filtered attributes in the RDN
    int index = 0;
    for (index = 0 ; index < rdnAttrTypes.size() ; index++)
    {
      attributesMap.put(rdnAttrTypes.get(index), newRdnAttrLists.get(index));
    }
    return hasSomeAttributesToFilter;
  }
  /**
   * Prepares a list of attributes of interest for the fractional feature.
   * @param entryObjectClasses The object classes of an entry on which an
   * operation is going to be performed.
   * @return The list of attributes of the entry to be excluded/included
   * when the operation will be performed.
   */
  private List<String> createFractionalConcernedAttrList(
    Set<ObjectClass> entryObjectClasses)
  {
    /*
     * Is the concerned entry of a type concerned by fractional replication
     * configuration ? If yes, add the matching attribute names to a list of
     * attributes to take into account for filtering
     * (inclusive or exclusive mode)
     */
    List<String> fractionalConcernedAttributes = new ArrayList<String>();
    // Get object classes the entry matches
    Set<String> fractionalClasses =
        fractionalSpecificClassesAttributes.keySet();
    for (ObjectClass entryObjectClass : entryObjectClasses)
    {
      for(String fractionalClass : fractionalClasses)
      {
        if (entryObjectClass.hasNameOrOID(fractionalClass.toLowerCase()))
        {
          List<String> attrList =
            fractionalSpecificClassesAttributes.get(fractionalClass);
          for(String attr : attrList)
          {
            // Avoid duplicate attributes (from 2 inheriting classes for
            // instance)
            if (!fractionalConcernedAttributes.contains(attr))
            {
              fractionalConcernedAttributes.add(attr);
            }
          }
        }
      }
    }
    /*
     * Add to the list any attribute which is class independent
     */
    for (String attr : fractionalAllClassesAttributes)
    {
      if (!fractionalConcernedAttributes.contains(attr))
      {
        fractionalConcernedAttributes.add(attr);
      }
    }
    return fractionalConcernedAttributes;
  }
  /**
   * If fractional replication is enabled, this analyzes the operation and
   * suppresses the forbidden attributes in it so that they are not added/
   * deleted/modified in the local backend.
   *
   * @param modifyOperation The operation to modify based on fractional
   * replication configuration
   * @param performFiltering Tells if the effective attribute filtering should
   * be performed or if the call is just to analyze if there are some
   * attributes filtered by fractional configuration
   * @return FRACTIONAL_HAS_FRACTIONAL_FILTERED_ATTRIBUTES,
   * FRACTIONAL_HAS_NO_FRACTIONAL_FILTERED_ATTRIBUTES or FRACTIONAL_BECOME_NO_OP
   */
  public int fractionalFilterOperation(PreOperationModifyOperation
    modifyOperation, boolean performFiltering)
  {
    int result = FRACTIONAL_HAS_NO_FRACTIONAL_FILTERED_ATTRIBUTES;
    /*
     * Prepare a list of attributes to be included/excluded according to the
     * fractional replication configuration
     */
    Entry modifiedEntry = modifyOperation.getCurrentEntry();
    List<String> fractionalConcernedAttributes =
      createFractionalConcernedAttrList(
      modifiedEntry.getObjectClasses().keySet());
    if ( fractionalExclusive && (fractionalConcernedAttributes.size() == 0) )
      // No attributes to filter
      return FRACTIONAL_HAS_NO_FRACTIONAL_FILTERED_ATTRIBUTES;
    // Prepare list of object classes of the modified entry
    DN entryToModifyDn = modifyOperation.getEntryDN();
    Entry entryToModify = null;
    try
    {
      entryToModify = DirectoryServer.getEntry(entryToModifyDn);
    }
    catch(DirectoryException e)
    {
      Message message = NOTE_ERR_FRACTIONAL.get(baseDn.toString(),
        e.getLocalizedMessage());
      logError(message);
      return FRACTIONAL_HAS_NO_FRACTIONAL_FILTERED_ATTRIBUTES;
    }
    Set<ObjectClass> entryClasses = entryToModify.getObjectClasses().keySet();
    /*
     * Now go through the attribute modifications and filter the mods according
     * to the fractional configuration (using the just established concerned
     * attributes list):
     * - delete attributes: remove them if regarding a filtered attribute
     * - add attributes: remove them if regarding a filtered attribute
     * - modify attributes: remove them if regarding a filtered attribute
     */
    List<Modification> mods = modifyOperation.getModifications();
    Iterator<Modification> modsIt = mods.iterator();
    while (modsIt.hasNext())
    {
      Modification mod = modsIt.next();
      Attribute attr = mod.getAttribute();
      AttributeType attrType = attr.getAttributeType();
      // Fractional replication ignores operational attributes
      if (!attrType.isOperational())
      {
        // Only optional attributes may be removed
        boolean isMandatoryAttribute = false;
        for (ObjectClass objectClass : entryClasses)
        {
          if (objectClass.isRequired(attrType))
          {
            isMandatoryAttribute = true;
            break;
          }
        }
        if (isMandatoryAttribute)
        {
          continue;
        }
        String attributeName = attrType.getPrimaryName();
        String attributeOid = attrType.getOID();
        // Do not remove an attribute if it is a prohibited one
        if (((attributeName != null) &&
          isFractionalProhibitedAttr(attributeName)) ||
          isFractionalProhibitedAttr(attributeOid))
        {
          continue;
        }
        // Is the current attribute part of the established list ?
        boolean foundAttribute =
          fractionalConcernedAttributes.contains(attributeName.toLowerCase());
        if (!foundAttribute)
        {
          foundAttribute =
            fractionalConcernedAttributes.contains(attributeOid);
        }
        // Now remove the modification if:
        // - exclusive mode and the concerned attribute is in configuration
        // - inclusive mode and the concerned attribute is not in configuration
        if ( (foundAttribute && fractionalExclusive) ||
             (!foundAttribute && !fractionalExclusive) )
        {
          if (performFiltering)
          {
            // Found a modification to remove, remove it from the list.
            modsIt.remove();
            result = FRACTIONAL_HAS_FRACTIONAL_FILTERED_ATTRIBUTES;
            if (mods.size() == 0)
            {
              // This operation must become a no-op as no more modification in
              // it
              return FRACTIONAL_BECOME_NO_OP;
            }
          }
          else
          {
            // The call was just to check : at least one attribute to filter
            // found, return immediatly the answer;
            return FRACTIONAL_HAS_FRACTIONAL_FILTERED_ATTRIBUTES;
          }
        }
      }
    }
    return result;
  }
  /**
   * This is overwritten to allow stopping the (online) import process by the
   * fractional ldif import plugin when it detects that the (imported) remote
   * data set is not consistent with the local fractional configuration.
   * {@inheritDoc}
   */
  @Override
  protected byte[] receiveEntryBytes()
  {
    if (followImport)
    {
      // Ok, next entry is allowed to be received
      return super.receiveEntryBytes();
    } else
    {
      // Fractional ldif import plugin detected inconsistency between local
      // and remote server fractional configuration and is stopping the import
      // process:
      // This is an error termination during the import
      // The error is stored and the import is ended
      // by returning null
      Message msg = null;
      switch (importErrorMessageId)
      {
        case IMPORT_ERROR_MESSAGE_BAD_REMOTE:
          msg = NOTE_ERR_FULL_UPDATE_IMPORT_FRACTIONAL_BAD_REMOTE.get(
            baseDn.toString(), Short.toString(ieContext.getImportSource()));
          break;
        case IMPORT_ERROR_MESSAGE_REMOTE_IS_FRACTIONAL:
          msg = NOTE_ERR_FULL_UPDATE_IMPORT_FRACTIONAL_REMOTE_IS_FRACTIONAL.get(
            baseDn.toString(), Short.toString(ieContext.getImportSource()));
          break;
      }
      ieContext.setException(
        new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, msg));
      return null;
    }
  }
  /**
   * This is overwritten to allow stopping the (online) export process if the
   * local domain is fractional and the destination is all other servers:
   * This make no sense to have only fractional servers in a replicated
   * topology. This prevents from administrator manipulation error that would
   * lead to whole topology data corruption.
   * {@inheritDoc}
   */
  @Override
  protected void initializeRemote(short target, short requestorID,
    Task initTask) throws DirectoryException
  {
    if ((target == RoutableMsg.ALL_SERVERS) && fractional)
    {
      Message msg = NOTE_ERR_FRACTIONAL_FORBIDDEN_FULL_UPDATE_FRACTIONAL.get(
            baseDn.toString(), Short.toString(getServerId()));
      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, msg);
    } else
    {
      super.initializeRemote(target, requestorID, initTask);
    }
  }
  /**
   * Returns the base DN of this ReplicationDomain.
   *
   * @return The base DN of this ReplicationDomain
@@ -580,6 +2076,38 @@
          ResultCode.UNWILLING_TO_PERFORM, msg);
    }
    if (fractional)
    {
      if (addOperation.isSynchronizationOperation())
      {
        /*
         * Filter attributes here for fractional replication. If fractional
         * replication is enabled, we analyze the operation to suppress the
         * forbidden attributes in it so that they are not added in the local
         * backend. This must be called before any other plugin is called, to
         * keep coherency across plugin calls.
         */
        fractionalFilterOperation(addOperation, true);
      }
      else
      {
        /*
         * Direct access from an LDAP client : if some attributes are to be
         * removed according to the fractional configuration, simply forbid
         * the operation
         */
        if (fractionalFilterOperation(addOperation, false))
        {
          StringBuilder sb = new StringBuilder();
          addOperation.toString(sb);
          Message msg = NOTE_ERR_FRACTIONAL_FORBIDDEN_OPERATION.get(
            baseDn.toString(), sb.toString());
          return new SynchronizationProviderResult.StopProcessing(
            ResultCode.UNWILLING_TO_PERFORM, msg);
        }
      }
    }
    if (addOperation.isSynchronizationOperation())
    {
      AddContext ctx = (AddContext) addOperation.getAttachment(SYNCHROCONTEXT);
@@ -686,6 +2214,36 @@
          ResultCode.UNWILLING_TO_PERFORM, msg);
    }
    if (fractional)
    {
      if (modifyDNOperation.isSynchronizationOperation())
      {
        /*
         * Filter operation here for fractional replication. If fractional
         * replication is enabled, we analyze the operation and modify it if
         * necessary to stay consistent with what is defined in fractional
         * configuration.
         */
        fractionalFilterOperation(modifyDNOperation, true);
      }
      else
      {
        /*
         * Direct access from an LDAP client : something is inconsistent with
         * the fractional configuration, forbid the operation.
         */
        if (fractionalFilterOperation(modifyDNOperation, false))
        {
          StringBuilder sb = new StringBuilder();
          modifyDNOperation.toString(sb);
          Message msg = NOTE_ERR_FRACTIONAL_FORBIDDEN_OPERATION.get(
            baseDn.toString(), sb.toString());
          return new SynchronizationProviderResult.StopProcessing(
            ResultCode.UNWILLING_TO_PERFORM, msg);
        }
      }
    }
    ModifyDnContext ctx =
      (ModifyDnContext) modifyDNOperation.getAttachment(SYNCHROCONTEXT);
    if (ctx != null)
@@ -775,6 +2333,51 @@
          ResultCode.UNWILLING_TO_PERFORM, msg);
    }
    if (fractional)
    {
      if  (modifyOperation.isSynchronizationOperation())
      {
        /*
         * Filter attributes here for fractional replication. If fractional
         * replication is enabled, we analyze the operation and modify it so
         * that no forbidden attribute is added/modified/deleted in the local
         * backend. This must be called before any other plugin is called, to
         * keep coherency across plugin calls.
         */
        if (fractionalFilterOperation(modifyOperation, true) ==
          FRACTIONAL_BECOME_NO_OP)
        {
          // Every modifications filtered in this operation: the operation
          // becomes a no-op
          return new SynchronizationProviderResult.StopProcessing(
            ResultCode.SUCCESS, null);
        }
      }
      else
      {
        /*
         * Direct access from an LDAP client : if some attributes are to be
         * removed according to the fractional configuration, simply forbid
         * the operation
         */
        switch(fractionalFilterOperation(modifyOperation, false))
        {
          case FRACTIONAL_HAS_NO_FRACTIONAL_FILTERED_ATTRIBUTES:
            // Ok, let the operation happen
            break;
          case FRACTIONAL_HAS_FRACTIONAL_FILTERED_ATTRIBUTES:
            // Some attributes not compliant with fractional configuration :
            // forbid the operation
            StringBuilder sb = new StringBuilder();
            modifyOperation.toString(sb);
            Message msg = NOTE_ERR_FRACTIONAL_FORBIDDEN_OPERATION.get(
              baseDn.toString(), sb.toString());
            return new SynchronizationProviderResult.StopProcessing(
              ResultCode.UNWILLING_TO_PERFORM, msg);
        }
      }
    }
    ModifyContext ctx =
      (ModifyContext) modifyOperation.getAttachment(SYNCHROCONTEXT);
@@ -2045,12 +3648,6 @@
  }
  /**
   * The attribute name used to store the state in the backend.
   */
  protected static final String REPLICATION_GENERATION_ID =
    "ds-sync-generation-id";
  /**
   * Stores the value of the generationId.
   * @param generationId The value of the generationId.
   * @return a ResultCode indicating if the method was successful.
@@ -2136,7 +3733,7 @@
    /*
     * Search the database entry that is used to periodically
     * save the ServerState
     * save the generation id
     */
    InternalSearchOperation search = null;
    LinkedHashSet<String> attributes = new LinkedHashSet<String>(1);
@@ -2534,6 +4131,11 @@
        includeBranches.add(this.baseDn);
        importConfig.setIncludeBranches(includeBranches);
        importConfig.setAppendToExistingData(false);
        // Allow fractional replication ldif import plugin to be called
        importConfig.setInvokeImportPlugins(true);
        // Reset the follow import flag and message before starting the import
        importErrorMessageId = -1;
        followImport = true;
        // TODO How to deal with rejected entries during the import
        importConfig.writeRejectedEntries(
@@ -2736,6 +4338,17 @@
      unacceptableReasons.add(message);
      return false;
    }
    // Check fractional configuration
    try
    {
      isFractionalConfigAcceptable(configuration);
    } catch (ConfigException e)
    {
      unacceptableReasons.add(e.getMessageObject());
      return false;
    }
    return true;
  }
@@ -2756,6 +4369,9 @@
    // Read assured configuration and reconnect if needed
    readAssuredConfig(configuration, true);
    // Read fractional configuration and reconnect if needed
    readFractionalConfig(configuration, true);
    return new ConfigChangeResult(ResultCode.SUCCESS, false);
  }
@@ -2765,14 +4381,25 @@
  public boolean isConfigurationChangeAcceptable(
         ReplicationDomainCfg configuration, List<Message> unacceptableReasons)
  {
    // Check that a import/export is not in progress
    if (this.importInProgress() || this.exportInProgress())
    {
      unacceptableReasons.add(
          NOTE_ERR_CANNOT_CHANGE_CONFIG_DURING_TOTAL_UPDATE.get());
      return false;
    }
    else
      return true;
    // Check fractional configuration
    try
    {
      isFractionalConfigAcceptable(configuration);
    } catch (ConfigException e)
    {
      unacceptableReasons.add(e.getMessageObject());
      return false;
    }
    return true;
  }
  /**
@@ -2827,8 +4454,25 @@
      long generationID,
      ProtocolSession session)
  {
    // Check domain fractional configuration consistency with local
    // configuration variables
    force_bad_data_set = !isBackendFractionalConfigConsistent();
    super.sessionInitiated(
        initStatus, replicationServerState,generationID, session);
    // Now for bad data set status if needed
    if (force_bad_data_set)
    {
      // Go into bad data set status
      setNewStatus(StatusMachineEvent.TO_BAD_GEN_ID_STATUS_EVENT);
      broker.signalStatusChange(status);
      Message message = NOTE_FRACTIONAL_BAD_DATA_SET_NEED_RESYNC.get(
        baseDn.toString());
      logError(message);
      return; // Do not send changes to the replication server
    }
    try
    {
      /*
@@ -3036,6 +4680,13 @@
  @Override
  public boolean processUpdate(UpdateMsg updateMsg)
  {
    // Ignore message if fractional configuration is inconcsistent and
    // we have been passed into bad data set status
    if (force_bad_data_set)
    {
      return false;
    }
    if (updateMsg instanceof LDAPUpdateMsg)
    {
      LDAPUpdateMsg msg = (LDAPUpdateMsg) updateMsg;
opends/src/server/org/opends/server/replication/protocol/MonitorMsg.java
@@ -78,7 +78,7 @@
      new HashMap<Short, ServerData>();
  }
  SubTopoMonitorData data = new SubTopoMonitorData();;
  SubTopoMonitorData data = new SubTopoMonitorData();
  /**
   * Creates a new EntryMessage.
opends/src/server/org/opends/server/replication/service/ReplicationDomain.java
@@ -151,7 +151,7 @@
  /**
   * Current status for this replicated domain.
   */
  private ServerStatus status = ServerStatus.NOT_CONNECTED_STATUS;
  protected ServerStatus status = ServerStatus.NOT_CONNECTED_STATUS;
  /**
   * The tracer object for the debug logger.
@@ -176,7 +176,7 @@
   * The ReplicationBroker that is used by this ReplicationDomain to
   * connect to the ReplicationService.
   */
  private ReplicationBroker broker = null;
  protected ReplicationBroker broker = null;
  /**
   * This Map is used to store all outgoing assured messages in order
@@ -191,7 +191,7 @@
   * The context related to an import or export being processed
   * Null when none is being processed.
   */
  private IEContext ieContext = null;
  protected IEContext ieContext = null;
  /**
   * The Thread waiting for incoming update messages for this domain and pushing
@@ -788,6 +788,7 @@
            mb.append(de.getMessageObject());
            TRACER.debugInfo(Message.toString(mb.toMessage()));
            broker.publish(errorMsg);
            logError(de.getMessageObject());
          }
        }
        else if (msg instanceof ErrorMsg)
@@ -1076,11 +1077,11 @@
   * This class contain the context related to an import or export
   * launched on the domain.
   */
  private class IEContext
  protected class IEContext
  {
    // The task that initiated the operation.
    // Theprivate task that initiated the operation.
    Task initializeTask;
    // The target in the case of an export
    // The destination in the case of an export
    short exportTarget = RoutableMsg.UNKNOWN_SERVER;
    // The source in the case of an import
    short importSource = RoutableMsg.UNKNOWN_SERVER;
@@ -1111,9 +1112,9 @@
    /**
     * Initializes the import/export counters with the provider value.
     * @param total
     * @param left
     * @throws DirectoryException
     * @param total Total number of entries to be processed.
     * @param left Remaining number of entries to be processed.
     * @throws DirectoryException if an error occurred.
     */
    public void setCounters(long total, long left)
      throws DirectoryException
@@ -1139,7 +1140,7 @@
    /**
     * Update the counters of the task for each entry processed during
     * an import or export.
     * @throws DirectoryException
     * @throws DirectoryException if an error occurred.
     */
    public void updateCounters()
      throws DirectoryException
@@ -1166,7 +1167,7 @@
     * @param  entriesDone The number of entries that were processed
     *                     since the last time this method was called.
     *
     * @throws DirectoryException
     * @throws DirectoryException if an error occurred.
     */
    public void updateCounters(int entriesDone)
      throws DirectoryException
@@ -1195,6 +1196,42 @@
      return new String("[ Entry count=" + this.entryCount +
                        ", Entry left count=" + this.entryLeftCount + "]");
    }
    /**
     * Gets the server id of the exporting server.
     * @return the server id of the exporting server.
     */
    public short getExportTarget()
    {
      return exportTarget;
    }
    /**
     * Gets the server id of the importing server.
     * @return the server id of the importing server.
     */
    public short getImportSource()
    {
      return importSource;
    }
    /**
     * Get the exception that occurred during the import/export.
     * @return the exception that occurred during the import/export.
     */
    public DirectoryException getException()
    {
      return exception;
    }
    /**
     * Set the exception that occurred during the import/export.
     * @param exception the exception that occurred during the import/export.
     */
    public void setException(DirectoryException exception)
    {
      this.exception = exception;
    }
  }
  /**
   * Verifies that the given string represents a valid source
@@ -1304,8 +1341,8 @@
   *
   * @exception DirectoryException When an error occurs.
   */
  void initializeRemote(short target, short requestorID, Task initTask)
  throws DirectoryException
  protected void initializeRemote(short target, short requestorID,
    Task initTask) throws DirectoryException
  {
    Message msg = NOTE_FULL_UPDATE_ENGAGED_FOR_REMOTE_START.get(
      Short.toString(serverID),
@@ -1417,14 +1454,14 @@
    if (ieContext != null)
    {
      ieContext.exception = new DirectoryException(ResultCode.OTHER,
          errorMsg.getDetails());
      ieContext.setException(new DirectoryException(ResultCode.OTHER,
        errorMsg.getDetails()));
      if (ieContext.initializeTask instanceof InitializeTask)
      {
        // Update the task that initiated the import
        ((InitializeTask)ieContext.initializeTask).
        updateTaskCompletionState(ieContext.exception);
        updateTaskCompletionState(ieContext.getException());
        releaseIEContext();
      }
@@ -1437,7 +1474,7 @@
   *
   * @return The bytes. Null when the Done or Err message has been received
   */
  byte[] receiveEntryBytes()
  protected byte[] receiveEntryBytes()
  {
    ReplicationMsg msg;
    while (true)
@@ -1477,9 +1514,8 @@
          // The error is stored and the import is ended
          // by returning null
          ErrorMsg errorMsg = (ErrorMsg)msg;
          ieContext.exception = new DirectoryException(
                                      ResultCode.OTHER,
                                      errorMsg.getDetails());
          ieContext.setException(new DirectoryException(ResultCode.OTHER,
            errorMsg.getDetails()));
          return null;
        }
        else
@@ -1490,9 +1526,9 @@
      catch(Exception e)
      {
        // TODO: i18n
        ieContext.exception = new DirectoryException(ResultCode.OTHER,
            Message.raw("received an unexpected message type" +
                e.getLocalizedMessage()));
        ieContext.setException(new DirectoryException(ResultCode.OTHER,
          Message.raw("received an unexpected message type" +
          e.getLocalizedMessage())));
      }
    }
  }
@@ -1547,15 +1583,15 @@
  {
    // If an error was raised - like receiving an ErrorMsg
    // we just let down the export.
    if (ieContext.exception != null)
    if (ieContext.getException() != null)
    {
      IOException ioe = new IOException(ieContext.exception.getMessage());
      IOException ioe = new IOException(ieContext.getException().getMessage());
      ieContext = null;
      throw ioe;
    }
    EntryMsg entryMessage = new EntryMsg(
        serverID, ieContext.exportTarget, lDIFEntry, pos, length);
        serverID,ieContext.getExportTarget(), lDIFEntry, pos, length);
    broker.publish(entryMessage);
    try
@@ -1702,8 +1738,8 @@
    }
    finally
    {
      if ((ieContext != null)  && (ieContext.exception != null))
        de = ieContext.exception;
      if ((ieContext != null)  && (ieContext.getException() != null))
        de = ieContext.getException();
      // Update the task that initiated the import
      if ((ieContext != null ) && (ieContext.initializeTask != null))
@@ -1732,7 +1768,7 @@
   * event.
   * @param event The event that may make the status be changed
   */
  private void setNewStatus(StatusMachineEvent event)
  protected void setNewStatus(StatusMachineEvent event)
  {
    ServerStatus newStatus =
      StatusMachine.computeNewStatus(status, event);
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java
@@ -1202,4 +1202,34 @@
      throw new Exception("Entry: " + dn + " Could not be found.");
    return found;
  }
  /**
   * Utility method : removes a domain deleting the passed config entry
   */
  protected void removeDomain(Entry domainCfgEntry)
  {
    DeleteOperationBasis op;
    // Delete entries
    try
    {
      DN dn = domainCfgEntry.getDN();
      logError(Message.raw(Category.SYNC, Severity.NOTICE,
        "cleaning config entry " + dn));
      op = new DeleteOperationBasis(connection, InternalClientConnection.
        nextOperationID(), InternalClientConnection.nextMessageID(), null,
        dn);
      op.run();
      if ((op.getResultCode() != ResultCode.SUCCESS) &&
        (op.getResultCode() != ResultCode.NO_SUCH_OBJECT))
      {
        fail("Deleting config entry" + dn +
          " failed: " + op.getResultCode().getResultCodeName());
      }
    } catch (NoSuchElementException e)
    {
      // done
    }
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/AssuredReplicationPluginTest.java
@@ -266,36 +266,6 @@
  }
  /**
   * Removes a domain deleting the passed config entry
   */
  private void removeDomain(Entry domainCfgEntry)
  {
    DeleteOperationBasis op;
    // Delete entries
    try
    {
      DN dn = domainCfgEntry.getDN();
      logError(Message.raw(Category.SYNC, Severity.NOTICE,
        "cleaning config entry " + dn));
      op = new DeleteOperationBasis(connection, InternalClientConnection.
        nextOperationID(), InternalClientConnection.nextMessageID(), null,
        dn);
      op.run();
      if ((op.getResultCode() != ResultCode.SUCCESS) &&
        (op.getResultCode() != ResultCode.NO_SUCH_OBJECT))
      {
        fail("Deleting config entry" + dn +
          " failed: " + op.getResultCode().getResultCodeName());
      }
    } catch (NoSuchElementException e)
    {
      // done
    }
  }
  /**
   * The fake replication server used to emulate RS behaviour the way we want
   * for assured features test.
   * This fake replication server is able to receive a DS connection only.
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/DomainFakeCfg.java
@@ -26,6 +26,7 @@
 */
package org.opends.server.replication.plugin;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -63,6 +64,9 @@
  // Referrals urls to be published to other servers of the topology
  SortedSet<String> refUrls = new TreeSet<String>();
  private SortedSet<String> fractionalExcludes = new TreeSet<String>();
  private SortedSet<String> fractionalIncludes = new TreeSet<String>();
  /**
   * Creates a new Domain with the provided information
   * (assured mode disabled, default group id)
@@ -76,6 +80,30 @@
  /**
   * Creates a new Domain with the provided information
   * (with some fractional configuration provided)
   */
  public DomainFakeCfg(DN baseDn, int serverId, SortedSet<String> replServers,
    List<String> fractionalExcludes, List<String> fractionalIncludes)
  {
    this(baseDn, serverId, replServers);
    if (fractionalExcludes != null)
    {
      for (String str : fractionalExcludes)
      {
        this.fractionalExcludes.add(str);
      }
    }
    if (fractionalIncludes != null)
    {
      for (String str : fractionalIncludes)
      {
        this.fractionalIncludes.add(str);
      }
    }
  }
  /**
   * Creates a new Domain with the provided information
   * (assured mode disabled, group id provided)
   */
  public DomainFakeCfg(DN baseDn, int serverId, SortedSet<String> replServers,
@@ -305,4 +333,14 @@
  {
    return refUrls;
  }
  public SortedSet<String> getFractionalExclude()
  {
    return fractionalExcludes;
  }
  public SortedSet<String> getFractionalInclude()
  {
    return fractionalIncludes;
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/FractionalReplicationTest.java
New file
@@ -0,0 +1,2031 @@
/*
 * 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
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 Sun Microsystems, Inc.
 */
package org.opends.server.replication.plugin;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.opends.messages.Category;
import org.opends.messages.Message;
import org.opends.messages.Severity;
import org.opends.server.TestCaseUtils;
import org.opends.server.config.ConfigException;
import org.opends.server.core.AddOperationBasis;
import org.opends.server.core.DirectoryServer;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.replication.ReplicationTestCase;
import org.opends.server.replication.common.ChangeNumberGenerator;
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.protocol.UpdateMsg;
import org.opends.server.replication.server.ReplServerFakeConfiguration;
import org.opends.server.replication.server.ReplicationServer;
import org.opends.server.replication.service.ReplicationDomain;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.Attributes;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.Modification;
import org.opends.server.types.ModificationType;
import org.opends.server.types.ObjectClass;
import org.opends.server.types.ResultCode;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
import static org.opends.server.TestCaseUtils.*;
import static org.opends.server.loggers.ErrorLogger.logError;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
import static org.opends.messages.ReplicationMessages.*;
/**
 * Various tests around fractional replication
 */
public class FractionalReplicationTest extends ReplicationTestCase {
  // The RS
  private ReplicationServer replicationServer = null;
  // RS port
  private int replServerPort = -1;
  // Represents the real domain to test (replays and filters)
  private Entry fractionalDomainCfgEntry = null;
  // The domain used to send updates to the reald domain
  private FakeReplicationDomain replicationDomain = null;
  // Ids of servers
  private static final short DS1_ID = 1; // fractional domain
  private static final short DS2_ID = 2; // fake domain
  private static final short RS_ID = 91; // replication server
  private final String testName = this.getClass().getSimpleName();
  // Fractional mode
  private static final int EXCLUDE_FRAC_MODE = 0;
  private static final int INCLUDE_FRAC_MODE = 1;
  private ChangeNumberGenerator gen = null;
  // The tracer object for the debug logger
  private static final DebugTracer TRACER = getTracer();
  // Number of seconds before generating an error if some conditions not met
  private static final int TIMEOUT = 10;
  // 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 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 String ENTRY_DN2 = "uid=1," + TEST2_ORG_DN_STRING;
  private void debugInfo(String s) {
    logError(Message.raw(Category.SYNC, Severity.NOTICE, s));
    if (debugEnabled())
    {
      TRACER.debugInfo("** TEST **" + s);
    }
  }
  /**
   * Before starting the tests configure some stuff
   */
  @BeforeClass
  @Override
  public void setUp() throws Exception
  {
    super.setUp();
    // Find  a free port for the replicationServer
    ServerSocket socket = TestCaseUtils.bindFreePort();
    replServerPort = socket.getLocalPort();
    socket.close();
  }
  /**
   * Returns a bunch of single values for fractional-exclude configuration
   * attribute
   */
  @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
   */
  @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
    {
      // create replication server
      createReplicationServer(testcase);
      // create fractional domain with the passed fractional configuration
      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 = null;
      try
      {
        newEntry = getEntry(DN.decode(ENTRY_DN), TIMEOUT, true);
      } catch(Exception e)
      {
        fail("Entry has not been added: " + e.getMessage());
      }
      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 = null;
      boolean synchroAttrFound = false;
      int timeout = TIMEOUT;
      while(timeout>0)
      {
        try
        {
          entry = getEntry(DN.decode(ENTRY_DN), TIMEOUT, true);
          if (entry.hasAttribute(DirectoryServer.getAttributeType(SYNCHRO_OPTIONAL_ATTR.toLowerCase())))
          {
            synchroAttrFound = true;
            break;
          }
          Thread.sleep(1000);
          timeout--;
        } catch (Exception e)
        {
          fail("Error waiting for modify operation being replayed : " + e.getMessage());
        }
      }
      assertTrue(synchroAttrFound, "Modify operation not replayed");
      checkEntryFilteredAfterModify(entry, EXCLUDE_FRAC_MODE, fractionalConf);
    }
    finally
    {
      endTest();
    }
  }
  /**
   * Returns a bunch of single values for fractional-include configuration
   * attribute
   */
  @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
   */
  @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
    {
      // create replication server
      createReplicationServer(testcase);
      // create fractional domain with the passed fractional configuration
      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 = null;
      try
      {
        newEntry = getEntry(DN.decode(ENTRY_DN), TIMEOUT, true);
      } catch(Exception e)
      {
        fail("Entry has not been added: " + e.getMessage());
      }
      checkEntryFilteredAfterAdd(newEntry, INCLUDE_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 = null;
      boolean synchroAttrFound = false;
      int timeout = TIMEOUT;
      while(timeout>0)
      {
        try
        {
          entry = getEntry(DN.decode(ENTRY_DN), TIMEOUT, true);
          if (entry.hasAttribute(DirectoryServer.getAttributeType(SYNCHRO_OPTIONAL_ATTR.toLowerCase())))
          {
            synchroAttrFound = true;
            break;
          }
          Thread.sleep(1000);
          timeout--;
        } catch (Exception e)
        {
          fail("Error waiting for modify operation being replayed : " + e.getMessage());
        }
      }
      assertTrue(synchroAttrFound, "Modify operation not replayed");
      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)
  {
    try{
      List<String> replicationServers = new ArrayList<String>();
      replicationServers.add("localhost:" + replServerPort);
      replicationDomain = new FakeReplicationDomain(
            (firstBackend ? TEST_ROOT_DN_STRING : TEST2_ROOT_DN_STRING), DS2_ID, replicationServers, 100, 1000, generationId);
      // Test connection
      assertTrue(replicationDomain.isConnected());
      int rdPort = -1;
      // Check connected server port
      String serverStr = replicationDomain.getReplicationServer();
      int index = serverStr.lastIndexOf(':');
      if ((index == -1) || (index >= serverStr.length()))
        fail("Enable to find port number in: " + serverStr);
      String rdPortStr = serverStr.substring(index + 1);
      try
      {
        rdPort = (new Integer(rdPortStr)).intValue();
      } catch (Exception e)
      {
        fail("Enable to get an int from: " + rdPortStr);
      }
      assertEquals(rdPort, replServerPort);
    } catch (Exception e)
    {
      fail("createreplicationDomain " + e.getMessage());
    }
  }
  private void initTest()
  {
    replicationDomain = null;
    fractionalDomainCfgEntry = null;
    replicationServer = null;
    // Initialize the test backend
    try {
      TestCaseUtils.initializeTestBackend(false);
    } catch(Exception e) {
      fail("Could not initialize backend : " + e.getMessage());
    }
    // initialize cn generator
    gen = new ChangeNumberGenerator(DS2_ID, 0L);
  }
  private void endTest()
  {
    if (replicationDomain != null)
    {
      replicationDomain.disableService();
      replicationDomain = null;
    }
    if (fractionalDomainCfgEntry != null)
    {
      removeDomain(fractionalDomainCfgEntry);
      fractionalDomainCfgEntry = null;
    }
    if (replicationServer != null)
    {
      replicationServer.clearDb();
      replicationServer.remove();
      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)
  {
    try
    {
      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.getDN()),
        "Unable to add the domain config entry: " + configEntryLdif);
    }
    catch(Exception e)
    {
      fail("createFractionalDomain error: " + e.getMessage());
    }
  }
  /**
   * Creates a new ReplicationServer.
   */
  private void createReplicationServer(String testCase)
  {
    try
    {
      SortedSet<String> replServers = new TreeSet<String>();
      String dir = testName + RS_ID + testCase + "Db";
      ReplServerFakeConfiguration conf =
        new ReplServerFakeConfiguration(replServerPort, dir, 0, RS_ID, 0, 100,
        replServers);
      replicationServer = new ReplicationServer(conf);
    } catch (Exception e)
    {
      fail("createReplicationServer " + e.getMessage());
    }
  }
  /**
   * This class is the minimum implementation of a Concrete ReplicationDomain
   * used to be able to connect to the RS with a known genid. Also to be able
   * to send updates
   */
  private class FakeReplicationDomain extends ReplicationDomain
  {
    // A blocking queue that is used to receive updates from
    // the Replication Service.
    BlockingQueue<UpdateMsg> queue = new LinkedBlockingQueue<UpdateMsg>();
    // A string that will be exported should exportBackend be called.
    String exportString = null;
    // A StringBuilder that will be used to build a new String should the
    // import be called.
    StringBuilder importString = null;
    private int exportedEntryCount;
    private long generationID = -1;
    public FakeReplicationDomain(
      String serviceID,
      short serverID,
      Collection<String> replicationServers,
      int window,
      long heartbeatInterval,
      long generationId) throws ConfigException
    {
      super(serviceID, serverID);
      generationID = generationId;
      startPublishService(replicationServers, window, heartbeatInterval);
      startListenService();
    }
    public void initExport(String exportString, int exportedEntryCount)
    {
      this.exportString = exportString;
      this.exportedEntryCount = exportedEntryCount;
    }
    public void initImport(StringBuilder importString)
    {
      this.importString = importString;
    }
    @Override
    public long countEntries() throws DirectoryException
    {
      return exportedEntryCount;
    }
    @Override
    protected void exportBackend(OutputStream output) throws DirectoryException
    {
      try
      {
        output.write(exportString.getBytes());
        output.flush();
        output.close();
      } catch (IOException e)
      {
        throw new DirectoryException(ResultCode.OPERATIONS_ERROR,
          ERR_BACKEND_EXPORT_ENTRY.get("", ""));
      }
    }
    @Override
    public long getGenerationID()
    {
      return generationID;
    }
    @Override
    protected void importBackend(InputStream input) throws DirectoryException
    {
      byte[] buffer = new byte[1000];
      int ret;
      do
      {
        try
        {
          ret = input.read(buffer, 0, 1000);
        } catch (IOException e)
        {
          throw new DirectoryException(
            ResultCode.OPERATIONS_ERROR,
            ERR_BACKEND_EXPORT_ENTRY.get("", ""));
        }
        importString.append(new String(buffer, 0, ret));
      } while (ret >= 0);
    }
    @Override
    public boolean processUpdate(UpdateMsg updateMsg)
    {
      if (queue != null)
        queue.add(updateMsg);
      return true;
    }
    public void setGenerationID(long newGenerationID)
    {
      generationID = newGenerationID;
    }
  }
  private static final String REPLICATION_GENERATION_ID =
    "ds-sync-generation-id";
  private long readGenIdFromSuffixRootEntry(String rootDn)
  {
    long genId=-1;
    try
    {
      DN baseDn = DN.decode(rootDn);
      Entry resultEntry = getEntry(baseDn, 1000, true);
      if (resultEntry==null)
      {
        debugInfo("Entry not found <" + rootDn + ">");
      }
      else
      {
        debugInfo("Entry found <" + rootDn + ">");
        AttributeType synchronizationGenIDType =
          DirectoryServer.getAttributeType(REPLICATION_GENERATION_ID);
        List<Attribute> attrs =
          resultEntry.getAttribute(synchronizationGenIDType);
        if (attrs != null)
        {
          Attribute attr = attrs.get(0);
          if (attr.size() == 1)
          {
            genId =
                Long.decode(attr.iterator().next().getValue().toString());
          }
        }
      }
    }
    catch(Exception e)
    {
      fail("Exception raised in readGenId", e);
    }
    return genId;
  }
  /**
   * 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)
    {
      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 = null;
      try
      {
        entry = TestCaseUtils.entryFromLdifString(entryLdif);
      } catch (Exception e)
      {
        fail(e.getMessage());
      }
      // Create an update message to add an entry.
      AddMsg addMsg = new AddMsg(gen.newChangeNumber(),
        entry.getDN().toString(),
        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)
    {
      // Create modifications on the fractional attributes
      List<Modification> mods = new ArrayList<Modification>();
      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 = null;
      try
      {
        entryDn = DN.decode((firstBackend ? ENTRY_DN : ENTRY_DN2));
      } catch (Exception e)
      {
        fail("Cannot create dn entry: " + e.getMessage());
      }
      ModifyMsg modifyMsg = new ModifyMsg(gen.newChangeNumber(), entryDn, mods,
        ENTRY_UUID);
      replicationDomain.publish(modifyMsg);
    }
  /**
   * Utility method : Add an entry in the database
   */
  private void addEntry(Entry entry) throws Exception
  {
    AddOperationBasis addOp = new AddOperationBasis(connection,
      InternalClientConnection.nextOperationID(), InternalClientConnection.
      nextMessageID(), null, entry.getDN(), entry.getObjectClasses(),
      entry.getUserAttributes(), entry.getOperationalAttributes());
    addOp.setInternalOperation(true);
    addOp.run();
    assertNotNull(getEntry(entry.getDN(), 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
  {
    try
    {
      // 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.
                getAttributeType(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.
                getAttributeType(OPTIONAL_ATTR.toLowerCase())));
          break;
        default:
          fail("Unexpected fractional mode.");
      }
    }
    catch(Exception e)
    {
      fail("checkEntryFilteredAfterAdd error: " + e.getMessage());
    }
  }
  /**
   * 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
  {
    try
    {
      // 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.
                getAttributeType(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.
                getAttributeType(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");
    }
    catch(Exception e)
    {
      fail("checkEntryFilteredAfterAdd error: " + e.getMessage());
    }
  }
  /**
   * 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<AttributeValue> attrValues = attr.iterator();
    assertNotNull(attrValues);
    assertTrue(attrValues.hasNext());
    AttributeValue attrValue = attrValues.next();
    assertNotNull(attrValue);
    assertFalse(attrValues.hasNext());
    assertEquals(attrValue.toString(), attributeValue, "Was expecting attribute " +
      attributeName + "=" + attributeValue + " but got value: " + attrValue.toString());
  }
  /**
   * Returns a bunch of single values for fractional configuration
   * attributes
   */
  @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
   */
  @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.
    // Clear the backend
   LDAPReplicationDomain.clearJEBackend(false, "userRoot", TEST2_ROOT_DN_STRING);
    try
    {
      /*
       * Create replication server and connect fractional domain to it then fake
       * domain
       */
      // create replication server
      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.decode(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);
      /*
       * 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, 5);
      // check that entry has been created and that it does not contain
      // forbidden attributes
      Entry newEntry = null;
      try
      {
        newEntry = getEntry(DN.decode(ENTRY_DN2), TIMEOUT, true);
      } catch(Exception e)
      {
        fail("Entry has not been created: " + e.getMessage());
      }
      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 = null;
      boolean synchroAttrFound = false;
      int timeout = TIMEOUT;
      while(timeout>0)
      {
        try
        {
          entry = getEntry(DN.decode(ENTRY_DN2), TIMEOUT, true);
          if (entry.hasAttribute(DirectoryServer.getAttributeType(SYNCHRO_OPTIONAL_ATTR.toLowerCase())))
          {
            synchroAttrFound = true;
            break;
          }
          Thread.sleep(1000);
          timeout--;
        } catch (Exception e)
        {
          fail("Error waiting for modify operation being replayed : " + e.getMessage());
        }
      }
      assertTrue(synchroAttrFound, "Modify operation not replayed");
      checkEntryFilteredAfterModify(entry, EXCLUDE_FRAC_MODE, fractionalConf);
    }
    finally
    {
      endTest();
    }
  }
  /**
   * 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;
      }
      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
   */
  @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
   */
  @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.
    // Clear the backend
    LDAPReplicationDomain.clearJEBackend(false, "userRoot", TEST2_ROOT_DN_STRING);
    try
    {
      /*
       * Create replication server and connect fractional domain to it then fake
       * domain
       */
      // create replication server
      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.decode(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);
      /*
       * Chack 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, 5);
      // check that entry has been created and that it does not contain
      // forbidden attributes
      Entry newEntry = null;
      try
      {
        newEntry = getEntry(DN.decode(ENTRY_DN2), TIMEOUT, true);
      } catch(Exception e)
      {
        fail("Entry has not been created: " + e.getMessage());
      }
      checkEntryFilteredAfterAdd(newEntry, INCLUDE_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 = null;
      boolean synchroAttrFound = false;
      int timeout = TIMEOUT;
      while(timeout>0)
      {
        try
        {
          entry = getEntry(DN.decode(ENTRY_DN2), TIMEOUT, true);
          if (entry.hasAttribute(DirectoryServer.getAttributeType(SYNCHRO_OPTIONAL_ATTR.toLowerCase())))
          {
            synchroAttrFound = true;
            break;
          }
          Thread.sleep(1000);
          timeout--;
        } catch (Exception e)
        {
          fail("Error waiting for modify operation being replayed : " + e.getMessage());
        }
      }
      assertTrue(synchroAttrFound, "Modify operation not replayed");
      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()
  {
     String testcase = "testAddWithForbiddenAttrInRDNExclude";
    initTest();
    try
    {
      // create replication server
      createReplicationServer(testcase);
      // create fractional domain with the passed fractional configuration
      createFractionalDomain(true, EXCLUDE_FRAC_MODE,
        new String[] {"inetOrgPerson", "displayName", "description"});
      // create fake domain to send operations
      createFakeReplicationDomain(true, readGenIdFromSuffixRootEntry(TEST_ROOT_DN_STRING));
      /*
       * Perform add operation with fornbidden 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 = null;
      try
      {
        entry = TestCaseUtils.entryFromLdifString(entryLdif);
      } catch (Exception e)
      {
        fail(e.getMessage());
      }
      // Create an update message to add an entry.
      AddMsg addMsg = new AddMsg(gen.newChangeNumber(),
        entry.getDN().toString(),
        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 = null;
      try
      {
        newEntry = getEntry(entry.getDN(), TIMEOUT, true);
      } catch(Exception e)
      {
        fail("Entry has not been added: " + e.getMessage());
      }
      assertNotNull(newEntry);
      assertEquals(entry.getDN(), newEntry.getDN());
      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 fornbidden 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";
      try
      {
        entry = TestCaseUtils.entryFromLdifString(entryLdif);
      } catch (Exception e)
      {
        fail(e.getMessage());
      }
      // Create an update message to add an entry.
      addMsg = new AddMsg(gen.newChangeNumber(),
        entry.getDN().toString(),
        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
       */
      try
      {
        newEntry = getEntry(entry.getDN(), TIMEOUT, true);
      } catch(Exception e)
      {
        fail("Entry has not been added: " + e.getMessage());
      }
      assertNotNull(newEntry);
      assertEquals(entry.getDN(), newEntry.getDN());
      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()
  {
     String testcase = "testAddWithForbiddenAttrInRDNInclude";
    initTest();
    try
    {
      // create replication server
      createReplicationServer(testcase);
      // create fractional domain with the passed fractional configuration
      createFractionalDomain(true, INCLUDE_FRAC_MODE,
        new String[] {"inetOrgPerson", "carLicense"});
      // create fake domain to send operations
      createFakeReplicationDomain(true, readGenIdFromSuffixRootEntry(TEST_ROOT_DN_STRING));
      /*
       * Perform add operation with fornbidden 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 = null;
      try
      {
        entry = TestCaseUtils.entryFromLdifString(entryLdif);
      } catch (Exception e)
      {
        fail(e.getMessage());
      }
      // Create an update message to add an entry.
      AddMsg addMsg = new AddMsg(gen.newChangeNumber(),
        entry.getDN().toString(),
        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 = null;
      try
      {
        newEntry = getEntry(entry.getDN(), TIMEOUT, true);
      } catch(Exception e)
      {
        fail("Entry has not been added: " + e.getMessage());
      }
      assertNotNull(newEntry);
      assertEquals(entry.getDN(), newEntry.getDN());
      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 fornbidden 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";
      try
      {
        entry = TestCaseUtils.entryFromLdifString(entryLdif);
      } catch (Exception e)
      {
        fail(e.getMessage());
      }
      // Create an update message to add an entry.
      addMsg = new AddMsg(gen.newChangeNumber(),
        entry.getDN().toString(),
        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
       */
      try
      {
        newEntry = getEntry(entry.getDN(), TIMEOUT, true);
      } catch(Exception e)
      {
        fail("Entry has not been added: " + e.getMessage());
      }
      assertNotNull(newEntry);
      assertEquals(entry.getDN(), newEntry.getDN());
      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()
  {
     String testcase = "testModifyDnWithForbiddenAttrInRDNExclude";
    initTest();
    try
    {
      // create replication server
      createReplicationServer(testcase);
      // create fractional domain with the passed fractional configuration
      createFractionalDomain(true, EXCLUDE_FRAC_MODE,
        new String[] {"inetOrgPerson", "displayName", "description"});
      // create fake domain to send operations
      createFakeReplicationDomain(true, readGenIdFromSuffixRootEntry(TEST_ROOT_DN_STRING));
      /*
       * Perform add operation with fornbidden 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 = null;
      try
      {
        entry = TestCaseUtils.entryFromLdifString(entryLdif);
      } catch (Exception e)
      {
        fail(e.getMessage());
      }
      // Create an update message to add an entry.
      AddMsg addMsg = new AddMsg(gen.newChangeNumber(),
        entry.getDN().toString(),
        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 = null;
      try
      {
        newEntry = getEntry(entry.getDN(), TIMEOUT, true);
      } catch(Exception e)
      {
        fail("Entry has not been added: " + e.getMessage());
      }
      assertNotNull(newEntry);
      assertEquals(entry.getDN(), newEntry.getDN());
      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 = null;
      try
      {
        newEntryDn = DN.decode(newEntryName);
      } catch(DirectoryException e)
      {
        fail("Could not get DN from string: " + newEntryName);
      }
      // Create modify dn message to modify the entry.
      ModifyDNMsg modDnMsg = new ModifyDNMsg(entryName, gen.newChangeNumber(),
        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
       */
      try
      {
        newEntry = getEntry(newEntryDn, TIMEOUT, true);
      } catch(Exception e)
      {
        fail("Entry has not been added: " + e.getMessage());
      }
      assertNotNull(newEntry);
      assertEquals(newEntryDn, newEntry.getDN());
      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()
  {
     String testcase = "testModifyDnWithForbiddenAttrInRDNInclude";
    initTest();
    try
    {
      // create replication server
      createReplicationServer(testcase);
      // create fractional domain with the passed fractional configuration
      createFractionalDomain(true, INCLUDE_FRAC_MODE,
        new String[] {"inetOrgPerson", "carLicense"});
      // create fake domain to send operations
      createFakeReplicationDomain(true, readGenIdFromSuffixRootEntry(TEST_ROOT_DN_STRING));
      /*
       * Perform add operation with fornbidden 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 = null;
      try
      {
        entry = TestCaseUtils.entryFromLdifString(entryLdif);
      } catch (Exception e)
      {
        fail(e.getMessage());
      }
      // Create an update message to add an entry.
      AddMsg addMsg = new AddMsg(gen.newChangeNumber(),
        entry.getDN().toString(),
        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 = null;
      try
      {
        newEntry = getEntry(entry.getDN(), TIMEOUT, true);
      } catch(Exception e)
      {
        fail("Entry has not been added: " + e.getMessage());
      }
      assertNotNull(newEntry);
      assertEquals(entry.getDN(), newEntry.getDN());
      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 = null;
      try
      {
        newEntryDn = DN.decode(newEntryName);
      } catch(DirectoryException e)
      {
        fail("Could not get DN from string: " + newEntryName);
      }
      // Create modify dn message to modify the entry.
      ModifyDNMsg modDnMsg = new ModifyDNMsg(entryName, gen.newChangeNumber(),
        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
       */
      try
      {
        newEntry = getEntry(newEntryDn, TIMEOUT, true);
      } catch(Exception e)
      {
        fail("Entry has not been added: " + e.getMessage());
      }
      assertNotNull(newEntry);
      assertEquals(newEntryDn, newEntry.getDN());
      objectClass = DirectoryServer.getObjectClass("inetOrgPerson".toLowerCase());
      assertTrue(newEntry.hasObjectClass(objectClass));
      checkEntryAttributeValue(newEntry, "displayName", "ValueToBeKept");
      assertNull(newEntry.getAttribute("description"));
    }
    finally
    {
      endTest();
    }
  }
}