From dcc67378350418e11e50e72996c24b61f1ea8f81 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

---
 opends/src/server/org/opends/server/replication/plugin/Historical.java |  693 ++++++++++----------------------------------------------
 1 files changed, 131 insertions(+), 562 deletions(-)

diff --git a/opends/src/server/org/opends/server/replication/plugin/Historical.java b/opends/src/server/org/opends/server/replication/plugin/Historical.java
index 03d50b5..5e7aacc 100644
--- a/opends/src/server/org/opends/server/replication/plugin/Historical.java
+++ b/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 */

--
Gitblit v1.10.0