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/config/JMXMBean.java |  113 ++++++++++++++++++++++++++++++++++++++++----------------
 1 files changed, 80 insertions(+), 33 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/opends/server/config/JMXMBean.java b/opendj-server-legacy/src/main/java/org/opends/server/config/JMXMBean.java
index 8e06561..318101c 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/config/JMXMBean.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/config/JMXMBean.java
@@ -13,7 +13,7 @@
  *
  * Portions Copyright 2006-2007-2008 Sun Microsystems, Inc.
  * Portions Copyright 2013-2016 ForgeRock AS.
- * Portions Copyright 2023-2025 3A Systems LLC.
+ * Portions Copyright 2023-2026 3A Systems LLC.
  */
 package org.opends.server.config;
 
@@ -49,6 +49,7 @@
 import static org.opends.server.protocols.internal.Requests.newSearchRequest;
 import static org.opends.server.util.CollectionUtils.newArrayList;
 import static org.opends.server.util.ServerConstants.MBEAN_BASE_DOMAIN;
+import static org.opends.server.util.StaticUtils.byteToHex;
 import static org.opends.server.util.StaticUtils.isAlpha;
 import static org.opends.server.util.StaticUtils.isDigit;
 
@@ -95,40 +96,16 @@
   {
       try
       {
-          String typeStr = null;
-          String dnString = configEntryDN.toString();
-          if (dnString != null && dnString.length() != 0)
+          StringBuilder buffer = new StringBuilder();
+          // Walk the RDNs from the root, rather than splitting the DN string at every comma.
+          for (int j = configEntryDN.size() - 1; j >= 0; j--)
           {
-              StringBuilder buffer = new StringBuilder(dnString.length());
-              String rdns[] = dnString.replace(',', ';').split(";");
-              for (int j = rdns.length - 1; j >= 0; j--)
-              {
-                  int rdnIndex = rdns.length - j;
-                  buffer.append(",Rdn").append(rdnIndex).append("=") ;
-                  for (int i = 0; i < rdns[j].length(); i++)
-                  {
-                      char c = rdns[j].charAt(i);
-                      if (isAlpha(c) || isDigit(c))
-                      {
-                          buffer.append(c);
-                      } else
-                      {
-                          switch (c)
-                          {
-                              case ' ':
-                                  buffer.append("_");
-                                  break;
-                              case '=':
-                                  buffer.append("-");
-                          }
-                      }
-                  }
-              }
-
-              typeStr = buffer.toString();
+              int rdnIndex = configEntryDN.size() - j;
+              buffer.append(",Rdn").append(rdnIndex).append("=");
+              appendJmxRdn(buffer, configEntryDN.parent(j).rdn());
           }
-
-          return MBEAN_BASE_DOMAIN + ":" + "Name=rootDSE" + typeStr;
+          // The root DN keeps the name it always had, which ends with "null".
+          return MBEAN_BASE_DOMAIN + ":" + "Name=rootDSE" + (buffer.length() != 0 ? buffer : "null");
       } catch (Exception e)
       {
         logger.traceException(e);
@@ -138,6 +115,76 @@
   }
 
   /**
+   * Appends the JMX form of an RDN to the provided buffer. The attribute name keeps only its letters and
+   * digits, and is followed by '-' and the value. A value made of letters, digits and spaces is written as it
+   * always was, with '_' for a space. Any other value would lose characters that way, and two DNs would share
+   * a name, so it is percent-encoded instead: its letters and digits are kept, a space is still written as '_',
+   * and every other character, '_' included, is written as the '%' encoded bytes of its UTF-8 form. The SNMP
+   * extension relies on the '_': it finds the connection handlers, whose names hold an IP address, by
+   * "Connection_Handler" and their statistics by "_Statistics". The AVAs of a multi-valued RDN are joined
+   * with '+'.
+   */
+  private static void appendJmxRdn(StringBuilder buffer, RDN rdn)
+  {
+    boolean first = true;
+    for (AVA ava : rdn)
+    {
+      if (!first)
+      {
+        buffer.append('+');
+      }
+      first = false;
+      String name = ava.getAttributeName();
+      for (int i = 0; i < name.length(); i++)
+      {
+        char c = name.charAt(i);
+        if (isAlpha(c) || isDigit(c))
+        {
+          buffer.append(c);
+        }
+      }
+      buffer.append('-');
+      String value = ava.getAttributeValue().toString();
+      if (isPlainJmxValue(value))
+      {
+        buffer.append(value.replace(' ', '_'));
+      }
+      else
+      {
+        for (byte b : ava.getAttributeValue().toByteArray())
+        {
+          char c = (char) (b & 0xFF);
+          if (c == ' ')
+          {
+            buffer.append('_');
+          }
+          else if (c < 0x80 && (isAlpha(c) || isDigit(c)))
+          {
+            buffer.append(c);
+          }
+          else
+          {
+            buffer.append('%').append(byteToHex(b));
+          }
+        }
+      }
+    }
+  }
+
+  private static boolean isPlainJmxValue(String value)
+  {
+    for (int i = 0; i < value.length(); i++)
+    {
+      char c = value.charAt(i);
+      if (!isAlpha(c) && !isDigit(c) && c != ' ')
+      {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
    * Creates a new dynamic JMX MBean for use with the Directory Server.
    *
    * @param  configEntryDN  The DN of the configuration entry with which this

--
Gitblit v1.10.0