From 3af1c93c0de41be1cdfc0e6aff1d1c98f1d5530b 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
---
opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java | 1671 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 1,661 insertions(+), 10 deletions(-)
diff --git a/opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java b/opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java
index 76d15fc..82ed278 100644
--- a/opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java
+++ b/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;
--
Gitblit v1.10.0