From 86b33497f6d01df4b1185341d9c468d05a5970ca Mon Sep 17 00:00:00 2001
From: gbellato <gbellato@localhost>
Date: Mon, 04 Jun 2007 07:36:45 +0000
Subject: [PATCH] single valued attribute conflict resolution : issue 609
---
opendj-sdk/opends/src/server/org/opends/server/replication/plugin/Historical.java | 693 ++-----------
/dev/null | 240 -----
opendj-sdk/opends/src/server/org/opends/server/messages/ReplicationMessages.java | 8
opendj-sdk/opends/src/server/org/opends/server/replication/plugin/AttributeInfo.java | 119 ++
opendj-sdk/opends/src/server/org/opends/server/replication/plugin/AttrInfoMultiple.java | 653 +++++++++++++
opendj-sdk/opends/src/server/org/opends/server/replication/plugin/AttrInfoWithOptions.java | 10
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/AttrInfoTest.java | 32
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/HistoricalTest.java | 2
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/ModifyConflictTest.java | 805 +++++++++++-----
opendj-sdk/opends/src/server/org/opends/server/replication/plugin/HistVal.java | 12
opendj-sdk/opends/src/server/org/opends/server/replication/plugin/AttrInfoSingle.java | 231 ++++
11 files changed, 1,696 insertions(+), 1,109 deletions(-)
diff --git a/opendj-sdk/opends/src/server/org/opends/server/messages/ReplicationMessages.java b/opendj-sdk/opends/src/server/org/opends/server/messages/ReplicationMessages.java
index cd5fe07..fb4e3cf 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/messages/ReplicationMessages.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/messages/ReplicationMessages.java
@@ -395,6 +395,11 @@
public static final int MSGID_DUPLICATE_REPLICATION_SERVER_ID =
CATEGORY_MASK_SYNC | SEVERITY_MASK_SEVERE_ERROR | 55;
+ /**
+ * Some bad historical information was found in an entry.
+ */
+ public static final int MSGID_BAD_HISTORICAL =
+ CATEGORY_MASK_SYNC | SEVERITY_MASK_SEVERE_ERROR | 56;
/**
@@ -542,5 +547,8 @@
"Received a Null Msg from %s");
registerMessage(MSGID_READER_EXCEPTION,
"Exception when reading messages from %s");
+ registerMessage(MSGID_BAD_HISTORICAL,
+ "Entry %s was containing some unknown historical information,"
+ + " This may cause some inconsistency for this entry");
}
}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/AttrInfo.java b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/AttrInfo.java
deleted file mode 100644
index 183a7c8..0000000
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/AttrInfo.java
+++ /dev/null
@@ -1,240 +0,0 @@
-/*
- * 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
- *
- *
- * Portions Copyright 2006-2007 Sun Microsystems, Inc.
- */
-package org.opends.server.replication.plugin;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-
-import org.opends.server.replication.common.ChangeNumber;
-import org.opends.server.types.AttributeValue;
-
-
-/**
- * This classes is used to store historical information.
- * One object of this type is created for each attribute that was changed in
- * the entry.
- * It allows to record the last time a given value was added, the last
- * time a given value was deleted and the last time the whole attribute was
- * deleted.
- */
-public class AttrInfo
-{
- private ChangeNumber deleteTime, // last time when the attribute was deleted
- lastUpdateTime; // last time the attribute was modified
- private ArrayList<ValueInfo> valuesInfo; // creation or deletion time for
- // given values
- /**
- * create a new AttrInfo object.
- * @param deleteTime the deletion time
- * @param updateTime the update time
- * @param valuesInfo of Value Info
- */
- public AttrInfo(ChangeNumber deleteTime, ChangeNumber updateTime,
- ArrayList<ValueInfo> valuesInfo)
- {
- this.deleteTime = deleteTime;
- this.lastUpdateTime = updateTime;
- if (valuesInfo == null)
- this.valuesInfo = new ArrayList<ValueInfo>();
- else
- this.valuesInfo = valuesInfo;
- }
-
- /**
- * create a new empty AttrInfo object.
- */
- public AttrInfo()
- {
- this.deleteTime = null;
- this.lastUpdateTime = null;
- this.valuesInfo = new ArrayList<ValueInfo>();
- }
-
- /**
- * Returns the last time when the entry was updated.
- * @return the last time when the entry was updated
- */
- ChangeNumber getLastUpdateTime()
- {
- return lastUpdateTime;
- }
-
- /**
- * Returns the last time when the entry was deleted.
- * @return the last time when the entry was deleted
- */
- ChangeNumber getDeleteTime()
- {
- return deleteTime;
- }
-
- /**
- * set the last time when the entry was deleted.
- * @param time the last time when the entry was deleted
- */
- void setDeleteTime(ChangeNumber time)
- {
- deleteTime = time;
- }
-
- /**
- * set the last time when the entry was updated.
- * @param time the last time when the entry was updated
- */
- void setLastUpdateTime(ChangeNumber time)
- {
- lastUpdateTime = time;
- }
-
- /**
- * Duplicate an AttrInfo.
- * ChangeNumber are duplicated by references
- * @return the duplicated AttrInfo
- */
- AttrInfo duplicate()
- {
- AttrInfo dup = new AttrInfo(this.deleteTime, this.lastUpdateTime,
- this.valuesInfo);
- return dup;
- }
-
- /**
- * Delete all historical information that is older than
- * the provided ChangeNumber for this attribute type.
- * Add the delete attribute state information
- * @param CN time when the delete was done
- */
- void delete(ChangeNumber CN)
- {
- // iterate through the values in the valuesInfo
- // and suppress all the values that have not been added
- // after the date of this delete.
- Iterator<ValueInfo> it = this.valuesInfo.iterator();
- while (it.hasNext())
- {
- ValueInfo info = it.next();
- if (CN.newerOrEquals(info.getValueUpdateTime()))
- it.remove();
- }
-
- if (CN.newer(deleteTime))
- {
- deleteTime = CN;
- }
-
- if (CN.newer(lastUpdateTime))
- {
- lastUpdateTime = CN;
- }
- }
-
- /**
- * Change historical information after a delete value.
- * @param val value that was deleted
- * @param CN time when the delete was done
- */
- void delete(AttributeValue val, ChangeNumber CN)
- {
- ValueInfo info = new ValueInfo(val, null, CN);
- this.valuesInfo.remove(info);
- this.valuesInfo.add(info);
- if (CN.newer(lastUpdateTime))
- {
- lastUpdateTime = CN;
- }
- }
-
- /**
- * Change historical information after a delete of a set of values.
- *
- * @param values values that were deleted
- * @param CN time when the delete was done
- */
- void delete(LinkedHashSet<AttributeValue> values, ChangeNumber CN)
- {
- for (AttributeValue val : values)
- {
- ValueInfo info = new ValueInfo(val, null, CN);
- this.valuesInfo.remove(info);
- this.valuesInfo.add(info);
- if (CN.newer(lastUpdateTime))
- {
- lastUpdateTime = CN;
- }
- }
- }
-
- /**
- * Update the historical information when a value is added.
- *
- * @param val values that was added
- * @param CN time when the value was added
- */
- void add(AttributeValue val, ChangeNumber CN)
- {
- ValueInfo info = new ValueInfo(val, CN, null);
- this.valuesInfo.remove(info);
- valuesInfo.add(info);
- if (CN.newer(lastUpdateTime))
- {
- lastUpdateTime = CN;
- }
- }
-
- /**
- * Update the historical information when values are added.
- *
- * @param values the set of added values
- * @param CN time when the add is done
- */
- void add(LinkedHashSet<AttributeValue> values,
- ChangeNumber CN)
- {
- for (AttributeValue val : values)
- {
- ValueInfo info = new ValueInfo(val, CN, null);
- this.valuesInfo.remove(info);
- valuesInfo.add(info);
- if (CN.newer(lastUpdateTime))
- {
- lastUpdateTime = CN;
- }
- }
- }
-
- /**
- * Get the List of ValueInfo for this attribute Info.
- * @return the List of ValueInfo
- */
- public ArrayList<ValueInfo> getValuesInfo()
- {
- return valuesInfo;
- }
-}
-
-
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/AttrInfoMultiple.java b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/AttrInfoMultiple.java
new file mode 100644
index 0000000..4a48cf5
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/AttrInfoMultiple.java
@@ -0,0 +1,653 @@
+/*
+ * 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
+ *
+ *
+ * Portions Copyright 2006-2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.replication.plugin;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.opends.server.replication.common.ChangeNumber;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.Entry;
+import org.opends.server.types.Modification;
+import org.opends.server.types.ModificationType;
+
+
+/**
+ * This classes is used to store historical information for multiple valued
+ * attributes.
+ * One object of this type is created for each attribute that was changed in
+ * the entry.
+ * It allows to record the last time a given value was added, the last
+ * time a given value was deleted and the last time the whole attribute was
+ * deleted.
+ */
+public class AttrInfoMultiple extends AttributeInfo
+{
+ private ChangeNumber deleteTime, // last time when the attribute was deleted
+ lastUpdateTime; // last time the attribute was modified
+ private ArrayList<ValueInfo> valuesInfo; // creation or deletion time for
+ // given values
+ /**
+ * create a new AttrInfo object.
+ * @param deleteTime the deletion time
+ * @param updateTime the update time
+ * @param valuesInfo of Value Info
+ */
+ public AttrInfoMultiple(ChangeNumber deleteTime, ChangeNumber updateTime,
+ ArrayList<ValueInfo> valuesInfo)
+ {
+ this.deleteTime = deleteTime;
+ this.lastUpdateTime = updateTime;
+ if (valuesInfo == null)
+ this.valuesInfo = new ArrayList<ValueInfo>();
+ else
+ this.valuesInfo = valuesInfo;
+ }
+
+ /**
+ * create a new empty AttrInfo object.
+ */
+ public AttrInfoMultiple()
+ {
+ this.deleteTime = null;
+ this.lastUpdateTime = null;
+ this.valuesInfo = new ArrayList<ValueInfo>();
+ }
+
+ /**
+ * Returns the last time when the entry was updated.
+ * @return the last time when the entry was updated
+ */
+ private ChangeNumber getLastUpdateTime()
+ {
+ return lastUpdateTime;
+ }
+
+ /**
+ * Returns the last time when the attribute was deleted.
+ *
+ * @return the last time when the attribute was deleted
+ */
+ public ChangeNumber getDeleteTime()
+ {
+ return deleteTime;
+ }
+
+ /**
+ * Duplicate an AttrInfo.
+ * ChangeNumber are duplicated by references
+ * @return the duplicated AttrInfo
+ */
+ AttrInfoMultiple duplicate()
+ {
+ AttrInfoMultiple dup =
+ new AttrInfoMultiple(this.deleteTime, this.lastUpdateTime,
+ this.valuesInfo);
+ return dup;
+ }
+
+ /**
+ * Delete all historical information that is older than
+ * the provided ChangeNumber for this attribute type.
+ * Add the delete attribute state information
+ * @param CN time when the delete was done
+ */
+ protected void delete(ChangeNumber CN)
+ {
+ // iterate through the values in the valuesInfo
+ // and suppress all the values that have not been added
+ // after the date of this delete.
+ Iterator<ValueInfo> it = this.valuesInfo.iterator();
+ while (it.hasNext())
+ {
+ ValueInfo info = it.next();
+ if (CN.newerOrEquals(info.getValueUpdateTime()))
+ it.remove();
+ }
+
+ if (CN.newer(deleteTime))
+ {
+ deleteTime = CN;
+ }
+
+ if (CN.newer(lastUpdateTime))
+ {
+ lastUpdateTime = CN;
+ }
+ }
+
+ /**
+ * Change historical information after a delete value.
+ * @param val value that was deleted
+ * @param CN time when the delete was done
+ */
+ protected void delete(AttributeValue val, ChangeNumber CN)
+ {
+ ValueInfo info = new ValueInfo(val, null, CN);
+ this.valuesInfo.remove(info);
+ this.valuesInfo.add(info);
+ if (CN.newer(lastUpdateTime))
+ {
+ lastUpdateTime = CN;
+ }
+ }
+
+ /**
+ * Change historical information after a delete of a set of values.
+ *
+ * @param values values that were deleted
+ * @param CN time when the delete was done
+ */
+ protected void delete(LinkedHashSet<AttributeValue> values, ChangeNumber CN)
+ {
+ for (AttributeValue val : values)
+ {
+ ValueInfo info = new ValueInfo(val, null, CN);
+ this.valuesInfo.remove(info);
+ this.valuesInfo.add(info);
+ if (CN.newer(lastUpdateTime))
+ {
+ lastUpdateTime = CN;
+ }
+ }
+ }
+
+ /**
+ * Update the historical information when a value is added.
+ *
+ * @param val values that was added
+ * @param CN time when the value was added
+ */
+ protected void add(AttributeValue val, ChangeNumber CN)
+ {
+ ValueInfo info = new ValueInfo(val, CN, null);
+ this.valuesInfo.remove(info);
+ valuesInfo.add(info);
+ if (CN.newer(lastUpdateTime))
+ {
+ lastUpdateTime = CN;
+ }
+ }
+
+ /**
+ * Update the historical information when values are added.
+ *
+ * @param values the set of added values
+ * @param CN time when the add is done
+ */
+ private void add(LinkedHashSet<AttributeValue> values,
+ ChangeNumber CN)
+ {
+ for (AttributeValue val : values)
+ {
+ ValueInfo info = new ValueInfo(val, CN, null);
+ this.valuesInfo.remove(info);
+ valuesInfo.add(info);
+ if (CN.newer(lastUpdateTime))
+ {
+ lastUpdateTime = CN;
+ }
+ }
+ }
+
+ /**
+ * Get the List of ValueInfo for this attribute Info.
+ *
+ * @return the List of ValueInfo
+ */
+ public ArrayList<ValueInfo> getValuesInfo()
+ {
+ return valuesInfo;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean replayOperation(
+ Iterator<Modification> modsIterator, ChangeNumber changeNumber,
+ Entry modifiedEntry, Modification m)
+ {
+ // We are replaying an operation that was already done
+ // on another master server and this operation has a potential
+ // conflict
+ // with some more recent operations on this same entry
+ // we need to take the more complex path to solve them
+ Attribute modAttr = m.getAttribute();
+ AttributeType type = modAttr.getAttributeType();
+
+ if (ChangeNumber.compare(changeNumber, getLastUpdateTime()) <= 0)
+ {
+ // the attribute was modified after this change -> conflict
+
+ switch (m.getModificationType())
+ {
+ case DELETE:
+ if (changeNumber.older(getDeleteTime()))
+ {
+ /* this delete is already obsoleted by a more recent delete
+ * skip this mod
+ */
+ modsIterator.remove();
+ break;
+ }
+
+ conflictDelete(changeNumber, type, m, modifiedEntry, modAttr);
+ break;
+
+ case ADD:
+ conflictAdd(modsIterator, changeNumber,
+ modAttr.getValues(), modAttr.getOptions());
+ break;
+
+ case REPLACE:
+ if (changeNumber.older(getDeleteTime()))
+ {
+ /* this replace is already obsoleted by a more recent delete
+ * skip this mod
+ */
+ modsIterator.remove();
+ break;
+ }
+ /* save the values that are added by the replace operation
+ * into addedValues
+ * first process the replace as a delete operation -> this generate
+ * a list of values that should be kept
+ * then process the addedValues as if they were coming from a add
+ * -> this generate the list of values that needs to be added
+ * concatenate the 2 generated lists into a replace
+ */
+ LinkedHashSet<AttributeValue> addedValues = modAttr.getValues();
+ modAttr.setValues(new LinkedHashSet<AttributeValue>());
+
+ this.conflictDelete(changeNumber, type, m, modifiedEntry, modAttr);
+
+ LinkedHashSet<AttributeValue> keptValues = modAttr.getValues();
+ this.conflictAdd(modsIterator, changeNumber, addedValues,
+ modAttr.getOptions());
+ keptValues.addAll(addedValues);
+ break;
+
+ case INCREMENT:
+ // TODO : FILL ME
+ break;
+ }
+ return true;
+ }
+ else
+ {
+ processLocalOrNonConflictModification(changeNumber, m);
+ return false;// the attribute was not modified more recently
+ }
+ }
+
+ /**
+ * This method calculate the historical information and update the hist
+ * attribute to store the historical information for modify operation that
+ * does not conflict with previous operation.
+ * This is the usual path and should therefore be optimized.
+ *
+ * It does not check if the operation to process is conflicting or not with
+ * previous operations. The caller is responsible for this.
+ *
+ * @param changeNumber The changeNumber of the operation to process
+ * @param mod The modify operation to process.
+ */
+ public void processLocalOrNonConflictModification(ChangeNumber changeNumber,
+ Modification mod)
+ {
+ /*
+ * The operation is either a non-conflicting operation or a local
+ * operation so there is no need to check the historical information
+ * for conflicts.
+ * If this is a local operation, then this code is run after
+ * the pre-operation phase.
+ * If this is a non-conflicting replicated operation, this code is run
+ * during the handleConflictResolution().
+ */
+
+ Attribute modAttr = mod.getAttribute();
+ AttributeType type = modAttr.getAttributeType();
+
+ switch (mod.getModificationType())
+ {
+ case DELETE:
+ if (modAttr.getValues().isEmpty())
+ {
+ delete(changeNumber);
+ }
+ else
+ {
+ delete(modAttr.getValues(), changeNumber);
+ }
+ break;
+
+ case ADD:
+ if (type.isSingleValue())
+ {
+ delete(changeNumber);
+ }
+ add(modAttr.getValues(), changeNumber);
+ break;
+
+ case REPLACE:
+ /* TODO : can we replace specific attribute values ????? */
+ delete(changeNumber);
+ add(modAttr.getValues(), changeNumber);
+ break;
+
+ case INCREMENT:
+ /* FIXME : we should update ChangeNumber */
+ break;
+ }
+ }
+
+ /**
+ * Process a delete attribute values that is conflicting with a previous
+ * modification.
+ *
+ * @param changeNumber The changeNumber of the currently processed change
+ * @param type attribute type
+ * @param m the modification that is being processed
+ * @param modifiedEntry the entry that is modified (before current mod)
+ * @param attrInfo the historical info associated to the entry
+ * @param modAttr the attribute modification
+ * @return false if there is nothing to do
+ */
+ private boolean conflictDelete(ChangeNumber changeNumber,
+ AttributeType type, Modification m,
+ Entry modifiedEntry,
+ Attribute modAttr )
+ {
+ LinkedHashSet<AttributeValue> delValues;
+ LinkedHashSet<AttributeValue> replValues;
+
+ /*
+ * We are processing a conflicting DELETE modification
+ *
+ * This code is written on the assumption that conflict are
+ * rare. We therefore don't care much about the performance
+ * However since it is rarely executed this code needs to be
+ * as simple as possible to make sure that all paths are tested.
+ * In this case the most simple seem to change the DELETE
+ * in a REPLACE modification that keeps all values
+ * more recent that the DELETE.
+ * we are therefore going to change m into a REPLACE that will keep
+ * all the values that have been updated after the DELETE time
+ * If a value is present in the entry without any state information
+ * it must be removed so we simply ignore them
+ */
+
+
+
+ delValues = modAttr.getValues();
+ if ((delValues == null) || (delValues.isEmpty()))
+ {
+ /*
+ * We are processing a DELETE attribute modification
+ */
+ m.setModificationType(ModificationType.REPLACE);
+ replValues = new LinkedHashSet<AttributeValue>();
+
+ for (Iterator it = getValuesInfo().iterator();
+ it.hasNext();)
+ {
+ ValueInfo valInfo; valInfo = (ValueInfo) it.next();
+
+ if (changeNumber.older(valInfo.getValueUpdateTime()))
+ {
+ /*
+ * this value has been updated after this delete, therefore
+ * this value must be kept
+ */
+ replValues.add(valInfo.getValue());
+ }
+ else
+ {
+ /*
+ * this value is going to be deleted, remove it from historical
+ * information unless it is a Deleted attribute value that is
+ * more recent than this DELETE
+ */
+ if (changeNumber.newerOrEquals(valInfo.getValueDeleteTime()))
+ {
+ it.remove();
+ }
+ }
+ }
+
+ modAttr.setValues(replValues);
+ if (changeNumber.newer(getDeleteTime()))
+ {
+ deleteTime = changeNumber;
+ }
+ if (changeNumber.newer(getLastUpdateTime()))
+ {
+ lastUpdateTime = changeNumber;
+ }
+ }
+ else
+ {
+ /*
+ * we are processing DELETE of some attribute values
+ */
+ ArrayList<ValueInfo> valuesInfo = getValuesInfo();
+
+ for (Iterator<AttributeValue> delValIterator = delValues.iterator();
+ delValIterator.hasNext();)
+ {
+ AttributeValue val = delValIterator.next();
+ Boolean deleteIt = true; // true if the delete must be done
+
+ /* update historical information */
+ ValueInfo valInfo = new ValueInfo(val, null, changeNumber);
+ int index = valuesInfo.indexOf(valInfo);
+ if (index != -1)
+ {
+ /* this value already exist in the historical information */
+ ValueInfo oldValInfo = valuesInfo.get(index);
+ if (changeNumber.newer(oldValInfo.getValueDeleteTime()) &&
+ changeNumber.newer(oldValInfo.getValueUpdateTime()))
+ {
+ valuesInfo.remove(index);
+ valuesInfo.add(valInfo);
+ }
+ else if (oldValInfo.isUpdate())
+ {
+ deleteIt = false;
+ }
+ }
+ else
+ {
+ valuesInfo.add(valInfo);
+ }
+ /* if the attribute value is not to be deleted
+ * or if attribute value is not present suppress it from the
+ * mod to make sure the delete is going to process again
+ */
+ modifiedEntry.getAttribute(type);
+ if (!deleteIt
+ || !modifiedEntry.hasValue(type, modAttr.getOptions(), val))
+ {
+ delValIterator.remove();
+ }
+ }
+ if (changeNumber.newer(getLastUpdateTime()))
+ {
+ lastUpdateTime = changeNumber;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Process a add attribute values that is conflicting with a previous
+ * modification.
+ *
+ * @param modsIterator iterator on the list of modification
+ * @param changeNumber the historical info associated to the entry
+ * @param addValues the values that are added
+ * @param options the options that are added
+ * @return false if operation becomes empty and must not be processed
+ */
+ private boolean conflictAdd(Iterator modsIterator, ChangeNumber changeNumber,
+ LinkedHashSet<AttributeValue> addValues,
+ Set<String> options)
+ {
+ /* if historicalattributedelete is newer forget this mod
+ * else find attr value
+ * if does not exist
+ * add historicalvalueadded timestamp
+ * add real value in entry
+ * else if timestamp older and already was historicalvalueadded
+ * update historicalvalueadded
+ * else if timestamp older and was historicalvaluedeleted
+ * change historicalvaluedeleted into historicalvalueadded
+ * add value in real entry
+ */
+
+ if (changeNumber.older(getDeleteTime()))
+ {
+ /* A delete has been done more recently than this add
+ * forget this MOD ADD
+ */
+ modsIterator.remove();
+ return false;
+ }
+
+ for (Iterator<AttributeValue> valIterator = addValues.iterator();
+ valIterator.hasNext();)
+ {
+ AttributeValue addVal= valIterator.next();
+ ArrayList<ValueInfo> valuesInfo = getValuesInfo();
+ ValueInfo valInfo = new ValueInfo(addVal, changeNumber, null);
+ int index = valuesInfo.indexOf(valInfo);
+ if (index == -1)
+ {
+ /* this values does not exist yet
+ * add it in the historical information
+ * let the operation process normally
+ */
+ valuesInfo.add(valInfo);
+ }
+ else
+ {
+ ValueInfo oldValueInfo = valuesInfo.get(index);
+ if (oldValueInfo.isUpdate())
+ {
+ /* if the value is already present
+ * check if the updateTime must be updated
+ * in all cases suppress this value from the value list
+ * as it is already present in the entry
+ */
+ if (changeNumber.newer(oldValueInfo.getValueUpdateTime()))
+ {
+ valuesInfo.remove(index);
+ valuesInfo.add(valInfo);
+ }
+ valIterator.remove();
+ }
+ else
+ {
+ /* this value is marked as a deleted value
+ * check if this mod is more recent the this delete
+ */
+ if (changeNumber.newer(oldValueInfo.getValueDeleteTime()))
+ {
+ /* this add is more recent,
+ * remove the old delete historical information
+ * and add our more recent one
+ * let the operation process
+ */
+ valuesInfo.remove(index);
+ valuesInfo.add(valInfo);
+ }
+ else
+ {
+ /* the delete that is present in the historical information
+ * is more recent so it must win,
+ * remove this value from the list of values to add
+ * don't update the historical information
+ */
+ valIterator.remove();
+ }
+ }
+ }
+ }
+ if (addValues.isEmpty())
+ {
+ modsIterator.remove();
+ }
+
+ if (changeNumber.newer(getLastUpdateTime()))
+ {
+ lastUpdateTime = changeNumber;
+ }
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void load(HistKey histKey, AttributeValue value, ChangeNumber cn)
+ {
+ switch (histKey)
+ {
+ case ADD:
+ if (value != null)
+ {
+ add(value, cn);
+ }
+ break;
+
+ case DEL:
+ if (value != null)
+ {
+ delete(value, cn);
+ }
+ break;
+
+ case REPL:
+ delete(cn);
+ if (value != null)
+ {
+ add(value, cn);
+ }
+ break;
+
+ case DELATTR:
+ delete(cn);
+ break;
+ }
+ }
+}
+
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/AttrInfoSingle.java b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/AttrInfoSingle.java
new file mode 100644
index 0000000..73236fa
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/AttrInfoSingle.java
@@ -0,0 +1,231 @@
+/*
+ * 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
+ *
+ *
+ * Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.replication.plugin;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+
+import org.opends.server.replication.common.ChangeNumber;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.Entry;
+import org.opends.server.types.Modification;
+import org.opends.server.types.ModificationType;
+
+/**
+ * This classes is used to store historical information for single valued
+ * attributes.
+ * One object of this type is created for each attribute that was changed in
+ * the entry.
+ * It allows to record the last time a given value was added,
+ * and the last time the whole attribute was deleted.
+ */
+public class AttrInfoSingle extends AttributeInfo
+{
+ private ChangeNumber deleteTime = null; // last time when the attribute was
+ // deleted
+ private ChangeNumber addTime = null; // last time when a value was added
+ private AttributeValue value = null; // last added value
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ChangeNumber getDeleteTime()
+ {
+ return deleteTime;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ArrayList<ValueInfo> getValuesInfo()
+ {
+ if (addTime == null)
+ {
+ return new ArrayList<ValueInfo>();
+ }
+ else
+ {
+ ArrayList<ValueInfo> values = new ArrayList<ValueInfo>();
+ values.add(new ValueInfo(value, addTime, null));
+ return values;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void processLocalOrNonConflictModification(ChangeNumber changeNumber,
+ Modification mod)
+ {
+ Attribute modAttr = mod.getAttribute();
+ LinkedHashSet<AttributeValue> values = null;
+ if (modAttr != null)
+ values = modAttr.getValues();
+ AttributeValue newValue = null;
+ if (values.size() != 0)
+ newValue = values.iterator().next();
+
+ switch (mod.getModificationType())
+ {
+ case DELETE:
+ deleteTime = changeNumber;
+ value = newValue;
+ break;
+
+ case ADD:
+ addTime = changeNumber;
+ value = newValue;
+ break;
+
+ case REPLACE:
+ deleteTime = addTime = changeNumber;
+ value = newValue;
+ break;
+
+ case INCREMENT:
+ /* FIXME : we should update ChangeNumber */
+ break;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean replayOperation(Iterator<Modification> modsIterator,
+ ChangeNumber changeNumber, Entry modifiedEntry, Modification mod)
+ {
+ boolean conflict = false;
+
+ Attribute modAttr = mod.getAttribute();
+ LinkedHashSet<AttributeValue> values = null;
+ if (modAttr != null)
+ values = modAttr.getValues();
+ AttributeValue newValue = null;
+ if (values.size() != 0)
+ newValue = values.iterator().next();
+
+ switch (mod.getModificationType())
+ {
+ case DELETE:
+ if ((changeNumber.newer(addTime)) &&
+ ((newValue == null) || ((newValue != null)
+ && (newValue.equals(value)))))
+ {
+ deleteTime = changeNumber;
+ }
+ else
+ {
+ conflict = true;
+ modsIterator.remove();
+ }
+ break;
+
+ case ADD:
+ if (changeNumber.newerOrEquals(deleteTime) && changeNumber.older(addTime))
+ {
+ conflict = true;
+ mod.setModificationType(ModificationType.REPLACE);
+ addTime = changeNumber;
+ value = newValue;
+ }
+ else
+ {
+ if (changeNumber.newerOrEquals(deleteTime)
+ && ((addTime == null ) || addTime.older(deleteTime)))
+ {
+ // no conflict : don´t do anything beside setting the addTime
+ addTime = changeNumber;
+ value = newValue;
+ }
+ else
+ {
+ conflict = true;
+ modsIterator.remove();
+ }
+ }
+
+ break;
+
+ case REPLACE:
+ if ((changeNumber.older(deleteTime)) && (changeNumber.older(deleteTime)))
+ {
+ conflict = true;
+ modsIterator.remove();
+ }
+ else
+ {
+ addTime = changeNumber;
+ value = newValue;
+ deleteTime = changeNumber;
+ }
+ break;
+
+ case INCREMENT:
+ /* FIXME : we should update ChangeNumber */
+ break;
+ }
+ return conflict;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void load(HistKey histKey, AttributeValue value, ChangeNumber cn)
+ {
+ switch (histKey)
+ {
+ case ADD:
+ addTime = cn;
+ this.value = value;
+ break;
+
+ case DEL:
+ deleteTime = cn;
+ if (value != null)
+ this.value = value;
+ break;
+
+ case REPL:
+ addTime = deleteTime = cn;
+ if (value != null)
+ this.value = value;
+ break;
+
+ case DELATTR:
+ deleteTime = cn;
+ break;
+ }
+ }
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/AttrInfoWithOptions.java b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/AttrInfoWithOptions.java
index 66ded95..21baec5 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/AttrInfoWithOptions.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/AttrInfoWithOptions.java
@@ -36,14 +36,14 @@
*/
public class AttrInfoWithOptions
{
- private HashMap<Set<String> ,AttrInfo> attributesInfo;
+ private HashMap<Set<String> ,AttributeInfo> attributesInfo;
/**
* creates a new AttrInfoWithOptions.
*/
public AttrInfoWithOptions()
{
- attributesInfo = new HashMap<Set<String> ,AttrInfo>();
+ attributesInfo = new HashMap<Set<String> ,AttributeInfo>();
}
/**
@@ -52,7 +52,7 @@
* @param options the options
* @return the information
*/
- public AttrInfo get(Set<String> options)
+ public AttributeInfo get(Set<String> options)
{
return attributesInfo.get(options);
}
@@ -64,7 +64,7 @@
* @param attrInfo the info to associate
* @return the info to associate
*/
- public AttrInfo put(Set<String> options,AttrInfo attrInfo )
+ public AttributeInfo put(Set<String> options, AttributeInfo attrInfo )
{
return attributesInfo.put(options, attrInfo);
}
@@ -73,7 +73,7 @@
* get the Attributes information associated to this object.
* @return the set of informations
*/
- public HashMap<Set<String>, AttrInfo> getAttributesInfo()
+ public HashMap<Set<String>, AttributeInfo> getAttributesInfo()
{
return attributesInfo;
}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/AttributeInfo.java b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/AttributeInfo.java
new file mode 100644
index 0000000..10bcd7f
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/AttributeInfo.java
@@ -0,0 +1,119 @@
+/*
+ * 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
+ *
+ *
+ * Portions Copyright 2006-2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.replication.plugin;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.opends.server.replication.common.ChangeNumber;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.Entry;
+import org.opends.server.types.Modification;
+
+
+/**
+ * This classes is used to store historical information.
+ * One object of this type is created for each attribute that was changed in
+ * the entry.
+ */
+public abstract class AttributeInfo
+{
+ /**
+ * This method will be called when replaying an operation.
+ * It should use whatever historical information is stored in this class
+ * to solve the conflict and modify the mod and the mods iterator accordingly
+ *
+ * @param modsIterator The iterator on the mods from which the mod is\
+ * extracted.
+ * @param changeNumber The changeNumber associated to the operation.
+ * @param modifiedEntry The entry modified by this operation.
+ * @param mod The modification.
+ *
+ * @return a boolean indicating if a conflict was detected.
+ */
+ public abstract boolean replayOperation(
+ Iterator<Modification> modsIterator, ChangeNumber changeNumber,
+ Entry modifiedEntry, Modification mod);
+
+ /**
+ * This method calculate the historical information and update the hist
+ * attribute to store the historical information for modify operation that
+ * does not conflict with previous operation.
+ * This is the usual path and should therefore be optimized.
+ *
+ * It does not check if the operation to process is conflicting or not with
+ * previous operations. The caller is responsible for this.
+ *
+ * @param changeNumber The changeNumber of the operation to process
+ * @param mod The modify operation to process.
+ */
+ public abstract void processLocalOrNonConflictModification(
+ ChangeNumber changeNumber, Modification mod);
+
+ /**
+ * Create a new AttributeInfo object that will be used with the givene type.
+ *
+ * @param type the AttrbuteType with which the ATtributeInfo is going to be
+ * used.
+ * @return a new AttributeInfo object.
+ */
+ public static AttributeInfo createAttributeInfo(AttributeType type)
+ {
+ if (type.isSingleValue())
+ return new AttrInfoSingle();
+ else
+ return new AttrInfoMultiple();
+ }
+
+ /**
+ * Get the List of ValueInfo for this attribute Info.
+ *
+ * @return the List of ValueInfo
+ */
+ public abstract ArrayList<ValueInfo> getValuesInfo();
+
+
+ /**
+ * Returns the last time when the attribute was deleted.
+ *
+ * @return the last time when the attribute was deleted
+ */
+ public abstract ChangeNumber getDeleteTime();
+
+ /**
+ * Load the provided information.
+ *
+ * @param histKey the key to load.
+ * @param value the associated value or null if there is no value;
+ * @param cn the associated ChangeNumber.
+ */
+ public abstract void load(
+ HistKey histKey, AttributeValue value, ChangeNumber cn);
+
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/HistVal.java b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/HistVal.java
index a947d44..f5ae47c 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/HistVal.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/HistVal.java
@@ -70,6 +70,9 @@
* description:00000108b3a6cbb800000001:repl:new_value
* or
* description:00000108b3a6cbb800000001:delAttr
+ * or
+ * description:00000108b3a6554100000001:add
+ * or
*
* so after split
* token[0] will contain the attribute name
@@ -104,8 +107,13 @@
stringValue = null;
if (histKey != HistKey.DELATTR)
{
- stringValue = token[3];
- attributeValue = new AttributeValue(attrType, stringValue);
+ if (token.length == 4)
+ {
+ stringValue = token[3];
+ attributeValue = new AttributeValue(attrType, stringValue);
+ }
+ else
+ attributeValue = null;
}
else
{
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/Historical.java b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/Historical.java
index 03d50b5..5e7aacc 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/Historical.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/Historical.java
@@ -26,7 +26,10 @@
*/
package org.opends.server.replication.plugin;
-import java.util.ArrayList;
+import static org.opends.server.loggers.ErrorLogger.logError;
+import static org.opends.server.messages.MessageHandler.getMessage;
+import static org.opends.server.messages.ReplicationMessages.*;
+
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
@@ -45,6 +48,8 @@
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.Entry;
+import org.opends.server.types.ErrorLogCategory;
+import org.opends.server.types.ErrorLogSeverity;
import org.opends.server.types.Modification;
import org.opends.server.types.ModificationType;
@@ -84,11 +89,6 @@
static final AttributeType entryuuidAttrType =
DirectoryServer.getSchema().getAttributeType(ENTRYUIDNAME);
- /*
- * The last update seen on this entry, allows fast conflict detection.
- */
- private ChangeNumber moreRecentChangenumber =
- new ChangeNumber(0,0,(short)0);
/*
* contains Historical information for each attribute sorted by attribute type
@@ -108,21 +108,6 @@
}
/**
- * Duplicates an Historical Object.
- * attributesInfo are nor duplicated but used as references.
- * @return The duplicate of the Historical Object
- */
- public Historical duplicate()
- {
- Historical dup = new Historical();
-
- dup.attributesInfo =
- new HashMap<AttributeType,AttrInfoWithOptions>(attributesInfo);
- dup.moreRecentChangenumber = this.moreRecentChangenumber;
- return dup;
- }
-
- /**
* Process an operation.
* This method is responsible for detecting and resolving conflict for
* modifyOperation. This is done by using the historical information.
@@ -132,200 +117,33 @@
* @return true if the replayed operation was in conflict
*/
public boolean replayOperation(ModifyOperation modifyOperation,
- Entry modifiedEntry)
+ Entry modifiedEntry)
{
boolean bConflict = false;
List<Modification> mods = modifyOperation.getModifications();
ChangeNumber changeNumber =
OperationContext.getChangeNumber(modifyOperation);
- for (Iterator modsIterator = mods.iterator(); modsIterator.hasNext();)
+ for (Iterator<Modification> modsIterator = mods.iterator();
+ modsIterator.hasNext(); )
{
- Modification m = (Modification) modsIterator.next();
- Attribute modAttr = m.getAttribute();
- Set<String> options = modAttr.getOptions();
- AttributeType type = modAttr.getAttributeType();
- AttrInfoWithOptions attrInfoWithOptions = attributesInfo.get(type);
- AttrInfo attrInfo = null;
- if (attrInfoWithOptions != null)
+ Modification m = modsIterator.next();
+
+ AttributeInfo attrInfo = getAttrInfo(m);
+
+ if (attrInfo.replayOperation(modsIterator, changeNumber,
+ modifiedEntry, m))
{
- attrInfo = attrInfoWithOptions.get(options);
- }
-
- if (this.hasConflict(attrInfo, changeNumber))
- {
- // We are replaying an operation that was already done
- // on another master server and this operation has a potential
- // conflict
- // with some more recent operations on this same entry
- // we need to take the more complex path to solve them
-
- switch (m.getModificationType())
- {
- case DELETE:
- if (changeNumber.older(attrInfo.getDeleteTime()))
- {
- /* this delete is already obsoleted by a more recent delete
- * skip this mod
- */
- modsIterator.remove();
- break;
- }
-
- this.conflictDelete(changeNumber,
- type, m, modifiedEntry, attrInfo, modAttr);
- break;
-
- case ADD:
- this.conflictAdd(modsIterator, changeNumber, attrInfo,
- modAttr.getValues(), modAttr.getOptions());
- break;
-
- case REPLACE:
- if (changeNumber.older(attrInfo.getDeleteTime()))
- {
- /* this replace is already obsoleted by a more recent delete
- * skip this mod
- */
- modsIterator.remove();
- break;
- }
- /* save the values that are added by the replace operation
- * into addedValues
- * first process the replace as a delete operation -> this generate
- * a list of values that should be kept
- * then process the addedValues as if they were coming from a add
- * -> this generate the list of values that needs to be added
- * concatenate the 2 generated lists into a replace
- */
- LinkedHashSet<AttributeValue> addedValues = modAttr.getValues();
- modAttr.setValues(new LinkedHashSet<AttributeValue>());
-
- this.conflictDelete(changeNumber, type, m, modifiedEntry,
- attrInfo, modAttr);
-
- LinkedHashSet<AttributeValue> keptValues = modAttr.getValues();
- this.conflictAdd(modsIterator, changeNumber, attrInfo, addedValues,
- modAttr.getOptions());
- keptValues.addAll(addedValues);
- break;
-
- case INCREMENT:
- // TODO : FILL ME
- break;
- }
bConflict = true;
}
- else
- {
- processLocalOrNonConflictModification(changeNumber, m);
- }
}
- // TODO : now purge old historical information
-
- if (moreRecentChangenumber == null ||
- moreRecentChangenumber.older(changeNumber))
- {
- moreRecentChangenumber = changeNumber;
- }
return bConflict;
}
/**
- * This method calculate the historical information and update the hist
- * attribute to store the historical information for modify operation that
- * does not conflict with previous operation.
- * This is the usual path and should therefore be optimized.
- *
- * It does not check if the operation to process is conflicting or not with
- * previous operations. The caller is responsible for this.
- *
- * @param changeNumber The changeNumber of the operation to process
- * @param mod The modify operation to process.
- */
- private void processLocalOrNonConflictModification(ChangeNumber changeNumber,
- Modification mod)
- {
- /*
- * The operation is either a non-conflicting operation or a local
- * operation so there is no need to check the historical information
- * for conflicts.
- * If this is a local operation, the this code is run during
- * the pre-operation phase (TODO : should make sure that replication
- * is always run after all other plugins)
- * If this is a non-conflicting replicated operation, this code is run
- * during the handleConflictResolution().
- */
-
- Attribute modAttr = mod.getAttribute();
- if (modAttr.getAttributeType().equals(historicalAttrType))
- {
- return;
- }
- Set<String> options = modAttr.getOptions();
- AttributeType type = modAttr.getAttributeType();
- AttrInfoWithOptions attrInfoWithOptions = attributesInfo.get(type);
- AttrInfo attrInfo;
- if (attrInfoWithOptions != null)
- {
- attrInfo = attrInfoWithOptions.get(options);
- }
- else
- {
- attrInfo = null;
- }
-
- /*
- * The following code only works for multi-valued attributes.
- * TODO : See impact of single valued attributes
- */
- if (attrInfo == null)
- {
- attrInfo = new AttrInfo();
- if (attrInfoWithOptions == null)
- {
- attrInfoWithOptions = new AttrInfoWithOptions();
- }
- attrInfoWithOptions.put(options, attrInfo);
- attributesInfo.put(type, attrInfoWithOptions);
- }
- switch (mod.getModificationType())
- {
- case DELETE:
- if (modAttr.getValues().isEmpty())
- {
- attrInfo.delete(changeNumber);
- }
- else
- {
- attrInfo.delete(modAttr.getValues(), changeNumber);
- }
- break;
-
- case ADD:
- if (type.isSingleValue())
- {
- attrInfo.delete(changeNumber);
- }
- attrInfo.add(modAttr.getValues(), changeNumber);
- break;
-
- case REPLACE:
- /* TODO : can we replace specific attribute values ????? */
- attrInfo.delete(changeNumber);
- attrInfo.add(modAttr.getValues(), changeNumber);
- break;
-
- case INCREMENT:
- /* FIXME : we should update ChangeNumber */
- break;
- }
- }
-
-
- /**
* Append replacement of state information to a given modification.
+ *
* @param modifyOperation the modification.
*/
public void generateState(ModifyOperation modifyOperation)
@@ -346,12 +164,9 @@
{
for (Modification mod : mods)
{
- processLocalOrNonConflictModification(changeNumber, mod);
- }
- if (moreRecentChangenumber == null ||
- moreRecentChangenumber.older(changeNumber))
- {
- moreRecentChangenumber = changeNumber;
+ AttributeInfo attrInfo = getAttrInfo(mod);
+ if (attrInfo != null)
+ attrInfo.processLocalOrNonConflictModification(changeNumber, mod);
}
}
@@ -364,6 +179,48 @@
}
/**
+ * Get the AttrInfo for a given Modification.
+ * The AttrInfo is the object that is used to store the historical
+ * information of a given attribute type.
+ * If there is no historical information for this attribute yet, a new
+ * empty AttrInfo is created and returned.
+ *
+ * @param mod The Modification that must be used.
+ * @return The AttrInfo corresponding to the given Modification.
+ */
+ private AttributeInfo getAttrInfo(Modification mod)
+ {
+ Attribute modAttr = mod.getAttribute();
+ if (modAttr.getAttributeType().equals(Historical.historicalAttrType))
+ {
+ // Don´t keep historical information for the attribute that is
+ // used to store the historical information.
+ return null;
+ }
+ Set<String> options = modAttr.getOptions();
+ AttributeType type = modAttr.getAttributeType();
+ AttrInfoWithOptions attrInfoWithOptions = attributesInfo.get(type);
+ AttributeInfo attrInfo;
+ if (attrInfoWithOptions != null)
+ {
+ attrInfo = attrInfoWithOptions.get(options);
+ }
+ else
+ {
+ attrInfoWithOptions = new AttrInfoWithOptions();
+ attributesInfo.put(type, attrInfoWithOptions);
+ attrInfo = null;
+ }
+
+ if (attrInfo == null)
+ {
+ attrInfo = AttributeInfo.createAttributeInfo(type);
+ attrInfoWithOptions.put(options, attrInfo);
+ }
+ return attrInfo;
+ }
+
+ /**
* Encode the historical information in an operational attribute.
* @return The historical information encoded in an operational attribute.
*/
@@ -376,16 +233,17 @@
{
AttributeType type = entryWithOptions.getKey();
- HashMap<Set<String> , AttrInfo> attrwithoptions =
+ HashMap<Set<String> , AttributeInfo> attrwithoptions =
entryWithOptions.getValue().getAttributesInfo();
- for (Map.Entry<Set<String>, AttrInfo> entry : attrwithoptions.entrySet())
+ for (Map.Entry<Set<String>, AttributeInfo> entry :
+ attrwithoptions.entrySet())
{
boolean delAttr = false;
Set<String> options = entry.getKey();
String optionsString = "";
- AttrInfo info = entry.getValue();
- ChangeNumber deleteTime = info.getDeleteTime();
+ AttributeInfo info = entry.getValue();
+
if (options != null)
{
@@ -398,6 +256,7 @@
optionsString = optionsBuilder.toString();
}
+ ChangeNumber deleteTime = info.getDeleteTime();
/* generate the historical information for deleted attributes */
if (deleteTime != null)
{
@@ -428,9 +287,18 @@
}
else
{
- strValue = type.getNormalizedPrimaryName() + optionsString + ":" +
- valInfo.getValueUpdateTime().toString() +
- ":add:" + valInfo.getValue().toString();
+ if (valInfo.getValue() == null)
+ {
+ strValue = type.getNormalizedPrimaryName() + optionsString
+ + ":" + valInfo.getValueUpdateTime().toString() +
+ ":add";
+ }
+ else
+ {
+ strValue = type.getNormalizedPrimaryName() + optionsString
+ + ":" + valInfo.getValueUpdateTime().toString() +
+ ":add:" + valInfo.getValue().toString();
+ }
}
AttributeValue val = new AttributeValue(historicalAttrType,
@@ -463,286 +331,6 @@
return attr;
}
- /**
- * Detect if a new change has a potential conflict with previous changes.
- * Must be blinding fast
- * @param info the historical information for the attribute that
- * was modified
- * @param newChange the ChangeNumber of the change for which we need to check
- * for potential conflict
- * @return true if there is a potential conflict, false otherwise
- */
- private boolean hasConflict(AttrInfo info, ChangeNumber newChange)
- {
- // if I've already seen a change that is more recent than the one
- // that is currently being processed, then there is
- // a potential conflict
- if (ChangeNumber.compare(newChange, moreRecentChangenumber) <= 0)
- {
- if (info == null)
- {
- return false; // the attribute was never modified -> no conflict
- }
- else if (ChangeNumber.compare(newChange, info.getLastUpdateTime()) <= 0)
- {
- return true; // the attribute was modified after this change -> conflict
- }
- else
- {
- return false;// the attribute was not modified more recently
- }
- }
- else
- {
- return false;
- }
- }
-
- /**
- * Process a delete attribute values that is conflicting with a previous
- * modification.
- *
- * @param changeNumber The changeNumber of the currently processed change
- * @param type attribute type
- * @param m the modification that is being processed
- * @param modifiedEntry the entry that is modified (before current mod)
- * @param attrInfo the historical info associated to the entry
- * @param modAttr the attribute modification
- * @return false if there is nothing to do
- */
- private boolean conflictDelete(ChangeNumber changeNumber,
- AttributeType type, Modification m,
- Entry modifiedEntry,
- AttrInfo attrInfo, Attribute modAttr )
- {
- LinkedHashSet<AttributeValue> delValues;
- LinkedHashSet<AttributeValue> replValues;
-
- /*
- * We are processing a conflicting DELETE modification
- *
- * This code is written on the assumption that conflict are
- * rare. We therefore don't care much about the performance
- * However since it is rarely executed this code needs to be
- * as simple as possible to make sure that all paths are tested.
- * In this case the most simple seem to change the DELETE
- * in a REPLACE modification that keeps all values
- * more recent that the DELETE.
- * we are therefore going to change m into a REPLACE that will keep
- * all the values that have been updated after the DELETE time
- * If a value is present in the entry without any state information
- * it must be removed so we simply ignore them
- */
-
-
-
- delValues = modAttr.getValues();
- if ((delValues == null) || (delValues.isEmpty()))
- {
- /*
- * We are processing a DELETE attribute modification
- */
- m.setModificationType(ModificationType.REPLACE);
- replValues = new LinkedHashSet<AttributeValue>();
-
- for (Iterator it = attrInfo.getValuesInfo().iterator();
- it.hasNext();)
- {
- ValueInfo valInfo; valInfo = (ValueInfo) it.next();
-
- if (changeNumber.older(valInfo.getValueUpdateTime()))
- {
- /*
- * this value has been updated after this delete, therefore
- * this value must be kept
- */
- replValues.add(valInfo.getValue());
- }
- else
- {
- /*
- * this value is going to be deleted, remove it from historical
- * information unless it is a Deleted attribute value that is
- * more recent than this DELETE
- */
- if (changeNumber.newerOrEquals(valInfo.getValueDeleteTime()))
- {
- it.remove();
- }
- }
- }
-
- modAttr.setValues(replValues);
- if (changeNumber.newer(attrInfo.getDeleteTime()))
- {
- attrInfo.setDeleteTime(changeNumber);
- }
- if (changeNumber.newer(attrInfo.getLastUpdateTime()))
- {
- attrInfo.setLastUpdateTime(changeNumber);
- }
- }
- else
- {
- /*
- * we are processing DELETE of some attribute values
- */
- ArrayList<ValueInfo> valuesInfo = attrInfo.getValuesInfo();
-
- for (Iterator<AttributeValue> delValIterator = delValues.iterator();
- delValIterator.hasNext();)
- {
- AttributeValue val = delValIterator.next();
- Boolean deleteIt = true; // true if the delete must be done
-
- /* update historical information */
- ValueInfo valInfo = new ValueInfo(val, null, changeNumber);
- int index = valuesInfo.indexOf(valInfo);
- if (index != -1)
- {
- /* this value already exist in the historical information */
- ValueInfo oldValInfo = valuesInfo.get(index);
- if (changeNumber.newer(oldValInfo.getValueDeleteTime()) &&
- changeNumber.newer(oldValInfo.getValueUpdateTime()))
- {
- valuesInfo.remove(index);
- valuesInfo.add(valInfo);
- }
- else if (oldValInfo.isUpdate())
- {
- deleteIt = false;
- }
- }
- else
- {
- valuesInfo.add(valInfo);
- }
- /* if the attribute value is not to be deleted
- * or if attribute value is not present suppress it from the
- * mod to make sure the delete is going to process again
- */
- modifiedEntry.getAttribute(type);
- if (!deleteIt
- || !modifiedEntry.hasValue(type, modAttr.getOptions(), val))
- {
- delValIterator.remove();
- }
- }
- if (changeNumber.newer(attrInfo.getLastUpdateTime()))
- {
- attrInfo.setLastUpdateTime(changeNumber);
- }
- }
- return true;
- }
-
- /**
- * Process a add attribute values that is conflicting with a previous
- * modification.
- *
- * @param modsIterator iterator on the list of modification
- * @param changeNumber the historical info associated to the entry
- * @param attrInfo the historical info associated to the entry
- * @param addValues the values that are added
- * @param options the options that are added
- * @return false if operation becomes empty and must not be processed
- */
- private boolean conflictAdd(Iterator modsIterator, ChangeNumber changeNumber,
- AttrInfo attrInfo,
- LinkedHashSet<AttributeValue> addValues,
- Set<String> options)
- {
- /* if historicalattributedelete is newer forget this mod
- * else find attr value
- * if does not exist
- * add historicalvalueadded timestamp
- * add real value in entry
- * else if timestamp older and already was historicalvalueadded
- * update historicalvalueadded
- * else if timestamp older and was historicalvaluedeleted
- * change historicalvaluedeleted into historicalvalueadded
- * add value in real entry
- */
-
- if (changeNumber.older(attrInfo.getDeleteTime()))
- {
- /* A delete has been done more recently than this add
- * forget this MOD ADD
- */
- modsIterator.remove();
- return false;
- }
-
- for (Iterator<AttributeValue> valIterator = addValues.iterator();
- valIterator.hasNext();)
- {
- AttributeValue addVal= valIterator.next();
- ArrayList<ValueInfo> valuesInfo = attrInfo.getValuesInfo();
- ValueInfo valInfo = new ValueInfo(addVal, changeNumber, null);
- int index = valuesInfo.indexOf(valInfo);
- if (index == -1)
- {
- /* this values does not exist yet
- * add it in the historical information
- * let the operation process normally
- */
- valuesInfo.add(valInfo);
- }
- else
- {
- ValueInfo oldValueInfo = valuesInfo.get(index);
- if (oldValueInfo.isUpdate())
- {
- /* if the value is already present
- * check if the updateTime must be updated
- * in all cases suppress this value from the value list
- * as it is already present in the entry
- */
- if (changeNumber.newer(oldValueInfo.getValueUpdateTime()))
- {
- valuesInfo.remove(index);
- valuesInfo.add(valInfo);
- }
- valIterator.remove();
- }
- else
- {
- /* this value is marked as a deleted value
- * check if this mod is more recent the this delete
- */
- if (changeNumber.newer(oldValueInfo.getValueDeleteTime()))
- {
- /* this add is more recent,
- * remove the old delete historical information
- * and add our more recent one
- * let the operation process
- */
- valuesInfo.remove(index);
- valuesInfo.add(valInfo);
- }
- else
- {
- /* the delete that is present in the historical information
- * is more recent so it must win,
- * remove this value from the list of values to add
- * don't update the historical information
- */
- valIterator.remove();
- }
- }
- }
- }
- if (addValues.isEmpty())
- {
- modsIterator.remove();
- }
-
- if (changeNumber.newer(attrInfo.getLastUpdateTime()))
- {
- attrInfo.setLastUpdateTime(changeNumber);
- }
- return true;
- }
/**
* read the historical information from the entry attribute and
@@ -756,7 +344,7 @@
Historical histObj = new Historical();
AttributeType lastAttrType = null;
Set<String> lastOptions = new HashSet<String>();
- AttrInfo attrInfo = null;
+ AttributeInfo attrInfo = null;
AttrInfoWithOptions attrInfoWithOptions = null;
if (hist == null)
@@ -764,89 +352,70 @@
return histObj;
}
- for (Attribute attr : hist)
+ try
{
- for (AttributeValue val : attr.getValues())
+ for (Attribute attr : hist)
{
- HistVal histVal = new HistVal(val.getStringValue());
- AttributeType attrType = histVal.getAttrType();
- Set<String> options = histVal.getOptions();
- ChangeNumber cn = histVal.getCn();
- AttributeValue value = histVal.getAttributeValue();
- HistKey histKey = histVal.getHistKey();
-
- if (attrType == null)
+ for (AttributeValue val : attr.getValues())
{
- /*
- * This attribute is unknown from the schema
- * Just skip it, the modification will be processed but no
- * historical information is going to be kept.
- * TODO : recovery tool should deal with this, add some logging.
- */
- continue;
- }
+ HistVal histVal = new HistVal(val.getStringValue());
+ AttributeType attrType = histVal.getAttrType();
+ Set<String> options = histVal.getOptions();
+ ChangeNumber cn = histVal.getCn();
+ AttributeValue value = histVal.getAttributeValue();
+ HistKey histKey = histVal.getHistKey();
- /* if attribute type does not match we create new
- * AttrInfoWithOptions and AttrInfo
- * we also add old AttrInfoWithOptions into histObj.attributesInfo
- * if attribute type match but options does not match we create new
- * AttrInfo that we add to AttrInfoWithOptions
- * if both match we keep everything
- */
- if (attrType != lastAttrType)
- {
- attrInfo = new AttrInfo();
- attrInfoWithOptions = new AttrInfoWithOptions();
- attrInfoWithOptions.put(options, attrInfo);
- histObj.attributesInfo.put(attrType, attrInfoWithOptions);
-
- lastAttrType = attrType;
- lastOptions = options;
- }
- else
- {
- if (!options.equals(lastOptions))
+ if (attrType == null)
{
- attrInfo = new AttrInfo();
+ /*
+ * This attribute is unknown from the schema
+ * Just skip it, the modification will be processed but no
+ * historical information is going to be kept.
+ * TODO : REPAIR tool should deal with this, add some logging.
+ */
+ continue;
+ }
+
+ /* if attribute type does not match we create new
+ * AttrInfoWithOptions and AttrInfo
+ * we also add old AttrInfoWithOptions into histObj.attributesInfo
+ * if attribute type match but options does not match we create new
+ * AttrInfo that we add to AttrInfoWithOptions
+ * if both match we keep everything
+ */
+ if (attrType != lastAttrType)
+ {
+ attrInfo = AttributeInfo.createAttributeInfo(attrType);
+ attrInfoWithOptions = new AttrInfoWithOptions();
attrInfoWithOptions.put(options, attrInfo);
+ histObj.attributesInfo.put(attrType, attrInfoWithOptions);
+
+ lastAttrType = attrType;
lastOptions = options;
}
- }
-
- if (histObj.moreRecentChangenumber.older(cn))
- {
- histObj.moreRecentChangenumber = cn;
- }
-
- switch (histKey)
- {
- case ADD:
- if (value != null)
+ else
{
- attrInfo.add(value, cn);
+ if (!options.equals(lastOptions))
+ {
+ attrInfo = AttributeInfo.createAttributeInfo(attrType);
+ attrInfoWithOptions.put(options, attrInfo);
+ lastOptions = options;
+ }
}
- break;
- case DEL:
- if (value != null)
- {
- attrInfo.delete(value, cn);
- }
- break;
-
- case REPL:
- attrInfo.delete(cn);
- if (value != null)
- {
- attrInfo.add(value, cn);
- }
- break;
-
- case DELATTR:
- attrInfo.delete(cn);
- break;
+ attrInfo.load(histKey, value, cn);
}
}
+ } catch (Exception e)
+ {
+ // Any exception happening here means that the coding of the hsitorical
+ // information was wrong.
+ // Log an error and continue with an empty historical.
+ int msgID = MSGID_BAD_HISTORICAL;
+ String message = getMessage(msgID, entry.getDN().toString());
+ logError(ErrorLogCategory.SYNCHRONIZATION,
+ ErrorLogSeverity.SEVERE_ERROR,
+ message, msgID);
}
/* set the reference to the historical information in the entry */
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/AttrInfoTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/AttrInfoTest.java
index 92a7307..85c15e0 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/AttrInfoTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/AttrInfoTest.java
@@ -32,7 +32,7 @@
import org.opends.server.core.DirectoryServer;
import org.opends.server.replication.ReplicationTestCase;
import org.opends.server.replication.common.ChangeNumber;
-import org.opends.server.replication.plugin.AttrInfo;
+import org.opends.server.replication.plugin.AttrInfoMultiple;
import org.opends.server.replication.plugin.ValueInfo;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
@@ -87,23 +87,7 @@
throws Exception
{
// Create an empty AttrInfo
- AttrInfo attrInfo1 = new AttrInfo();
-
- // Check getLastUpdateTime setLastUpdateTime
- if (attrInfo1.getLastUpdateTime() != null)
- {
- assertTrue(false);
- }
- attrInfo1.setLastUpdateTime(updateTime);
- assertTrue(attrInfo1.getLastUpdateTime().compareTo(updateTime) == 0);
-
- // Check getDeleteTime setDeleteTime
- if (attrInfo1.getDeleteTime() != null)
- {
- assertTrue(false);
- }
- attrInfo1.setDeleteTime(deleteTime);
- assertTrue(attrInfo1.getDeleteTime().compareTo(deleteTime) == 0);
+ AttrInfoMultiple attrInfo1 = new AttrInfoMultiple();
// Check add(AttributeValue val, ChangeNumber CN)
attrInfo1.add(att, updateTime);
@@ -116,15 +100,13 @@
ValueInfo valueInfo2 = new ValueInfo(att, updateTime, deleteTime);
ArrayList<ValueInfo> values = new ArrayList<ValueInfo>();
values.add(valueInfo2);
- AttrInfo attrInfo2 = new AttrInfo(deleteTime, updateTime, values);
+ AttrInfoMultiple attrInfo2 = new AttrInfoMultiple(deleteTime, updateTime, values);
// Check equality
- assertTrue(attrInfo1.getLastUpdateTime().compareTo(
- attrInfo2.getLastUpdateTime()) == 0);
- assertTrue(attrInfo1.getDeleteTime().compareTo(attrInfo2.getDeleteTime())==0);
+ //assertTrue(attrInfo1.getDeleteTime().compareTo(attrInfo2.getDeleteTime())==0);
// Check constructor with time parameter and not Value
- AttrInfo attrInfo3 = new AttrInfo(deleteTime, updateTime, null);
+ AttrInfoMultiple attrInfo3 = new AttrInfoMultiple(deleteTime, updateTime, null);
attrInfo3.add(att, updateTime);
ArrayList<ValueInfo> values3 = attrInfo3.getValuesInfo();
assertTrue(values3.size() == 1);
@@ -132,11 +114,9 @@
assertTrue(values3.get(0).equals(valueInfo1));
// Check duplicate
- AttrInfo attrInfo4 = attrInfo3.duplicate();
+ AttrInfoMultiple attrInfo4 = attrInfo3.duplicate();
ArrayList<ValueInfo> values4 = attrInfo4.getValuesInfo();
assertTrue(attrInfo4.getDeleteTime().compareTo(attrInfo3.getDeleteTime())==0);
- assertTrue(attrInfo4.getLastUpdateTime().compareTo(
- attrInfo3.getLastUpdateTime()) == 0);
assertEquals(values4.size(), values3.size());
// Check delete(AttributeValue val, ChangeNumber CN)
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/HistoricalTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/HistoricalTest.java
index af0d948..be4b289 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/HistoricalTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/HistoricalTest.java
@@ -195,7 +195,7 @@
* second server on a different entry. Confused yet?
* @throws Exception If the test fails.
*/
- @Test(enabled=false, groups="slow")
+ @Test(enabled=true, groups="slow")
public void conflictSingleValue()
throws Exception
{
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/ModifyConflictTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/ModifyConflictTest.java
index 89cc688..ef37b40 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/ModifyConflictTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/ModifyConflictTest.java
@@ -55,6 +55,7 @@
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
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;
@@ -82,32 +83,7 @@
@Test()
public void replaceAndAdd() throws Exception
{
- DN dn = DN.decode("dc=com");
- Map<ObjectClass, String> objectClasses = new HashMap<ObjectClass, String>();
- ObjectClass org = DirectoryServer.getObjectClass("organization");
- objectClasses.put(org, "organization");
-
- Entry entry = new Entry(dn, objectClasses, null, null);
-
- // Construct a new random UUID. and add it into the entry
- UUID uuid = UUID.randomUUID();
-
- // Create the att values list of uuid
- LinkedHashSet<AttributeValue> valuesUuid =
- new LinkedHashSet<AttributeValue>(1);
- valuesUuid.add(new AttributeValue(Historical.entryuuidAttrType,
- new ASN1OctetString(uuid.toString())));
- ArrayList<Attribute> uuidList = new ArrayList<Attribute>(1);
- Attribute uuidAttr = new Attribute(Historical.entryuuidAttrType,
- "entryUUID", valuesUuid);
- uuidList.add(uuidAttr);
-
- /*
- * Add the uuid in the entry
- */
- Map<AttributeType, List<Attribute>> operationalAttributes = entry
- .getOperationalAttributes();
- operationalAttributes.put(Historical.entryuuidAttrType, uuidList);
+ Entry entry = initializeEntry();
// load historical from the entry
Historical hist = Historical.load(entry);
@@ -143,38 +119,55 @@
}
/**
+ * Test that conflict between a modify-replace and modify-add for
+ * single-valued attributes are handled correctly.
+ */
+ @Test()
+ public void replaceAndAddSingle() throws Exception
+ {
+ Entry entry = initializeEntry();
+
+ // load historical from the entry
+ Historical hist = Historical.load(entry);
+
+ /*
+ * simulate a modify-replace done at time t10
+ */
+ testModify(entry, hist, "displayname", ModificationType.REPLACE,
+ "init value", 10, true);
+
+ /*
+ * Now simulate an add at an earlier date that the previous replace
+ * conflict resolution should remove it.
+ */
+ testModify(entry, hist, "displayname", ModificationType.ADD,
+ "older value", 1, false);
+
+ /*
+ * Now simulate an add at an earlier date that the previous replace
+ * conflict resolution should remove it. (a second time to make
+ * sure...)
+ */
+ testModify(entry, hist, "displayname", ModificationType.ADD,
+ "older value", 2, false);
+
+ /*
+ * Now simulate an add at a later date that the previous replace.
+ * conflict resolution should remove it
+ */
+ testModify(entry, hist, "displayname", ModificationType.ADD, "new value",
+ 11, false);
+
+ }
+
+ /**
* Test that conflict between modify-add and modify-replace for
* multi-valued attributes are handled correctly.
*/
@Test()
public void addAndReplace() throws Exception
{
- DN dn = DN.decode("dc=com");
- Map<ObjectClass, String> objectClasses = new HashMap<ObjectClass, String>();
- ObjectClass org = DirectoryServer.getObjectClass("organization");
- objectClasses.put(org, "organization");
-
- Entry entry = new Entry(dn, objectClasses, null, null);
-
- // Construct a new random UUID. and add it into the entry
- UUID uuid = UUID.randomUUID();
-
- // Create the att values list of uuid
- LinkedHashSet<AttributeValue> valuesUuid =
- new LinkedHashSet<AttributeValue>(1);
- valuesUuid.add(new AttributeValue(Historical.entryuuidAttrType,
- new ASN1OctetString(uuid.toString())));
- ArrayList<Attribute> uuidList = new ArrayList<Attribute>(1);
- Attribute uuidAttr = new Attribute(Historical.entryuuidAttrType,
- "entryUUID", valuesUuid);
- uuidList.add(uuidAttr);
-
- /*
- * Add the uuid in the entry
- */
- Map<AttributeType, List<Attribute>> operationalAttributes = entry
- .getOperationalAttributes();
- operationalAttributes.put(Historical.entryuuidAttrType, uuidList);
+ Entry entry = initializeEntry();
// load historical from the entry
Historical hist = Historical.load(entry);
@@ -208,6 +201,399 @@
"new value", 11, true);
}
+
+ /**
+ * Test that conflict between modify-add and modify-replace for
+ * single-valued attributes are handled correctly.
+ */
+ @Test()
+ public void addAndReplaceSingle() throws Exception
+ {
+ Entry entry = initializeEntry();
+
+ // load historical from the entry
+ Historical hist = Historical.load(entry);
+
+ /*
+ * simulate a modify-add done at time 2
+ */
+ testModify(entry, hist, "displayname", ModificationType.ADD,
+ "init value", 2, true);
+
+ /*
+ * Now simulate a replace at an earlier date that the previous replace
+ * conflict resolution should keep it.
+ */
+ testModify(entry, hist, "displayname", ModificationType.REPLACE,
+ "older value", 1, true);
+
+ /*
+ * Now simulate a replace at a later date.
+ * Conflict resolution should keept it.
+ */
+ testModify(entry, hist, "displayname", ModificationType.REPLACE,
+ "older value", 3, true);
+
+ }
+
+ /**
+ * Test that conflict between a modify-delete-attribute and modify-add
+ * for multi-valued attributes are handled correctly.
+ */
+ @Test()
+ public void deleteAndAdd() throws Exception
+ {
+ Entry entry = initializeEntry();
+
+
+ // load historical from the entry
+ Historical hist = Historical.load(entry);
+
+ /*
+ * simulate a delete of the whole description attribute done at time
+ * t10
+ */
+ testModify(entry, hist, "description", ModificationType.DELETE, null, 10,
+ true);
+
+ /*
+ * Now simulate an add at an earlier date that the previous delete. The
+ * conflict resolution should detect that this add must be ignored.
+ */
+ testModify(entry, hist, "description", ModificationType.ADD,
+ "older value", 1, false);
+
+ /*
+ * Now simulate an add at an earlier date that the previous delete. The
+ * conflict resolution should detect that this add must be ignored. (a
+ * second time to make sure that historical information is kept...)
+ */
+ testModify(entry, hist, "description", ModificationType.ADD,
+ "older value", 2, false);
+
+ /*
+ * Now simulate an add at a later date that the previous delete.
+ * conflict resolution should keep it
+ */
+ testModify(entry, hist, "description", ModificationType.ADD, "new value",
+ 11, true);
+ }
+
+ /**
+ * Test that conflict between a modify-delete-attribute and modify-add
+ * for multi-valued attributes are handled correctly.
+ */
+ @Test()
+ public void deleteAndAddSingle() throws Exception
+ {
+ Entry entry = initializeEntry();
+
+
+ // load historical from the entry
+ Historical hist = Historical.load(entry);
+
+ /*
+ * simulate a delete of the whole description attribute done at time
+ * t2
+ */
+ testModify(entry, hist, "displayname", ModificationType.DELETE, null, 3,
+ true);
+
+ /*
+ * Now simulate an add at an earlier date that the previous delete. The
+ * conflict resolution should detect that this add must be ignored.
+ */
+ testModify(entry, hist, "displayname", ModificationType.ADD,
+ "older value", 1, false);
+
+ /*
+ * Now simulate an add at an earlier date that the previous delete. The
+ * conflict resolution should detect that this add must be ignored. (a
+ * second time to make sure that historical information is kept...)
+ */
+ testModify(entry, hist, "displayname", ModificationType.ADD,
+ "older value", 2, false);
+
+ /*
+ * Now simulate an add at a later date that the previous delete.
+ * conflict resolution should keep it
+ */
+ testModify(entry, hist, "displayname", ModificationType.ADD, "new value",
+ 4, true);
+ }
+
+ /**
+ * Test that conflict between a modify-delete-attribute and modify-add
+ * for multi-valued attributes are handled correctly.
+ */
+ @Test()
+ public void deleteAndReplace() throws Exception
+ {
+ Entry entry = initializeEntry();
+
+
+ // load historical from the entry
+ Historical hist = Historical.load(entry);
+
+ /*
+ * simulate a delete of the whole description attribute done at time
+ * t4
+ */
+ testModify(entry, hist, "description", ModificationType.DELETE, null, 4,
+ true);
+
+ /*
+ * Now simulate a replace at an earlier date that the previous delete. The
+ * conflict resolution should detect that this replace must be ignored.
+ */
+ testModify(entry, hist, "description", ModificationType.REPLACE,
+ "new value", 3, false);
+ }
+
+ /**
+ * Test that conflict between a modify-delete-attribute and modify-add
+ * for multi-valued attributes are handled correctly.
+ */
+ @Test()
+ public void deleteAndReplaceSingle() throws Exception
+ {
+ Entry entry = initializeEntry();
+
+
+ // load historical from the entry
+ Historical hist = Historical.load(entry);
+
+ /*
+ * simulate a delete of the whole description attribute done at time
+ * t4
+ */
+ testModify(entry, hist, "displayname", ModificationType.DELETE, null, 4,
+ true);
+
+ /*
+ * Now simulate a replace at an earlier date that the previous delete. The
+ * conflict resolution should detect that this replace must be ignored.
+ */
+ testModify(entry, hist, "displayname", ModificationType.REPLACE,
+ "new value", 3, false);
+ }
+
+ /**
+ * Test that conflict between a modify-add and modify-add for
+ * multi-valued attributes are handled correctly.
+ */
+ @Test()
+ public void addAndAdd() throws Exception
+ {
+ Entry entry = initializeEntry();
+
+ // load historical from the entry
+ Historical hist = Historical.load(entry);
+
+ /*
+ * simulate a add of the description attribute done at time t10
+ */
+ testModify(entry, hist, "description", ModificationType.ADD,
+ "init value", 10, true);
+ /*
+ * Now simulate an add at an earlier date that the previous add. The
+ * conflict resolution should detect that this add must be kept.
+ */
+ testModify(entry, hist, "description", ModificationType.ADD,
+ "older value", 1, true);
+
+ /*
+ * Now simulate an add with a value already existing.
+ * The conflict resolution should remove this add.
+ */
+ testModify(entry, hist, "description", ModificationType.ADD,
+ "older value", 2, false);
+
+ /*
+ * Now simulate an add at a later date that the previous add. conflict
+ * resolution should keep it
+ */
+ testModify(entry, hist, "description", ModificationType.ADD, "new value",
+ 11, true);
+ }
+
+ /**
+ * Test that conflict between a modify-add and modify-add for
+ * multi-valued attributes are handled correctly.
+ */
+ @Test()
+ public void addAndAddSingle() throws Exception
+ {
+ Entry entry = initializeEntry();
+
+ // load historical from the entry
+ Historical hist = Historical.load(entry);
+
+ /*
+ * simulate a add of the description attribute done at time t10
+ */
+ testModify(entry, hist, "displayname", ModificationType.ADD,
+ "init value", 10, true);
+ /*
+ * Now simulate an add at an earlier date that the previous add. The
+ * conflict resolution should detect that this add must be kept.
+ * and that the previous value must be discarded, and therefore
+ * turn the add into a replace.
+ */
+ Modification mod = buildMod("displayname", ModificationType.ADD,
+ "older value");
+ List<Modification> mods = replayModify(entry, hist, mod, 1);
+
+ /*
+ * After replay the mods should contain only one mod,
+ * the mod should now be a replace with the older value.
+ */
+ testMods(mods, 1, ModificationType.REPLACE, "older value");
+
+ /*
+ * Now simulate a new value at a later date.
+ * The conflict modify code should detect that there is already a value
+ * and skip this change.
+ */
+ mod = buildMod("displayname", ModificationType.ADD, "new value");
+ mods = replayModify(entry, hist, mod, 2);
+ assertTrue(mods.isEmpty());
+ }
+
+ /**
+ * Test that conflict between add delete and add are handled correctly.
+ */
+ @Test()
+ public void addDelAddSingle() throws Exception
+ {
+ Entry entry = initializeEntry();
+
+ // load historical from the entry
+ Historical hist = Historical.load(entry);
+
+ /*
+ * simulate a add of the description attribute done at time t1
+ */
+ testModify(entry, hist, "displayname", ModificationType.ADD,
+ "init value", 1, true);
+
+ /*
+ * simulate a del of the description attribute done at time t3
+ * this should be processed normally
+ */
+ testModify(entry, hist, "displayname", ModificationType.DELETE,
+ "init value", 3, true);
+
+ /*
+ * Now simulate another add, that would come from another master
+ * and done at time t2 (between t1 and t2)
+ * This add should not be processed.
+ */
+ testModify(entry, hist, "displayname", ModificationType.ADD,
+ "second value", 2, false);
+ }
+
+ /**
+ * Test that conflict between add, add and delete are handled correctly.
+ *
+ * This test simulate the case where a simple add is done on a first server
+ * and at a later date but before replication happens, a add followed by
+ * a delete of the second value is done on another server.
+ * The test checks that the firs tvalue wins and stay in the entry.
+ */
+ @Test()
+ public void addAddDelSingle() throws Exception
+ {
+ Entry entry = initializeEntry();
+
+ // load historical from the entry
+ Historical hist = Historical.load(entry);
+
+ /*
+ * simulate a add of the description attribute done at time t1
+ */
+ testModify(entry, hist, "displayname", ModificationType.ADD,
+ "first value", 1, true);
+
+ /*
+ * simulate a add of the description attribute done at time t2
+ * with a second value. This should not work because there is already
+ * a value
+ */
+ testModify(entry, hist, "displayname", ModificationType.ADD,
+ "second value", 2, false);
+
+ /*
+ * Now simulate a delete of the second value.
+ * The delete should not be accepted because it is done on a value
+ * that did not get into the entry.
+ */
+ testModify(entry, hist, "displayname", ModificationType.DELETE,
+ "second value", 2, false);
+ }
+
+ /**
+ * Check that the mods given as first parameter match the next parameters.
+ *
+ * @param mods The mods that must be tested.
+ * @param size the size that the mods must have.
+ * @param modType the type of Modification that the first mod of the
+ * mods should have.
+ * @param value the value that the first mod of the mods should have.
+ */
+ private void testMods(
+ List<Modification> mods, int size, ModificationType modType, String value)
+ {
+ assertEquals(size, mods.size());
+ Modification newMod = mods.get(0);
+ assertTrue(newMod.getModificationType().equals(modType));
+ AttributeValue val = newMod.getAttribute().getValues().iterator().next();
+ assertEquals(val.getStringValue(), value);
+ }
+
+ /**
+ * Create an initialize an entry that can be used for modify conflict
+ * resolution tests.
+ */
+ private Entry initializeEntry() throws DirectoryException
+ {
+ /*
+ * Objectclass and DN do not have any impact on the modifty conflict
+ * resolution for the description attribute. Always use the same values
+ * for all these tests.
+ */
+ DN dn = DN.decode("dc=com");
+ Map<ObjectClass, String> objectClasses = new HashMap<ObjectClass, String>();
+ ObjectClass org = DirectoryServer.getObjectClass("organization");
+ objectClasses.put(org, "organization");
+
+ /*
+ * start with a new entry with an empty attribute
+ */
+ Entry entry = new Entry(dn, objectClasses, null, null);
+
+ // Construct a new random UUID. and add it into the entry
+ UUID uuid = UUID.randomUUID();
+
+ // Create the att values list
+ LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(
+ 1);
+ values.add(new AttributeValue(Historical.entryuuidAttrType,
+ new ASN1OctetString(uuid.toString())));
+ ArrayList<Attribute> uuidList = new ArrayList<Attribute>(1);
+ Attribute uuidAttr = new Attribute(Historical.entryuuidAttrType,
+ "entryUUID", values);
+ uuidList.add(uuidAttr);
+
+ /*
+ * Add the uuid in the entry
+ */
+ Map<AttributeType, List<Attribute>> operationalAttributes = entry
+ .getOperationalAttributes();
+
+ operationalAttributes.put(Historical.entryuuidAttrType, uuidList);
+ return entry;
+ }
/*
* helper function.
@@ -262,8 +648,100 @@
}
}
- /*
- * helper function.
+ /**
+ *
+ *
+ */
+ private void testModify(Entry entry,
+ Historical hist, String attrName,
+ ModificationType modType, String value,
+ int date, boolean keepChangeResult)
+ {
+ Modification mod = buildMod(attrName, modType, value);
+ List<Modification> mods = replayModify(entry, hist, mod, date);
+
+ if (keepChangeResult)
+ {
+ /*
+ * The last change should have been detected as newer and
+ * should be kept by the conflict resolution code.
+ */
+ assertTrue(mods.contains(mod));
+ assertEquals(1, mods.size());
+ }
+ else
+ {
+ /*
+ * The last older change should have been detected as conflicting and
+ * should be removed by the conflict resolution code.
+ */
+ assertFalse(mods.contains(mod));
+ assertEquals(0, mods.size());
+ }
+ }
+
+ /**
+ *
+ */
+ private List<Modification> replayModify(
+ Entry entry, Historical hist, Modification mod, int date)
+ {
+ InternalClientConnection connection =
+ InternalClientConnection.getRootConnection();
+ ChangeNumber t = new ChangeNumber(date, (short) 0, (short) 0);
+
+ List<Modification> mods = new ArrayList<Modification>();
+ mods.add(mod);
+
+ ModifyOperation modOp = new ModifyOperation(connection, 1, 1, null,
+ entry.getDN(), mods);
+ ModifyContext ctx = new ModifyContext(t, "uniqueId");
+ modOp.setAttachment(SYNCHROCONTEXT, ctx);
+
+ hist.replayOperation(modOp, entry);
+ if (mod.getModificationType() == ModificationType.ADD)
+ {
+ AddOperation addOp = new AddOperation(connection, 1, 1, null, entry
+ .getDN(), entry.getObjectClasses(), entry.getUserAttributes(),
+ entry.getOperationalAttributes());
+ testHistorical(hist, addOp);
+ }
+ else
+ {
+ testHistoricalAndFake(hist, entry);
+ }
+
+ /*
+ * Check that the encoding decoding of historical information
+ * works by encoding decoding and checking that the result is the same
+ * as the initial value.
+ */
+ entry.removeAttribute(Historical.historicalAttrType);
+ entry.addAttribute(hist.encode(), null);
+ Historical hist2 = Historical.load(entry);
+ assertEquals(hist2.encode().toString(), hist.encode().toString());
+
+ return mods;
+ }
+
+ private Modification buildMod(
+ String attrName, ModificationType modType, String value)
+ {
+ /* create AttributeType that will be used for this test */
+ AttributeType attrType =
+ DirectoryServer.getAttributeType(attrName, true);
+
+ LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>();
+ if (value != null)
+ values.add(new AttributeValue(attrType, value));
+ Attribute attr = new Attribute(attrType, attrName, values);
+ Modification mod = new Modification(modType, attr);
+
+ return mod;
+ }
+
+ /**
+ *
*/
private void testHistorical(
Historical hist, AddOperation addOp)
@@ -288,223 +766,4 @@
}
}
}
-
- /**
- * Test that conflict between a modify-delete-attribute and modify-add
- * for multi-valued attributes are handled correctly.
- */
- @Test()
- public void deleteAndAdd() throws Exception
- {
- /*
- * Objectclass and DN do not have any impact on the modifty conflict
- * resolution for the description attribute. Always use the same values
- * for all these tests.
- */
- DN dn = DN.decode("dc=com");
- Map<ObjectClass, String> objectClasses = new HashMap<ObjectClass, String>();
- ObjectClass org = DirectoryServer.getObjectClass("organization");
- objectClasses.put(org, "organization");
-
- /*
- * start with a new entry with an empty description
- */
- Entry entry = new Entry(dn, objectClasses, null, null);
-
- // Construct a new random UUID. and add it into the entry
- UUID uuid = UUID.randomUUID();
-
- // Create the att values list
- LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(
- 1);
- values.add(new AttributeValue(Historical.entryuuidAttrType,
- new ASN1OctetString(uuid.toString())));
- ArrayList<Attribute> uuidList = new ArrayList<Attribute>(1);
- Attribute uuidAttr = new Attribute(Historical.entryuuidAttrType,
- "entryUUID", values);
- uuidList.add(uuidAttr);
-
- /*
- * Add the uuid in the entry
- */
- Map<AttributeType, List<Attribute>> operationalAttributes = entry
- .getOperationalAttributes();
-
- operationalAttributes.put(Historical.entryuuidAttrType, uuidList);
-
-
- // load historical from the entry
- Historical hist = Historical.load(entry);
-
- /*
- * simulate a delete of the whole description attribute done at time
- * t10
- */
- testModify(entry, hist, "description", ModificationType.DELETE, null, 10,
- true);
-
- /*
- * Now simulate an add at an earlier date that the previous delete. The
- * conflict resolution should detect that this add must be ignored.
- */
- testModify(entry, hist, "description", ModificationType.ADD,
- "older value", 1, false);
-
- /*
- * Now simulate an add at an earlier date that the previous delete. The
- * conflict resolution should detect that this add must be ignored. (a
- * second time to make sure that historical information is kept...)
- */
- testModify(entry, hist, "description", ModificationType.ADD,
- "older value", 2, false);
-
- /*
- * Now simulate an add at a later date that the previous delete.
- * conflict resolution should keep it
- */
- testModify(entry, hist, "description", ModificationType.ADD, "new value",
- 11, true);
- }
-
- /**
- * Test that conflict between a modify-add and modify-add for
- * multi-valued attributes are handled correctly.
- */
- @Test()
- public void addAndAdd() throws Exception
- {
- /*
- * Objectclass and DN do not have any impact on the modifty conflict
- * resolution for the description attribute. Always use the same values
- * for all these tests.
- */
- DN dn = DN.decode("dc=com");
- Map<ObjectClass, String> objectClasses = new HashMap<ObjectClass, String>();
- ObjectClass org = DirectoryServer.getObjectClass("organization");
- objectClasses.put(org, "organization");
-
- /*
- * start with a new entry with an empty description
- */
- Entry entry = new Entry(dn, objectClasses, null, null);
-
- // Construct a new random UUID. and add it into the entry
- UUID uuid = UUID.randomUUID();
-
- // Create the att values list
- LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(
- 1);
- values.add(new AttributeValue(Historical.entryuuidAttrType,
- new ASN1OctetString(uuid.toString())));
- ArrayList<Attribute> uuidList = new ArrayList<Attribute>(1);
- Attribute uuidAttr = new Attribute(Historical.entryuuidAttrType,
- "entryUUID", values);
- uuidList.add(uuidAttr);
-
- /*
- * Add the uuid in the entry
- */
- Map<AttributeType, List<Attribute>> operationalAttributes = entry
- .getOperationalAttributes();
-
- operationalAttributes.put(Historical.entryuuidAttrType, uuidList);
-
- // load historical from the entry
- Historical hist = Historical.load(entry);
-
- /*
- * simulate a add of the description attribute done at time t10
- */
- testModify(entry, hist, "description", ModificationType.ADD,
- "init value", 10, true);
- /*
- * Now simulate an add at an earlier date that the previous add. The
- * conflict resolution should detect that this add must be kept.
- */
- testModify(entry, hist, "description", ModificationType.ADD,
- "older value", 1, true);
-
- /*
- * Now simulate an add at an earlier date that the previous add. The
- * conflict resolution should detect that this add must be kept. (a
- * second time to make sure that historical information is kept...)
- */
- testModify(entry, hist, "description", ModificationType.ADD,
- "older value", 2, false);
-
- /*
- * Now simulate an add at a later date that the previous add. conflict
- * resolution should keep it
- */
- testModify(entry, hist, "description", ModificationType.ADD, "new value",
- 11, true);
- }
-
- /*
- * helper function.
- */
- private void testModify(Entry entry,
- Historical hist, String attrName,
- ModificationType modType, String value,
- int date, boolean keepChangeResult)
- {
- InternalClientConnection connection =
- InternalClientConnection.getRootConnection();
- ChangeNumber t = new ChangeNumber(date, (short) 0, (short) 0);
-
- /* create AttributeType description that will be used for this test */
- AttributeType attrType =
- DirectoryServer.getAttributeType(attrName, true);
-
- LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>();
- if (value != null)
- values.add(new AttributeValue(attrType, value));
- Attribute attr = new Attribute(attrType, attrName, values);
- List<Modification> mods = new ArrayList<Modification>();
- Modification mod = new Modification(modType, attr);
- mods.add(mod);
-
- ModifyOperation modOp = new ModifyOperation(connection, 1, 1, null,
- entry.getDN(), mods);
- ModifyContext ctx = new ModifyContext(t, "uniqueId");
- modOp.setAttachment(SYNCHROCONTEXT, ctx);
-
- hist.replayOperation(modOp, entry);
- if (modType == ModificationType.ADD)
- {
- AddOperation addOp = new AddOperation(connection, 1, 1, null, entry
- .getDN(), entry.getObjectClasses(), entry.getUserAttributes(),
- entry.getOperationalAttributes());
- testHistorical(hist, addOp);
- }
- else
- {
- testHistoricalAndFake(hist, entry);
- }
-
- /*
- * Check that the encoding decoding of historical information
- * works by encoding decoding and checking that the result is the same
- * as the initial value.
- */
- entry.removeAttribute(Historical.historicalAttrType);
- entry.addAttribute(hist.encode(), null);
- Historical hist2 = Historical.load(entry);
- assertEquals(hist2.encode().toString(), hist.encode().toString());
-
- /*
- * The last older change should have been detected as conflicting and
- * should be removed by the conflict resolution code.
- */
- if (keepChangeResult)
- {
- assertTrue(mods.contains(mod));
- assertEquals(1, mods.size());
- }
- else
- {
- assertFalse(mods.contains(mod));
- assertEquals(0, mods.size());
- }
- }
}
--
Gitblit v1.10.0