From 3f4deb91789189521d577457bd6da27de8fd75b1 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Wed, 07 Oct 2026 08:31:10 +0000
Subject: [PATCH] [#1153] Parse the whole DN string, and build or split DN strings through DN instead of string operations (#1171)
---
opendj-server-legacy/src/main/java/org/opends/server/tools/upgrade/UpgradeUtils.java | 143 +++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 143 insertions(+), 0 deletions(-)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/tools/upgrade/UpgradeUtils.java b/opendj-server-legacy/src/main/java/org/opends/server/tools/upgrade/UpgradeUtils.java
index e9be4cb..2e1d4e0 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/tools/upgrade/UpgradeUtils.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/tools/upgrade/UpgradeUtils.java
@@ -25,14 +25,18 @@
import java.io.FilenameFilter;
import java.io.IOException;
import java.nio.file.Files;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.Assertion;
+import org.forgerock.opendj.ldap.AVA;
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ConditionResult;
@@ -47,6 +51,7 @@
import org.forgerock.opendj.ldap.requests.ModifyRequest;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.requests.SearchRequest;
+import org.forgerock.opendj.ldap.schema.AttributeType;
import org.forgerock.opendj.ldap.schema.CoreSchema;
import org.forgerock.opendj.ldap.schema.MatchingRule;
import org.forgerock.opendj.ldap.schema.Schema;
@@ -65,6 +70,8 @@
import static org.forgerock.opendj.ldap.schema.SchemaOptions.*;
import static org.opends.messages.ConfigMessages.*;
import static org.opends.messages.ToolMessages.*;
+import static org.opends.server.schema.SchemaConstants.EMR_DN_OID;
+import static org.opends.server.schema.SchemaConstants.EMR_UNIQUE_MEMBER_OID;
import static org.opends.server.tools.upgrade.FileManager.*;
import static org.opends.server.tools.upgrade.Installation.*;
import static org.opends.server.util.ChangeOperationType.*;
@@ -303,6 +310,142 @@
return baseDNs;
}
+ /**
+ * Returns, for each enabled pluggable backend of the server, the attributes with an equality index whose matching
+ * rule compares DNs: "distinguishedNameMatch" (member, owner, seeAlso...) or "uniqueMemberMatch" (uniqueMember).
+ * Backends without such an index are left out.
+ *
+ * @return The names of these attributes, per backend ID.
+ * @throws IOException
+ * If the configuration or a schema file cannot be read.
+ */
+ static Map<String, Set<String>> getDNEqualityIndexedAttributesPerBackend() throws IOException
+ {
+ return getDNEqualityIndexedAttributesPerBackend(configFile, configSchemaDirectory);
+ }
+
+ /**
+ * Returns, for each enabled pluggable backend in the provided configuration, the attributes with an equality index
+ * whose matching rule compares DNs, as {@link #getDNEqualityIndexedAttributesPerBackend()} does. The matching rule
+ * of an attribute is read from the schema files of the instance, so that an attribute of a custom schema is found
+ * too.
+ *
+ * @param config
+ * The configuration file.
+ * @param schemaDirectory
+ * The directory of the schema files.
+ * @return The names of these attributes, per backend ID.
+ * @throws IOException
+ * If the configuration or a schema file cannot be read.
+ */
+ static Map<String, Set<String>> getDNEqualityIndexedAttributesPerBackend(final File config,
+ final File schemaDirectory) throws IOException
+ {
+ final Schema schema = readSchemaFiles(schemaDirectory);
+ final Set<String> enabledBackends = new HashSet<>();
+ final Map<String, Set<String>> attributes = new TreeMap<>();
+ try (LDIFEntryReader reader = new LDIFEntryReader(new FileInputStream(config)))
+ {
+ while (reader.hasNext())
+ {
+ final Entry entry = reader.readEntry();
+ if (hasObjectClass(entry, "ds-cfg-pluggable-backend")
+ && "true".equalsIgnoreCase(entry.parseAttribute("ds-cfg-enabled").asString()))
+ {
+ enabledBackends.add(entry.parseAttribute("ds-cfg-backend-id").asString());
+ }
+ else if (hasObjectClass(entry, "ds-cfg-backend-index")
+ && hasValueIgnoringCase(entry, "ds-cfg-index-type", "equality"))
+ {
+ final String attribute = entry.parseAttribute("ds-cfg-attribute").asString();
+ final String backendID = getBackendID(entry.getName());
+ if (attribute != null && backendID != null && comparesDNs(schema.getAttributeType(attribute)))
+ {
+ attributes.computeIfAbsent(backendID, id -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER)).add(attribute);
+ }
+ }
+ }
+ }
+ attributes.keySet().retainAll(enabledBackends);
+ return attributes;
+ }
+
+ private static Schema readSchemaFiles(final File schemaDirectory) throws IOException
+ {
+ final SchemaBuilder builder = new SchemaBuilder(Schema.getCoreSchema());
+ final File[] files = schemaFilesInReadOrder(schemaDirectory);
+ if (files != null)
+ {
+ for (final File file : files)
+ {
+ try (LDIFEntryReader reader = new LDIFEntryReader(new FileInputStream(file)))
+ {
+ while (reader.hasNext())
+ {
+ builder.addSchema(reader.readEntry(), true);
+ }
+ }
+ }
+ }
+ return builder.toSchema().asNonStrictSchema();
+ }
+
+ /**
+ * Returns the LDIF files of a schema directory in the order the server reads them, so that a later file may
+ * redefine an attribute of an earlier one.
+ *
+ * @param schemaDirectory
+ * The schema directory.
+ * @return The LDIF files, or {@code null} if the directory cannot be listed.
+ */
+ static File[] schemaFilesInReadOrder(final File schemaDirectory)
+ {
+ final File[] files = schemaDirectory.listFiles((dir, name) -> name.toLowerCase().endsWith(".ldif"));
+ if (files != null)
+ {
+ Arrays.sort(files);
+ }
+ return files;
+ }
+
+ private static boolean comparesDNs(final AttributeType attributeType)
+ {
+ final MatchingRule equality = attributeType.getEqualityMatchingRule();
+ return equality != null
+ && (EMR_DN_OID.equals(equality.getOID()) || EMR_UNIQUE_MEMBER_OID.equals(equality.getOID()));
+ }
+
+ private static boolean hasObjectClass(final Entry entry, final String objectClass)
+ {
+ return hasValueIgnoringCase(entry, "objectClass", objectClass);
+ }
+
+ private static boolean hasValueIgnoringCase(final Entry entry, final String attribute, final String value)
+ {
+ for (final String v : entry.parseAttribute(attribute).asSetOfString())
+ {
+ if (value.equalsIgnoreCase(v))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Returns the ID of the backend whose configuration entry is, or is above, the provided DN. */
+ private static String getBackendID(final DN dn)
+ {
+ for (DN d = dn; d != null && !d.isRootDN(); d = d.parent())
+ {
+ final AVA ava = d.rdn().getFirstAVA();
+ if ("ds-cfg-backend-id".equalsIgnoreCase(ava.getAttributeName()))
+ {
+ return ava.getAttributeValue().toString();
+ }
+ }
+ return null;
+ }
+
static EntryReader searchConfigFile(final SearchRequest searchRequest) throws FileNotFoundException
{
final Schema schema = getUpgradeSchema();
--
Gitblit v1.10.0