From 26defc28efac7cc706352bea7126a19308065468 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Wed, 23 Sep 2026 07:41:33 +0000
Subject: [PATCH] [#1021] Reset the objectClass attribute of an entry when its object classes change (#1022)
---
opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/ModifyConflictTest.java | 73 ++++++++++++++++++++++++++++++++++++
1 files changed, 73 insertions(+), 0 deletions(-)
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/ModifyConflictTest.java b/opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/ModifyConflictTest.java
index 633227b..396a2eb 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/ModifyConflictTest.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/ModifyConflictTest.java
@@ -13,6 +13,7 @@
*
* Copyright 2006-2010 Sun Microsystems, Inc.
* Portions Copyright 2011-2016 ForgeRock AS.
+ * Portions Copyright 2026 3A Systems, LLC.
*/
package org.opends.server.replication.plugin;
@@ -69,6 +70,8 @@
public class ModifyConflictTest extends ReplicationTestCase
{
private static final String ORGANIZATION = "organization";
+ private static final String OBJECTCLASS = "objectClass";
+ private static final String EXTENSIBLEOBJECT = "extensibleObject";
private static final String DISPLAYNAME = "displayName";
private static final String EMPLOYEENUMBER = "employeeNumber";
private static final String DESCRIPTION = "description";
@@ -1042,6 +1045,76 @@
}
/**
+ * Test that a replayed modification of the objectClass attribute leaves the entry handing out
+ * the object classes it ends up with.
+ * <p>
+ * Solving the conflicts of a delete reads the entry, and it does so before the core applies the
+ * modifications to it. The read must not fix what the entry answers afterwards: the backend
+ * stores the object classes of the entry but indexes it through its objectClass attribute, so
+ * an attribute left behind by the modification silently takes the objectClass index out of step
+ * with the stored entry.
+ */
+ @Test
+ public void replayObjectClassAddAndDelete() throws Exception
+ {
+ Entry entry = initializeEntry();
+ EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry);
+
+ List<Modification> mods = newArrayList(
+ newModification(ADD, OBJECTCLASS, EXTENSIBLEOBJECT),
+ newModification(DELETE, OBJECTCLASS, ORGANIZATION));
+
+ // The conflict resolution, which reads the entry to solve the delete.
+ replayModifies(entry, hist, 10, mods);
+ assertThat(mods).hasSize(2);
+
+ // The core, which applies the modifications to the entry.
+ applyModificationsTheWayTheCoreDoes(entry, mods);
+
+ assertThat(entry.getObjectClasses().values()).containsOnly(EXTENSIBLEOBJECT);
+ assertThat(objectClassAttributeOf(entry)).containsOnly(EXTENSIBLEOBJECT);
+ }
+
+ /**
+ * Applies the modifications to the entry the way {@code LocalBackendModifyOperation} does, that
+ * is with {@code addAttribute()} and {@code removeAttribute()} rather than with
+ * {@code Entry.applyModifications()}.
+ */
+ private void applyModificationsTheWayTheCoreDoes(Entry entry, List<Modification> mods)
+ {
+ for (Modification mod : mods)
+ {
+ Attribute attr = mod.getAttribute();
+ switch (mod.getModificationType().asEnum())
+ {
+ case ADD:
+ entry.addAttribute(attr, new LinkedList<ByteString>());
+ break;
+ case DELETE:
+ entry.removeAttribute(attr, new LinkedList<ByteString>());
+ break;
+ default:
+ entry.replaceAttribute(attr);
+ break;
+ }
+ }
+ }
+
+ /** Returns the object classes of the entry as its objectClass attribute reports them. */
+ private List<String> objectClassAttributeOf(Entry entry)
+ {
+ List<String> values = new LinkedList<>();
+ for (Attribute attr : entry.getAllAttributes(getObjectClassAttributeType()))
+ {
+ for (ByteString value : attr)
+ {
+ values.add(value.toString());
+ }
+ }
+ return values;
+ }
+
+ /**
* Check that the mods given as first parameter match the next parameters.
*
* @param mods The mods that must be tested.
--
Gitblit v1.10.0