From e8911375edbab16632ebbdd58e642e49aadb2b36 Mon Sep 17 00:00:00 2001
From: mrossign <mrossign@localhost>
Date: Tue, 07 Jul 2009 09:15:52 +0000
Subject: [PATCH] Fractional replication Info about the feature: https://www.opends.org/wiki/page/FractionalReplication
---
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java | 30
opendj-sdk/opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java | 1671 ++++++++++++++++++++++
opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/ReplicationDomainConfiguration.xml | 95 +
opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/FractionalLDIFImportPluginConfiguration.xml | 50
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/DomainFakeCfg.java | 38
opendj-sdk/opends/src/server/org/opends/server/replication/common/ChangeNumber.java | 2
opendj-sdk/opends/src/messages/messages/replication.properties | 39
opendj-sdk/opends/resource/config/config.ldif | 10
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/FractionalReplicationTest.java | 2031 +++++++++++++++++++++++++++
opendj-sdk/opends/src/server/org/opends/server/replication/plugin/FractionalLDIFImportPlugin.java | 355 ++++
opendj-sdk/opends/src/server/org/opends/server/replication/protocol/MonitorMsg.java | 2
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/AssuredReplicationPluginTest.java | 30
opendj-sdk/opends/src/server/org/opends/server/replication/service/ReplicationDomain.java | 94
opendj-sdk/opends/resource/schema/02-config.ldif | 29
14 files changed, 4,402 insertions(+), 74 deletions(-)
diff --git a/opendj-sdk/opends/resource/config/config.ldif b/opendj-sdk/opends/resource/config/config.ldif
index 0e486b7..850f830 100644
--- a/opendj-sdk/opends/resource/config/config.ldif
+++ b/opendj-sdk/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
diff --git a/opendj-sdk/opends/resource/schema/02-config.ldif b/opendj-sdk/opends/resource/schema/02-config.ldif
index 6aba8b1..236ecda 100644
--- a/opendj-sdk/opends/resource/schema/02-config.ldif
+++ b/opendj-sdk/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' )
diff --git a/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/FractionalLDIFImportPluginConfiguration.xml b/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/FractionalLDIFImportPluginConfiguration.xml
new file mode 100644
index 0000000..a859f9a
--- /dev/null
+++ b/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/FractionalLDIFImportPluginConfiguration.xml
@@ -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>
diff --git a/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/ReplicationDomainConfiguration.xml b/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/ReplicationDomainConfiguration.xml
index b4a5726..8930324 100644
--- a/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/ReplicationDomainConfiguration.xml
+++ b/opendj-sdk/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>
diff --git a/opendj-sdk/opends/src/messages/messages/replication.properties b/opendj-sdk/opends/src/messages/messages/replication.properties
index b77bb30..0dc17b9 100644
--- a/opendj-sdk/opends/src/messages/messages/replication.properties
+++ b/opendj-sdk/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
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/common/ChangeNumber.java b/opendj-sdk/opends/src/server/org/opends/server/replication/common/ChangeNumber.java
index e38bee8..49f9bab 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/common/ChangeNumber.java
+++ b/opendj-sdk/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.
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/FractionalLDIFImportPlugin.java b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/FractionalLDIFImportPlugin.java
new file mode 100644
index 0000000..9afb119
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/FractionalLDIFImportPlugin.java
@@ -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);
+ }
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java
index 76d15fc..82ed278 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java
+++ b/opendj-sdk/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;
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/MonitorMsg.java b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/MonitorMsg.java
index a1dd7b0..94161aa 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/MonitorMsg.java
+++ b/opendj-sdk/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.
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/service/ReplicationDomain.java b/opendj-sdk/opends/src/server/org/opends/server/replication/service/ReplicationDomain.java
index c87076a..fb50284 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/service/ReplicationDomain.java
+++ b/opendj-sdk/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);
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java
index 6a692eb..d10664a 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java
+++ b/opendj-sdk/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
+ }
+ }
}
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/AssuredReplicationPluginTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/AssuredReplicationPluginTest.java
index 329e40a..73f378e 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/AssuredReplicationPluginTest.java
+++ b/opendj-sdk/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.
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/DomainFakeCfg.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/DomainFakeCfg.java
index 92161f0..6f2ffbf 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/DomainFakeCfg.java
+++ b/opendj-sdk/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;
+ }
}
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/FractionalReplicationTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/FractionalReplicationTest.java
new file mode 100644
index 0000000..ddfa514
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/FractionalReplicationTest.java
@@ -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();
+ }
+ }
+}
--
Gitblit v1.10.0