From 15d537d27c0116138191b0a60ac03b54eb00d4e5 Mon Sep 17 00:00:00 2001
From: coulbeck <coulbeck@localhost>
Date: Wed, 07 Feb 2007 16:51:16 +0000
Subject: [PATCH] An extra synchronization unit test and a number of bug fixes for attributes with options.

---
 opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/ModifyConflictTest.java |    9 
 opends/src/server/org/opends/server/synchronization/plugin/Historical.java                                 |  193 +++++++++++++++++++--------
 opends/src/server/org/opends/server/util/LDIFReader.java                                                   |   18 +-
 opends/src/server/org/opends/server/synchronization/plugin/HistVal.java                                    |    2 
 opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/HistoricalTest.java     |  166 +++++++++++++++++++++++
 5 files changed, 315 insertions(+), 73 deletions(-)

diff --git a/opends/src/server/org/opends/server/synchronization/plugin/HistVal.java b/opends/src/server/org/opends/server/synchronization/plugin/HistVal.java
index b43458c..ed4c600 100644
--- a/opends/src/server/org/opends/server/synchronization/plugin/HistVal.java
+++ b/opends/src/server/org/opends/server/synchronization/plugin/HistVal.java
@@ -89,7 +89,7 @@
       String[] optionsToken = token[0].split(";");
       options = new HashSet<String>();
       int index = 1;
-      while (optionsToken[index] != null)
+      while (index < optionsToken.length)
       {
         options.add(optionsToken[index]);
         index ++;
diff --git a/opends/src/server/org/opends/server/synchronization/plugin/Historical.java b/opends/src/server/org/opends/server/synchronization/plugin/Historical.java
index fe95ffa..1831ef3 100644
--- a/opends/src/server/org/opends/server/synchronization/plugin/Historical.java
+++ b/opends/src/server/org/opends/server/synchronization/plugin/Historical.java
@@ -96,6 +96,17 @@
                            = new HashMap<AttributeType,AttrInfoWithOptions>();
 
   /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    StringBuilder builder = new StringBuilder();
+    builder.append(encode());
+    return builder.toString();
+  }
+
+  /**
    * Duplicates an Historical Object.
    * attributesInfo are nor duplicated but used as references.
    * @return The duplicate of the Historical Object
@@ -131,12 +142,16 @@
       Attribute modAttr = m.getAttribute();
       Set<String> options = modAttr.getOptions();
       if (options.isEmpty())
+      {
         options = null;
+      }
       AttributeType type = modAttr.getAttributeType();
       AttrInfoWithOptions attrInfoWithOptions =  attributesInfo.get(type);
       AttrInfo attrInfo = null;
       if (attrInfoWithOptions != null)
+      {
         attrInfo = attrInfoWithOptions.get(options);
+      }
 
       if (this.hasConflict(attrInfo, changeNumber))
       {
@@ -148,68 +163,72 @@
 
         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();
+          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;
-          }
 
-          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();
+          case ADD:
+            this.conflictAdd(modsIterator, changeNumber, attrInfo,
+                             modAttr.getValues(), modAttr.getOptions());
             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);
+          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>());
 
-          LinkedHashSet<AttributeValue> keptValues = modAttr.getValues();
-          this.conflictAdd(modsIterator, changeNumber, attrInfo, addedValues,
-              modAttr.getOptions());
-          keptValues.addAll(addedValues);
-          break;
+            this.conflictDelete(changeNumber, type, m, modifiedEntry,
+                                attrInfo, modAttr);
 
-        case INCREMENT:
-          // TODO : FILL ME
-          break;
+            LinkedHashSet<AttributeValue> keptValues = modAttr.getValues();
+            this.conflictAdd(modsIterator, changeNumber, attrInfo, addedValues,
+                             modAttr.getOptions());
+            keptValues.addAll(addedValues);
+            break;
+
+          case INCREMENT:
+            // TODO : FILL ME
+            break;
         }
       }
       else
+      {
         processLocalOrNonConflictModification(changeNumber, m);
+      }
     }
 
     // TODO : now purge old historical information
 
     if (moreRecentChangenumber == null ||
-        moreRecentChangenumber.older(changeNumber))
+         moreRecentChangenumber.older(changeNumber))
+    {
       moreRecentChangenumber = changeNumber;
+    }
   }
 
   /**
@@ -240,17 +259,25 @@
 
     Attribute modAttr = mod.getAttribute();
     if (modAttr.getAttributeType().equals(historicalAttrType))
+    {
       return;
+    }
     Set<String> options = modAttr.getOptions();
     if (options.isEmpty())
+    {
       options = null;
+    }
     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.
@@ -260,7 +287,9 @@
     {
       attrInfo = new AttrInfo();
       if (attrInfoWithOptions == null)
+      {
         attrInfoWithOptions = new AttrInfoWithOptions();
+      }
       attrInfoWithOptions.put(options, attrInfo);
       attributesInfo.put(type, attrInfoWithOptions);
     }
@@ -268,9 +297,13 @@
     {
     case DELETE:
       if (modAttr.getValues().isEmpty())
+      {
         attrInfo.delete(changeNumber);
+      }
       else
+      {
         attrInfo.delete(modAttr.getValues(), changeNumber);
+      }
       break;
 
     case ADD:
@@ -319,10 +352,26 @@
         processLocalOrNonConflictModification(changeNumber, mod);
       }
       if (moreRecentChangenumber == null ||
-          moreRecentChangenumber.older(changeNumber))
+           moreRecentChangenumber.older(changeNumber))
+      {
         moreRecentChangenumber = changeNumber;
+      }
     }
 
+    Attribute attr = encode();
+    Modification mod;
+    mod = new Modification(ModificationType.REPLACE, attr);
+    mods.add(mod);
+    modifiedEntry.removeAttribute(historicalAttrType);
+    modifiedEntry.addAttribute(attr, null);
+  }
+
+  /**
+   * Encode the historical information in an operational attribute.
+   * @return The historical information encoded in an operational attribute.
+   */
+  public Attribute encode()
+  {
     LinkedHashSet<AttributeValue> hist = new LinkedHashSet<AttributeValue>();
 
     for (Map.Entry<AttributeType, AttrInfoWithOptions> entryWithOptions :
@@ -330,7 +379,7 @@
 
     {
       AttributeType type = entryWithOptions.getKey();
-      HashMap<Set<String> ,AttrInfo> attrwithoptions =
+      HashMap<Set<String> , AttrInfo> attrwithoptions =
                                 entryWithOptions.getValue().getAttributesInfo();
 
       for (Map.Entry<Set<String>, AttrInfo> entry : attrwithoptions.entrySet())
@@ -342,8 +391,15 @@
         ChangeNumber deleteTime = info.getDeleteTime();
 
         if (options != null)
+        {
+          StringBuilder optionsBuilder = new StringBuilder();
           for (String s : options)
-            optionsString.concat(";"+s);
+          {
+            optionsBuilder.append(';');
+            optionsBuilder.append(s);
+          }
+          optionsString = optionsBuilder.toString();
+        }
 
         /* generate the historical information for deleted attributes */
         if (deleteTime != null)
@@ -391,7 +447,6 @@
           String strValue = type.getNormalizedPrimaryName()
               + optionsString + ":" + deleteTime.toString()
               + ":attrDel";
-          delAttr = false;
           AttributeValue val = new AttributeValue(historicalAttrType, strValue);
           hist.add(val);
         }
@@ -399,16 +454,16 @@
     }
 
     Attribute attr;
-    Modification mod;
 
     if (hist.isEmpty())
+    {
       attr = new Attribute(historicalAttrType, HISTORICALATTRIBUTENAME, null);
+    }
     else
+    {
       attr = new Attribute(historicalAttrType, HISTORICALATTRIBUTENAME, hist);
-    mod = new Modification(ModificationType.REPLACE, attr);
-    mods.add(mod);
-    modifiedEntry.removeAttribute(historicalAttrType);
-    modifiedEntry.addAttribute(attr, null);
+    }
+    return attr;
   }
 
   /**
@@ -428,15 +483,22 @@
     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)
+      }
+      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;
+    }
   }
 
   /**
@@ -515,9 +577,13 @@
 
       modAttr.setValues(replValues);
       if (changeNumber.newer(attrInfo.getDeleteTime()))
+      {
         attrInfo.setDeleteTime(changeNumber);
+      }
       if (changeNumber.newer(attrInfo.getLastUpdateTime()))
+      {
         attrInfo.setLastUpdateTime(changeNumber);
+      }
     }
     else
     {
@@ -566,7 +632,9 @@
         }
       }
       if (changeNumber.newer(attrInfo.getLastUpdateTime()))
+      {
         attrInfo.setLastUpdateTime(changeNumber);
+      }
     }
     return true;
   }
@@ -668,10 +736,14 @@
       }
     }
     if (addValues.isEmpty())
+    {
       modsIterator.remove();
+    }
 
     if (changeNumber.newer(attrInfo.getLastUpdateTime()))
+    {
       attrInfo.setLastUpdateTime(changeNumber);
+    }
     return true;
   }
 
@@ -691,7 +763,9 @@
     AttrInfoWithOptions attrInfoWithOptions = null;
 
     if (hist == null)
+    {
       return histObj;
+    }
 
     for (Attribute attr : hist)
     {
@@ -734,8 +808,7 @@
         }
         else
         {
-          attrType = lastAttrType;
-          if (options != lastOptions)
+          if (!options.equals(lastOptions))
           {
             attrInfo = new AttrInfo();
             attrInfoWithOptions.put(options, attrInfo);
@@ -782,6 +855,8 @@
     /* set the reference to the historical information in the entry */
     return histObj;
   }
+
+
   /**
    * Use this historical information to generate fake operations that would
    * result in this historical information.
diff --git a/opends/src/server/org/opends/server/util/LDIFReader.java b/opends/src/server/org/opends/server/util/LDIFReader.java
index d5da92c..ba873b1 100644
--- a/opends/src/server/org/opends/server/util/LDIFReader.java
+++ b/opends/src/server/org/opends/server/util/LDIFReader.java
@@ -983,8 +983,8 @@
    *                                entry (used for writing reject information).
    * @param  line                   The line to decode.
    * @param  entryDN                The DN of the entry being decoded.
-   * @param  attributeName          The name of the attribute to return
-   *                                the values for.
+   * @param  attributeName          The name and options of the attribute to
+   *                                return the values for.
    *
    * @return                        The attribute in octet string form.
    * @throws  LDIFException         If a problem occurs while trying to decode
@@ -1007,13 +1007,17 @@
     String attrDescr = line.substring(0, colonPos);
     Attribute attribute = parseAttrDescription(attrDescr);
     String attrName = attribute.getName();
-    String lowerName = toLowerCase(attrName);
 
-    if(attributeName != null && !toLowerCase(attributeName).equals(lowerName))
+    if (attributeName != null)
     {
-      int msgID = MSGID_LDIF_INVALID_CHANGERECORD_ATTRIBUTE;
-      String message = getMessage(msgID, lowerName, attributeName);
-      throw new LDIFException(msgID, message, lastEntryLineNumber, false);
+      Attribute expectedAttr = parseAttrDescription(attributeName);
+
+      if (!attribute.equals(expectedAttr))
+      {
+        int msgID = MSGID_LDIF_INVALID_CHANGERECORD_ATTRIBUTE;
+        String message = getMessage(msgID, attrDescr, attributeName);
+        throw new LDIFException(msgID, message, lastEntryLineNumber, false);
+      }
     }
 
     //  Now parse the attribute value.
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/HistoricalTest.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/HistoricalTest.java
new file mode 100644
index 0000000..fdf922f
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/HistoricalTest.java
@@ -0,0 +1,166 @@
+/*
+ * 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.synchronization.plugin;
+
+import org.opends.server.synchronization.SynchronizationTestCase;
+import org.opends.server.TestCaseUtils;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.tools.LDAPModify;
+import org.opends.server.types.DN;
+import org.opends.server.types.Entry;
+import org.opends.server.types.Attribute;
+import org.opends.server.core.DirectoryServer;
+import org.testng.annotations.Test;
+import org.testng.annotations.BeforeClass;
+import static org.testng.Assert.assertEquals;
+
+import java.util.List;
+
+/**
+ * Tests the Historical class.
+ */
+public class HistoricalTest
+     extends SynchronizationTestCase
+{
+  /**
+   * Set up synchronization on the test backend.
+   * @throws Exception If an error occurs.
+   */
+  @BeforeClass
+  @Override
+  public void setUp()
+       throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+
+    // Create an internal connection.
+    connection = InternalClientConnection.getRootConnection();
+
+    // Top level synchronization provider.
+    String synchroStringDN = "cn=Synchronization Providers,cn=config";
+
+    // Multimaster synchronization plugin.
+    synchroPluginStringDN = "cn=Multimaster Synchronization, "
+         + synchroStringDN;
+    String synchroPluginLdif = "dn: "
+         + synchroPluginStringDN
+         + "\n"
+         + "objectClass: top\n"
+         + "objectClass: ds-cfg-synchronization-provider\n"
+         + "ds-cfg-synchronization-provider-enabled: true\n"
+         + "ds-cfg-synchronization-provider-class: " +
+         "org.opends.server.synchronization.MultimasterSynchronization\n";
+    synchroPluginEntry = TestCaseUtils.entryFromLdifString(synchroPluginLdif);
+
+    // The synchronization server.
+    String changeLogStringDN = "cn=Changelog Server, " + synchroPluginStringDN;
+    String changeLogLdif = "dn: " + changeLogStringDN + "\n"
+         + "objectClass: top\n"
+         + "objectClass: ds-cfg-synchronization-changelog-server-config\n"
+         + "cn: Changelog Server\n" + "ds-cfg-changelog-port: 8989\n"
+         + "ds-cfg-changelog-server-id: 1\n";
+    changeLogEntry = TestCaseUtils.entryFromLdifString(changeLogLdif);
+
+    // The suffix to be synchronized.
+    String synchroServerStringDN = "o=test, " + synchroPluginStringDN;
+    String synchroServerLdif = "dn: " + synchroServerStringDN + "\n"
+         + "objectClass: top\n"
+         + "objectClass: ds-cfg-synchronization-provider-config\n"
+         + "cn: example\n"
+         + "ds-cfg-synchronization-dn: o=test\n"
+         + "ds-cfg-changelog-server: localhost:8989\n"
+         + "ds-cfg-directory-server-id: 1\n"
+         + "ds-cfg-receive-status: true\n";
+    synchroServerEntry = TestCaseUtils.entryFromLdifString(synchroServerLdif);
+
+    configureSynchronization();
+  }
+
+  /**
+   * Tests that the attribute modification history is correctly read from
+   * and written to an operational attribute of the entry.
+   * @throws Exception If the test fails.
+   */
+  @Test
+  public void testEncoding()
+       throws Exception
+  {
+    //  Add a test entry.
+    TestCaseUtils.addEntry(
+         "dn: uid=user.1,o=test",
+         "objectClass: top",
+         "objectClass: person",
+         "objectClass: organizationalPerson",
+         "objectClass: inetOrgPerson",
+         "uid: user.1",
+         "cn: Aaccf Amar",
+         "sn: Amar",
+         "givenName: Aaccf",
+         "userPassword: password",
+         "description: Initial description"
+       );
+
+    // Modify the test entry to give it some history.
+
+    String path = TestCaseUtils.createTempFile(
+         "dn: uid=user.1,o=test",
+         "changetype: modify",
+         "add: cn;lang-en",
+         "cn;lang-en: Aaccf Amar",
+         "cn;lang-en: Aaccf A Amar",
+         "-",
+         "replace: description",
+         "description: replaced description",
+         "-"
+    );
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+
+    // Read the entry back to get its history operational attribute.
+    DN dn = DN.decode("uid=user.1,o=test");
+    Entry entry = DirectoryServer.getEntry(dn);
+
+    // Check that encoding and decoding preserves the history information.
+    Historical hist = Historical.load(entry);
+    Attribute after = hist.encode();
+
+    List<Attribute> attrs = entry.getAttribute(Historical.historicalAttrType);
+    Attribute before = attrs.get(0);
+
+    assertEquals(after, before);
+  }
+}
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/ModifyConflictTest.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/ModifyConflictTest.java
index 14fb6d3..f87a450 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/ModifyConflictTest.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/ModifyConflictTest.java
@@ -45,9 +45,6 @@
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.synchronization.SynchronizationTestCase;
 import org.opends.server.synchronization.common.ChangeNumber;
-import org.opends.server.synchronization.plugin.FakeOperation;
-import org.opends.server.synchronization.plugin.FakeOperationComparator;
-import org.opends.server.synchronization.plugin.Historical;
 import org.opends.server.synchronization.protocol.ModifyContext;
 import org.opends.server.synchronization.protocol.SynchronizationMessage;
 import org.opends.server.synchronization.protocol.UpdateMessage;
@@ -132,7 +129,7 @@
      * sure...)
      */
     testModify(entry, hist, "description", ModificationType.ADD,
-        "older value", 2, false);;
+        "older value", 2, false);
 
     /*
      * Now simulate an add at a later date that the previous replace.
@@ -387,7 +384,7 @@
         InternalClientConnection.getRootConnection();
     ChangeNumber t = new ChangeNumber(date, (short) 0, (short) 0);
 
-    /* create AttributeType description that will be usedfor this test */
+    /* create AttributeType description that will be used for this test */
     AttributeType attrType =
       DirectoryServer.getAttributeType(attrName, true);
 
@@ -405,7 +402,7 @@
     modOp.setAttachment(SYNCHROCONTEXT, ctx);
 
     hist.replayOperation(modOp, entry);
-    if (modType.intValue() == ModificationType.ADD.intValue())
+    if (modType == ModificationType.ADD)
     {
       AddOperation addOp = new AddOperation(connection, 1, 1, null, entry
           .getDN(), entry.getObjectClasses(), entry.getUserAttributes(),

--
Gitblit v1.10.0