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