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