From ab6eacf10af3f59ac3509c6a4e5e20192022c2ab Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Thu, 23 Feb 2012 15:23:18 +0000
Subject: [PATCH] Fix OPENDJ-409: Add support for optimistic concurrency control via ETag-like attributes
---
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/EntityTagVirtualAttributeProviderTestCase.java | 123 ++++++++++++++++++++++++++++++++++++++++
1 files changed, 122 insertions(+), 1 deletions(-)
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/EntityTagVirtualAttributeProviderTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/EntityTagVirtualAttributeProviderTestCase.java
index 80e49b9..4ea7b19 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/EntityTagVirtualAttributeProviderTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/EntityTagVirtualAttributeProviderTestCase.java
@@ -39,15 +39,22 @@
import org.opends.messages.MessageBuilder;
import org.opends.server.TestCaseUtils;
import org.opends.server.admin.server.ConfigurationChangeListener;
-import org.opends.server.admin.std.meta.VirtualAttributeCfgDefn;
import org.opends.server.admin.std.meta.EntityTagVirtualAttributeCfgDefn.ChecksumAlgorithm;
+import org.opends.server.admin.std.meta.VirtualAttributeCfgDefn;
import org.opends.server.admin.std.meta.VirtualAttributeCfgDefn.ConflictBehavior;
import org.opends.server.admin.std.meta.VirtualAttributeCfgDefn.Scope;
import org.opends.server.admin.std.server.EntityTagVirtualAttributeCfg;
import org.opends.server.admin.std.server.VirtualAttributeCfg;
+import org.opends.server.controls.LDAPAssertionRequestControl;
import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.ModifyOperation;
import org.opends.server.core.SearchOperation;
import org.opends.server.core.SearchOperationWrapper;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.protocols.internal.InternalSearchOperation;
+import org.opends.server.protocols.ldap.LDAPFilter;
+import org.opends.server.protocols.ldap.LDAPModification;
+import org.opends.server.schema.DirectoryStringSyntax;
import org.opends.server.types.*;
import org.opends.server.util.StaticUtils;
import org.testng.annotations.BeforeClass;
@@ -557,6 +564,120 @@
+ /**
+ * Simulates the main use case for entity tag support: optimistic concurrency.
+ * <p>
+ * This test reads an entry requesting its etag, then performs an update using
+ * an assertion control to prevent the change from being applied if the etag
+ * has changed since the read was performed.
+ *
+ * @throws Exception
+ * If an unexpected exception occurred.
+ */
+ public void testOptimisticConcurrency() throws Exception
+ {
+ // Use an internal connection.
+ AttributeType etagType = DirectoryServer.getAttributeType("etag");
+ AttributeType descrType = DirectoryServer.getAttributeType("description");
+ String userDN = "uid=test.user,ou=People,o=test";
+ InternalClientConnection conn = InternalClientConnection
+ .getRootConnection();
+
+ // Create a test backend containing the user entry to be modified.
+ TestCaseUtils.initializeTestBackend(true);
+
+ // @formatter:off
+ TestCaseUtils.addEntries(
+ "dn: ou=People,o=test",
+ "objectClass: top",
+ "objectClass: organizationalUnit",
+ "ou: People",
+ "",
+ "dn: uid=test.user,ou=People,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: test.user",
+ "givenName: Test",
+ "sn: User",
+ "cn: Test User",
+ "userPassword: password");
+ // @formatter:on
+
+ // Read the user entry and get the etag.
+ Entry e1 = readEntry(conn, userDN);
+ String etag1 = e1
+ .getAttributeValue(etagType, DirectoryStringSyntax.DECODER);
+ assertNotNull(etag1);
+
+ // Apply a change using the assertion control for optimistic concurrency.
+ List<RawModification> mods = Collections
+ .<RawModification> singletonList(new LDAPModification(
+ ModificationType.REPLACE, RawAttribute.create("description",
+ "first modify")));
+ List<Control> ctrls = Collections
+ .<Control> singletonList(new LDAPAssertionRequestControl(true,
+ LDAPFilter.createEqualityFilter("etag", ByteString.valueOf(etag1))));
+ ModifyOperation modifyOperation = conn.processModify(userDN, mods, ctrls);
+ assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+
+ // Reread the entry and check that the description has been added and that
+ // the etag has changed.
+ Entry e2 = readEntry(conn, userDN);
+
+ String etag2 = e2
+ .getAttributeValue(etagType, DirectoryStringSyntax.DECODER);
+ assertNotNull(etag2);
+ assertFalse(etag1.equals(etag2));
+
+ String description2 = e2.getAttributeValue(descrType,
+ DirectoryStringSyntax.DECODER);
+ assertNotNull(description2);
+ assertEquals(description2, "first modify");
+
+ // Simulate a concurrent update: perform another update using the old etag.
+ mods = Collections.<RawModification> singletonList(new LDAPModification(
+ ModificationType.REPLACE, RawAttribute.create("description",
+ "second modify")));
+ modifyOperation = conn.processModify(userDN, mods, ctrls);
+ assertEquals(modifyOperation.getResultCode(), ResultCode.ASSERTION_FAILED);
+
+ // Reread the entry and check that the description and etag have not
+ // changed.
+ Entry e3 = readEntry(conn, userDN);
+
+ String etag3 = e3
+ .getAttributeValue(etagType, DirectoryStringSyntax.DECODER);
+ assertNotNull(etag3);
+ assertEquals(etag2, etag3);
+
+ String description3 = e3.getAttributeValue(descrType,
+ DirectoryStringSyntax.DECODER);
+ assertNotNull(description3);
+ assertEquals(description3, description2);
+ }
+
+
+
+ private Entry readEntry(InternalClientConnection conn, String userDN)
+ throws DirectoryException
+ {
+ LinkedHashSet<String> attrList = new LinkedHashSet<String>(2);
+ attrList.add("*");
+ attrList.add("etag");
+ InternalSearchOperation searchOperation = conn.processSearch(userDN,
+ SearchScope.BASE_OBJECT, DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0,
+ false, "(objectClass=*)", attrList);
+ assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS);
+ assertEquals(searchOperation.getSearchEntries().size(), 1);
+ Entry e = searchOperation.getSearchEntries().get(0);
+ assertNotNull(e);
+ return e;
+ }
+
+
+
private AttributeValue getEntityTag(final Entry e)
{
final Set<AttributeValue> values = provider.getValues(e, null);
--
Gitblit v1.10.0